JAVA_HOME
系统变量,变量值为JDK安装路径%JAVA_HOME%\bin
与%JAVA_HOME%\jre\bin
java -version
命令显示版本号先确保已下载Git、IDEA已经配置好Git
64-bit Git for Windows Setup
并安装git(一路next)git config --global --replace-all user.name "你的 git 的名称"
git config --global --replace-all uesr.email "你的 git 的邮箱"
Java是一种混合型语言,先将.java文件编译成.class二进制字节码文件,然后再按行交给JVM进行解释
JDK:Java开发工具包,包含了JVM、核心类库、开发工具(如javac、java、jdb等)。
JRE:Java运行环境,包含JVM、核心类库、运行工具(如java)。
原码:最高位为符号位,0正1负,原码进行正数计算不会出现问题,负数计算的结果和预期结果相反。
反码:为了解决原码不能计算负数,负数反码:符号位不变,其余位取反;反码计算时跨0会出错,由于有-0和+0两个0。
补码:为了解决反码不能跨0,负数补码=反码+1,因此10000000规定为-128,计算机数字的存储和计算都是以补码的形式进行的。
注:(1) long类型定义变量时,数据值最后应加上L或l;float类型定义变量时,数据值最后应加上F或f
(2) 基本数据类型数据值存储在自己的空间中,赋值给其它变量时赋的是真实值
(3) 引用数据类型变量在栈空间中存储的是地址值,数据值存储在该地址指向的堆空间中,赋值给其它变量时赋的是地址值
(4) ==号对于基本数据类型比较的是数据值,而引用数据类型比较的是地址值
(5) 基本数据类型作为实参传入方法的形参时,会重新生成一个值相等的新的变量,因此变量原型不会发生改变;而引用数据类型作为实参传入方法的
形参时,由于传递的是引用数据类型的地址值,在方法中改变参数变量的同时变量原型也会发生改变(String字符串由于是不可变的,故不作考虑)。
一、Java的输入:Scanner类
import java.util.Scanner;
Scanner scan = new Scanner(System.in);
int a = scan.nextInt();
注:Scanner类有针对不同类型的接收方法,如next()和nextLine()接收字符串、nextInt()接收整数、nextDouble()接收小数等
nextLine()是遇到回车就停止接收数据,其它都是遇到空格就停止接收数据
二、Java的输出
注:只要有字符串参与的+运算,就代表连接操作;对于多个+则从左到右依次执行,但只有遇到字符串后+才开始变成连接符
一、Java的判断语句
if (关系表达式) {
关系表达式为true时要执行的语句;
} else {
关系表达式为false时要执行的语句; //当不考虑关系表达式为false时的情况可以省略else
}
switch (表达式) {
case 值1: //表达式值为值1时,则执行语句体1。注:若对于值1、值2要执行的语句体一样,则可写为case 值1,值2:相同语句体;
语句体1;
break;
case 值2: //表达式值为值2时,则执行语句体2
语句体2;
break;
...
default: //表达式值与上述的值都不匹配时,则执行语句体n+1
语句体n + 1;
break;
}
switch (表达式) {
case 值1 -> {
语句体1;
}
case 值2 -> {
语句体2;
}
...
default -> {
语句体n + 1;
}
}
注:if语句的多条件判断:一般适用于对范围的判断,如考试成绩的分段
switch语句:一般适用于可以将数据一一列举出来,如星期几
switch语句新特性:数据类型 变量名 = switch(表达式){ }; 其中语句体的赋值可以省略
二、Java的循环语句(continue跳过当前层的本次循环;break结束当前层的整个循环;可在循环语句前加上loop:,再用break loop;
语句跳出指定层的循环(一般用于多层嵌套循环),loop只是一个标记,用别的字符也可以表示,continue同样适用)
for (初始化语句; 条件判断语句; 条件控制语句) {
循环体语句;
}
初始化语句;
while (条件判断语句) {
循环体语句;
条件控制语句;
}
for(数组/集合的数据类型 变量名 : 数组名/集合名) {
循环体语句(只适合取数据,不能更改数据);
}
注:for循环和while循环的区别(for循环中的初始化语句只在for循环中可用,出了for循环则不可用)
for循环:知道循环次数或循环范围
while循环:只知道循环结束条件
一、一维数组的定义与初始化
数据类型[] 数组名
如int[] array; 数据类型 数组名[]
如int array[];该方式不推荐(不符合阿里巴巴编码规范)数据类型[] 数组名 = new 数据类型[]{元素1, 元素2, 元素3...};
//new 数据类型[]可省略,直接跟{}数据类型[] 数组名 = new 数据类型[数组长度];
二、二维数组的定义与初始化
数据类型[][] 数组名
如int[][] array; 数据类型 数组名[][]
如int array[][];该方式不推荐(不符合阿里巴巴编码规范)数据类型[][] 数组名 = new 数据类型[][]{{元素1,元素2,...},{元素3,元素4,...},...};
//new 数据类型[][]可省略,直接跟{};其中二维数组包含的一维数组元素的长度可以不同,建议把每一个一维数组单独写成一行便于阅读数据类型[][] 数组名 = new 数据类型[m][n];
其中m表示二维数组中一维数组的个数;n表示每个一维数组中元素的个数。注意:可以不指定n的值,后期再创建一维数组,再将一维数组的地址赋值给二维数组表示的一维数组,如array2D[0] = new int[5];
array2D[0]指的是第一个一维数组的地址。也可以指定n的值,后期再将地址覆盖也可实现变长二维数组。三、数组的长度(array.length
,该length是数组的属性,不是方法,故不加括号)
一、方法的定义
修饰符 返回值类型 方法名([参数类型 参数名,...]) {
...
方法体
...
[return 返回值;]
}
修饰符:定义了方法的访问类型
default
(即默认,什么也不写):在同一包内可见,不使用任何修饰符。使用对象:类、接口、变量、方法private
:在同一类内可见。使用对象:变量、方法。 注意:不能修饰类(外部类)public
:对所有类可见。使用对象:类、接口、变量、方法protected
:对同一包内的类和所有子类可见。使用对象:变量、方法。 注意:不能修饰类(外部类)static
:用来修饰静态方法和静态变量(属于类的,不属于对象,不用实例化就可以通过类名.方法名
调用,静态方法只能调用静态方法和静态变量,非静态方法都可以访问,且静态方法没有this关键字),静态变量存储在堆内存的静态区中,是该类所有对象共享的,静态变量和方法是随着类的加载而加载的,优先于对象出现的。final
:用来修饰类、方法和变量。final 修饰的类不能够被继承;final修饰的方法不能被继承类重新定义;final修饰的变量为常量,只能被赋值一次(常量命名规则:单个单词全部大写;多个单词全部大写,单词之间用下划线隔开)(final修饰的变量若为基本类型,那么变量存储的数据值不能发生改变;final修饰的变量若为引用类型,那么变量存储的地址值不能发生改变,对象的内部可以改变)。abstract
:用来创建抽象类(不能用来实例化对象,目的为了对该类进行扩充)和抽象方法(没有任何实现的方法,该方法的具体实现由子类提供)。synchronized
:声明的方法同一时间只能被一个线程访问。volatile
:修饰的成员变量在每次被线程访问时,都强制从共享内存中重新读取该成员变量的值;当成员变量发生变化时,会强制线程将变化值回写到共享内存;保证不同线程在任何时刻看到的成员变量都是相同的值。返回值类型:方法可能会有返回值,若无返回值则用关键字void
方法名:所定义方法的名称,小驼峰命名法(首单词的首字母小写,其后的单词的首字母都大写),如getSum
可定义为求和方法名
参数类型:形参,像是一个占位符,当方法被调用时,将实参的值传递给该参数,可有多个形参也可一个没有
方法体:定义该方法功能的代码
二、方法的重载
定义:在同一个类中,方法名相同,参数不同(参数类型、参数个数、参数顺序),与返回值无关
优点:(1)定义端(方法的提供者):使用相同的方法名(一个方法)来表示功能相同的(多个)方法
(2)调用端(方法的使用者): 在调用的时候,可以使用相同名字(一个名字)的方法实现不同的功能
(3)重载也是多态性的体现:一个内容,可以实现多个功能
当方法出现重载现象,JVM会优先调用实参和形参类型一致的那个方法。
三、可变参数
方法的形参的个数是可以发生变化的,格式:属性类型...形参名
,如int…args,就可以传入不确定个数的int类型的数据,底层仍是通过数组实现的,args就相当于数组名。但是在方法中最多只能有一个可变参数,且如果还有其它形参,则可变参数必须写在最后。
一、类:类是对某一类事物的描述,是抽象的、概念上的意义
public class 类名 { //类名采用大驼峰命名格式,即全部单词的首字母大写;类不能用private进行修饰,即类不能是私有的
(1) 成员变量(属性,名词)
(2) 成员方法(行为,动词)
(3) 构造器
(4) 代码块
(5) 内部类 //见5.6节
}
虚拟机调用
的,不能手动调用构造方法,若不写任何构造方法,虚拟机会给出一个无参构造方法,若写了一个有参构造,则必须写无参构造才可以无参实例化对象;每创建一次对象,就会调用一次构造方法。无论是否使用,建议无参构造和有参构造都写。若唯一的构造方法用private修饰了,证明该类无法被创建成对象,即无法实例化对象,则只能根据类名调用该类的静态成员和静态方法。修饰符 类名(参数) {
方法体;
}
static{}
括起来的代码,随着类的加载而加载(即要使用这个类的时候),并且自动触发,只执行一次。可以在程序运行时提前完成一些只进行一次的数据初始化操作。Alt+Insert
),有参和无参构造方法,toString()方法以及其它成员方法,ptg插件可快速生成标准JavaBean。二、对象:实际存在的该类事物的实例化个体
类名 对象名 = new 类名();
对象名.成员变量
对象名.方法名
三、封装
定义:封装是指一种将抽象性函式接口的实现细节部分包装、隐藏起来的方法。把类中的某些信息进行隐藏,从而使外部程序不能直接对这些信息进行直接的访问,只能通过类中定义的方法对这些隐藏的信息进行操作和访问。
目的:防止该类的代码和数据被外部类定义的代码随机访问;要访问该类的代码和数据,必须通过严格的接口控制。提高了程序的安全性和便利性,对象代表什么,就得封装对应的数据,并提供数据对应的行为,如get、set方法。
四、this关键字(this中存的是当前对象的地址值,对于非静态方法都有一个隐藏的当前类对象this形参,静态方法则没有)
this.属性名
:访问的是类的成员变量,用来区分成员变量和局部变量的重名问题this.方法名
:访问本类的成员方法this()
:访问本类的构造方法(可以有参数来指定对应的有参构造,只能在构造方法中且必须是第一条语句)public class 子类 extends 父类 { } //一个子类只能继承一个父类,但可以多层继承(父类可以还有它的父类)
super();
,不写也存在,且必须是第一句。也可以用super(参数…)调用父类的有参构造。this.成员变量名
调用本类的成员变量(不存在就会从父类中找),通过super.成员变量名
调用父类的成员变量。this.方法名()
调用本类的成员方法(不存在就从父类中找),super.方法名()
调用父类的成员方法。@Override
注解可以验证重写是否存在语法错误。父类类型 对象名称 = new 子类类型()
。对象名 instanceof 类名
来判断该对象是否是该类的类型,再进行强制转换。对象名 instanceof 类名 新的对象名
将判断和强转合并为一句,若是则强转后的变量名为新的对象名
,若不是则返回false。public abstract 返回值类型 方法名(参数列表);
public abstract class 类名{}
。抽象类不能被实例化;抽象类可以有构造方法(便于子类定义构造方法super,当创建子类对象时,给属性赋值);抽象类的子类要么重写抽象类中的所有抽象方法,要么子类也是抽象类;抽象类虽然不能实例化对象,但可以定义抽象类的引用,仍可以完成抽象的引用指向子类对象,即抽象类也可以实现多态。interface
实现:public interface 接口名 {}
implements
关键字来实现,public class 类名 implements 接口名 {}
;一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类;一个类只能继承一个父类,但可以实现多个接口。public static final
;接口没有构造方法;接口成员方法只能是抽象方法(JDK7之前),默认修饰符public abstract
。public default 返回值类型 方法名(参数列表) {}
。默认方法不是抽象方法,因此不强制实现类重写,但是如果默认方法被实现类重写,则重写时应去掉default关键字
;如果实现多个接口有同名的默认方法,那么实现类必须对该默认方法进行重写。public static 返回值类型 方法名(参数列表) {}
。接口中的静态方法只能通过接口名调用,不能通过实现类名或对象名调用。private 返回值类型 方法名(参数列表) {}
为默认方法提供服务;private static 返回值类型 方法名(参数列表) {}
为静态方法提供服务(静态方法只能访问静态方法和静态变量)。接口类型 对象名称 = new 实现类类型()
this.成员变量名
调用内部类的成员变量;外部类名.this.成员变量名
调用外部类的成员变量,这是因为在执行内部类方法的时候会默认添加一个变量外部类名.this
即外部类的地址值。外部类名.内部类名 对象名 = new 外部类名().new 内部类名();
,相当于先创建一个外部类对象后再创建内部类对象。Object 内部类对象名 = 外部类对象.getXXX();
,由于内部类私有,外部其它类识别不了内部类,可用它的父类引用,但是这样的话对内部类特殊的成员和方法就无法使用了,因此在用内部类对象的时候直接使用外部类对象.getXXX()
来获取,不赋值给某变量。static
修饰的内部类,属于成员内部类的一种。静态内部类只能访问外部类中的静态变量和静态方法,若要访问外部类的非静态成员则需要创建外部类对象。静态内部类对象创建方式:外部类名.内部类名 对象名 = new 外部类名.内部类名();
,静态内部类中的非静态成员只有先创建对象后才可以调用;静态内部类中的静态成员可直接调用外部类名.内部类名.方法名()
。new 类名或接口名(){ 重写的方法 };
定义了一个继承某类或实现某接口并重写方法的子类对象或实现类对象,这样就可以更加方便的使用多态类名或接口名 对象名 = new 类名或接口名(){ 重写的方法 };
而不用再重新定义一个继承某类的子类或实现某接口的实现类了,尤其是那些只使用一次的子类或实现类。 函数式编程(Functional programming)是一种思想特点。函数式编程思想,忽略面向对象的复杂语法,强调做什么,而不是谁去做。Lambda表达式就是函数式思想的体现,Lambda 表达式,也可称为闭包,它允许把函数作为一个方法的参数(函数作为参数传递进方法中)。
格式:(形参) ->{ 方法体 }
(1)参数类型可省略不写。
(2)如果只有一个参数,参数类型可省略不写,()也可省略不写。
(3)如果方法体只有一行,{}、return和分号都可省略不写,这三个需同时省略。
Lambda表达式只能用于实现接口的匿名内部类,且该接口中只能有一个抽象方法,且它是作为函数的参数出现的,可以在接口上方用@FunctionalInterface
注解来验证是否是函数式接口。可以使代码更加简洁。
Consumer
,抽象方法void accept(T t);,接收一个参数但无返回值,常用于打印等消费动作。Supplier
,抽象方法T get();,没有输入参数但返回一个T类型数据,常用于信息的获取。Function
,抽象方法R apply(T t);,对传入参数T进行操作,返回数据R,类似于一个映射函数。Predicate
,抽象方法boolean test(T t);,判断类型为T的对象是否符合约束条件,用于条件判断。 方法引用:把已经有的方法拿过来用,当作函数式接口中抽象方法的方法体。::
方法引用符
条件:
(1)引用处必须是函数式接口。
(2)被引用方法必须已经存在,且形参和返回值必须和抽象方法保持一致,且被引用方法功能要满足当前需求。
类名::静态方法名
对象::成员方法
,本类可用this来代替本类对象(静态方法无this和super),父类可用super来代替父类对象。类名::new
,用于对象的创建,如用Stream流的map将某数据映射为对象。类名::成员方法
,该方法要保证成员方法的形参和抽象方法从第二个开始的形参保持一致即可。数据类型[]::new
由于Java基本数据类型int、double、char等不是对象,不符合Java面向对象编程特点,因此每个基本数据类型都设计了一个对应的类进行代表,统称为包装类(Wrapper Class),均位于java.lang
包。
可通过静态方法来创建对象,如Integer integer = Integer.valueOf(int value);
,注:-128~127范围的Integer是提前创建好的。
自动装箱
:基本数据类型会自动转换为引用数据类型(底层仍是调用上边静态方法),如Integer integer = 10;
自动拆箱
:包装类会自动转换为对应的基本数据类型,如int i = integer;(JDK5之后出现的)
常用方法:
(1)将整数转换为二进制字符串 String str = Integer.toBinaryString(int value);
(2)将整数转换为八进制字符串 String str = Integer.toOctalString(int value);
(3)将整数转换为十六进制字符串 String str = Integer.toHexString(int value);
(4)字符串按进制转换为int整数 int value = Integer.parseInt(String str, int radix); //8大包装类除了Character都有对应的强转方法
泛型是JDK5中引入的特性,可以在编译阶段约束操作的数据类型,并进行检查。
泛型的格式:<数据类型>,注意:泛型只能支持引用数据类型,且可以传入该数据类型的子类类型,不写<>则默认是Object类型。
引入泛型的原因:对于集合来说,如果没有泛型,那么集合存储的是Object类型,可以存储全部类型对象(向上转型),但是在取出数据的时候,会无法使用子类特有的行为,虽然可以向下转型,但是每次还得判断该子类对象的类型instanceof,否则容易出现强转异常。引入泛型就可以统一数据类型,把运行时的问题提前到了编译期间,避免了强转异常。
Java泛型是伪泛型:由于泛型是JDK5引入的,为了向下兼容,这个泛型只是在添加的时候判断是否是指定的泛型类型,但在集合中存储的仍是Object类型,取出的时候会根据泛型类型向下转型。
修饰符 class 类名<类型> {}
,类型常用T(type)、E(element)、K(key)、V(value)来代表,创建该类对象的时候,E等就会确定类型。修饰符 返回值类型 方法名(类型 变量名) {}
,当实参传入方法时,就会自动确定泛型的数据类型。修饰符 interface 接口名<类型> {}
,可在实现类定义时直接指定接口的类型,也可在实现类定义时延续该泛型。注意:泛型不具备继承性,即泛型一旦指定具体数据类型,只能传递该类型的数据,如某方法的形参是指定数据类型的集合,则调用该方法只能传该种数据类型的集合,而不能是其子类数据类型的集合。但是数据具备继承性,如在父类集合种添加子类对象。
泛型的通配符:可以限定泛型种类型的范围,比如限定在某继承体系中。
(1)? :类似于E,表示任何不确定的类型,但是不用。
(2)? extends E :表示可以传递E类型或E的所有子类类型。
(3)? super E:表示可以传递E类型或E的父类类型和父类的父类等。
API(Application Programming Interface):应用程序编程接口
Java API:JDK中提供给我们使用的类,这些类将底层的代码实现封装了起来,我们不需要关心这些类是如何实现的,只要会使用即可。
Java API在线文档:JDK11API菜鸟教程中文版 JDK19API官方英文版
Math类是帮助我们进行数学计算的工具类,里面的方法都是静态的,直接用Math.方法名()
调用。
返回类型 方法名(参数)
(1)求绝对值 int abs(int a) ;
(2)向上取整 double ceil(double a); //向正无穷大方向获取距离最近的整数
(3)向下取整 double floor(double a); //向负无穷大方向获取距离最近的整数
(4)四舍五入 int round(float a);
(5)较大值 int max(int a, int b);
(6)较小值 int min(int a, int b);
(7)a的b次幂 double pow(double a, double b);
(8)a的平方根 double sqrt(double a);
(9)a的立方根 double cbrt(double a);
(10)随机值 double random(); //[0.0,1.0)
System类也是一个工具类,提供了一些与系统相关的方法。
返回类型 方法名(参数)
(1)终止JVM void exit(int status); //status=0JVM正常停止;status非0JVM非正常停止
(2)系统时间毫秒 long currentTimeMillis();//时间戳是指格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在的总秒数.
(3)数组拷贝 void arraycopy(数据源数组, 起始索引, 目的地数组, 起始索引, 拷贝个数); //基本数据类型数组类型必须一致;引用数据类型子类类型可以赋值给父类类型
Runtime表示当前虚拟机的运行环境。应用程序无法创建自己的此类实例,可以从getRuntime方法获得当前运行时实例对象,实例对象允许应用程序与运行应用程序的环境进行交互。
返回类型 方法名(参数)
(1)当前Runtime对象 Runtime getRuntime();
(2)停止JVM void exit(int status); //status=0JVM正常停止;status非0JVM非正常停止
(3)获取CPU线程数 int availableProcessors();
(4)JVM能获得内存大小 long maxMemory(); //byte为单位,JVM尝试使用的最大内存量
(5)JVM已获得内存大小 long totalMemory(); //JVM虚拟机中的内存总量
(6)JVM可用内存大小 long freeMemory();
(7)运行cmd命令 Process exec(String command);
Object类是Java中的顶级父类,所有的类都直接或间接的继承于Object类,Object类中方法可以被所有子类访问。
返回类型 方法名(参数)
(1)返回对象的字符串表现形式 String toString(); //在打印对象想要看到属性值,重写toString方法
(2)比较对象是否相等 boolean equals(Object obj); //比较的是两个对象的地址值,可重写equals方法比较属性值
(3)对象克隆(protected修饰) Object clone(); //把A对象的属性值完全拷贝给B对象。重写clone方法,javabean类实现Cloneable接口,最后克隆并强转
(4) 返回对象的运行时类 Class<?> getClass();
(5) 返回对象的哈希码值 int hashCode(); //默认使用地址值计算哈希值,一般会重写用属性值计算
(6) 唤醒此对象监视器上等待的单个线程 void notify();
(7) 唤醒等待此对象监视器的所有线程 void notifyAll();
(8) 使当前线程等待直到被唤醒 void wait(); //被notify或notifyAll唤醒
(9) 使当前线程等待直到被唤醒 void wait(long timeoutMillis);//被唤醒或到时间
(10)使当前线程等待直到被唤醒 void wait(long timeoutMillis, int nanos);//被唤醒或到时间
浅克隆:不管对象内部的属性是基本数据类型还是引用数据类型,都完全拷贝过来。Object类的clone方法是浅拷贝(拷贝的引用数据类型的地址值)
深克隆:基本数据类型拷贝,字符串在串池复用,引用数据类型会重新创建新的。可以重写clone方法,规定引用数据类型重新创建。
Objects类是一个工具类,用于操作对象或在操作前检查某些条件。包含的方法都是静态的,即可以Objects.方法名()
直接使用。
返回类型 方法名(参数)
(1)先做非空判断,再比较两个对象 boolean equals(Object a, Object b);
(2)判断对象是否为null boolean isNull(Object a); //若a为null则返回true
(3)判断对象是否不为null boolean nonNull(Object a); //若a不为null则返回true
BigInteger支持任意精度的整数,即该类型可以准确地表示任何大小的整数值而不会丢失任何信息。BigInteger对象一旦创建里面的数据不能发生改变,只要进行计算都会产生一个新的BigInteger对象,底层通过整型数组实现。
BigInteger构造方法
(1)public BigInteger(int numBits, Random rnd); //获取随机大整数,范围在[0~2^numBits-1]
(2)public BigInteger(String val); //将十进制字符串(必须是整数)表示形式转换为BigInteger
(3)public BigInteger(String val, int radix); //将radix进制字符串(必须与进制吻合)表示形式转换为BigInteger
BigInteger静态获取BigInteger对象方法
(4)public static BigInteger valueOf(long val); //返回一个值等于long的BigInteger对象。优化了-16~16已提前创建好BigInteger对象,多次获取不会重新创建新的
BigInteger常用方法
(5)加法:add() 减法:subtract() 乘法:multiply() 除法(商):divide() 较大值:max() 较小值:min() 次幂:pow()
(6)除法(商和余数) BigInteger[] divideAndRemainder(BigInteger val);
(7)比较是否相等(属性值,BigInteger重写了equals方法) boolean equals(Object x);
(8)转为int整数 int intValue(BigInteger val); //超出int范围则会出错,类似还有longValue()、doubleValue()
BigDecimal用于小数的精确计算,可用来表示很大的小数。BigDecimal对象一旦创建里面的数据不能发生改变。底层通过byte数组实现,存储的是字符串每一个字符的ASCII对应的数值。
BigDecimal构造方法
(1)public BigDecimal(String val) //将字符串表示的数转换为BigDecimal对象
(2)public BigDecimal(double val) //将double类型转换为BigDecimal对象,结果可能非精确的,不建议使用
BigDecimal静态获取BigDecimal对象方法
(3)public static BigDecimal valueOf(double val) //该静态方法底层仍是通过(1)方法实现。优化了0~10之间的整数,不会重新new。
BigDecimal常用方法(BigInteger的方法都有)
(4)除法 public BigDecimal divide(BigDecimal divisor, int scale, RoundingMode roundingMode) //scale:小数点保留的位数;RoundingMode:舍入模式,如RoundingMode.HALF_UP为四舍五入
正则表达式可以校验字符串是否满足规则(字符串.matches(正则表达式)
);[]
代表里面内容出现一次;|
代表或者;()
代表分组;^
代表取反;&&
代表交集;{}
代表具体次数;(?i)
表示忽略大小写(只需在想要忽略大小写的前面加上即可);
正则表达式在字符串方法中的使用
(1)判断字符串是否满足正则表达式的规则 public boolean matches(String regex)
(2)按照正则表达式的规则进行替换 public String replaceAll(String regex, String newStr) //底层是用Matcher的replaceAll方法
(3)按照正则表达式的规则切割字符串 public String[] split(String regex)
字符类(只匹配一个字符)
(1)[abc] 只能是a,b,或c
(2)[^abc] 除了a,b,c之外的任何字符
(3)[a-zA-Z] a到z A到z,包括边界
(4)[a-d[m-p]] a到d,或m到p
(5)[a-z&&[def] a-z和def的交集。为:d,e,f
(6)[a-z&&[^bc]] a-z和非bc的交集。(等同于[ad-z])
(7)[a-z&&[^m-p]] a到z和除了m到p的交集。(等同于[a-lq-z])
预定义字符(只匹配一个字符)(转移字符\将特殊字符转换为普通的字符,如\\d来实现\d功能)
(1). 任何字符 用\\.来代表只能是小数点(两次转义,第一次将后边的\转为普通字符'\',而在正则表达式中字符串"\."就表示将.转义)
(2)\d 一个数字:[0-9]
(3)\D 非数字:[^0-9]
(4)\s 一个空白字符:[ \t\n\xOB\f\r]
(5)\S 非空白字符:[^\s]
(6)\w [a-zA-Z_0-9]英文、数字、下划线
(7)\W [^\w]一个非单词字符
数量词
(1)X? X,一次或0次
(2)X* X,零次或多次
(3)X+ X,一次或多次
(4)X{n} X,正好n次
(5)X{n,} X,至少n次
(6)X{n,m} X,至少n但不超过m次
分组(从1开始,遇到一个左括号就是一组,第一个左括号是第一组,第二个左括号是第二组...)
捕获分组
(1)\\组号 代表匹配结果应该和第'组号'组的内容一摸一样(不是正则表达式一样,而是与第一组匹配的内容一样)
(2)$组号 可以在正则外部调用第'组号'组的内容,如str.replaceAll("(.)\\1+","$1")就实现了对字符串重复内容的删除
非捕获分组如(?=)、(?:)、(?!),这种括号不占用组号
正则表达式也可在文本中查找满足要求的内容(爬虫)。java.util.regex.Pattern类是编译正则表达式后创建一个匹配模式;java.util.regex.Matcher类是文本(模式)匹配器。
(1)获取正则表达式对象 Pattern p = Pattern.compile("正则表达式"); //compile方法是静态方法
(2)获取文本匹配器对象 Matcher m = p.matcher("文本");
(3)判断是否有匹配的数据 boolean b = m.find(); //若有满足规则的子串,则在底层记录子串的起始索引和结束索引+1
(4)获取匹配的子串 String s = m.group(); //按照上步记录的索引对文本进行截取进而获得子串,可以有参数,表示只要第几组()
可循环获取匹配的全部子串
while(m.find()){
String s = m.group();
}
(1)(?=) 表示匹配=号后边的正则表达式,但是不返回,只返回括号前边的内容。如java(?=8|11|17),对于java8来说返回的结果是java
(2)(?:) 表示匹配:号后边的正则表达式,并且返回。如java(?:8|11|17),对于java8来说返回的结果是java8
(3)(?!) 表示不匹配!后边的正则表达式。如java(?!8|11|17),对于java8来说则不符合匹配,对java7则返回java7
(4)贪婪爬取 java默认就是贪婪爬取,如abbbbbbb字符串对正则表达式ab+的结果就是abbbbbbb
(5)非贪婪爬取 +?或*?,如abbbbbbb字符串对正则表达式ab+?的结果就是ab
以前格林威治时间(GMT)偏差太大,目前世界标准时间(UTC)原子钟。System.currentTimeMillis();
获取当前系统时间的毫秒值。
(1)java.util.Date
Date date = new Date(); //空参构造,当前系统的时间
Date date = new Date(long 毫秒值);
date.getTime(); //获取Date对象的毫秒值
date.setTime(); //设置Date对象的毫秒值
(2)java.text.SimpleDateFormat
SimpleDateFormat sdf = new SimpleDateFormat(); //空参构造,Java默认格式
SimpleDateFormat sdf = new SimpleDateFormat(String pattern); //使用指定格式,如yyyy年MM月dd日 HH:mm:ss
String str = sdf.format(Date date); //将Date对象格式化(日期对象->字符串)
Date date = sdf.parse(String str); //将字符串解析为日期对象(字符串->日期对象)
(3)java.util.Calendar 抽象类,不能直接创建对象
Calendar calendar = Calendar.getInstance(); //通过静态方法来创建对象,会把年、月、日、时间、星期等放到数组中,月份从0开始,星期从星期日开始
calendar.setTime(Date date); //通过日期对象来设置日历时间
Date date = calendar.getTime(); //获取日期对象
calendar.setTimeInMillis(long millis); //通过毫秒值来设置日历时间
long millis = calendar.getTimeInMillis(); //获取时间毫秒值
int n = calendar.get(int field); //获取日历的某个字段信息。1:年,2:月,3:一年的第几周,4:一个月的第几周,5:日。也可用Calendar的静态常量来表示
calendar.set(int field, int value); //更改日历某个字段信息的值。
calendar.add(int field, int amount); //增加或减少日历某个字段信息的值。
(1)java.time.ZoneId 时区类
Set<String> zoneIds = ZoneId.getAvailableZoneIds(); //获取全部时区
ZoneId zoneId = ZoneId.systemDefault(); //获取系统默认时区
ZoneId zoneId = ZoneId.of(String zoneId); //获取指定的时区
(2)java.time.Instant 时间戳(标准时间UTC)
Instant instant = Instant.now(); //获取当前时间的Instant对象
Instant instant = Instant.ofXxxx(long time); //根据(秒/毫秒/纳秒)获取Instant对象,如ofEpochSecond是根据秒
ZonedDateTime zdt = instant.atZone(ZoneId zoneId); //指定时间戳的时区
boolean b = instant.isXxx(Instant instant); //对时间戳之间的关系进行判断,如isAfter, isBefore
Instant in = instant.minusXxx(long xxxToSubtract); //减少时间,返回的是新的Instant对象,如minusSeconds
Instant in = instant.plusXxx(long xxxToSubtract); //增加时间,返回的是新的Instant对象,如plusSeconds
(3)java.time.ZonedDateTime 具有时区的日期时间的不可变表示
now() ofXxx() minusXxx(时间) plusXxx(时间)
ZonedDateTime time = zonedDateTime.withXxx(时间) //修改时间,如withSecond(1)表示将秒设置为1
(4)java.time.format.DateTimeFormatter 时间的格式化与解析
DateTimeFormatter dtf = DateTimeFormatter.ofPattern(String pattern); //根据格式创建对象
String str = sdf.format(时间对象); //将时间对象(Instant、ZonedDateTime)格式化为字符串
(5)java.time.LocalDate(年月日) java.time.LocalTime(时分秒) java.time.LocalDateTime(年月日时分秒)
now() of() isBefore() isAfter() getXxx() withXxx(时间) minusXxx(时间) plusXxx(时间)
localDateTime.toLocalDate() localDateTime.toLocalTime() //可以将类型强制转换,只能这个转那俩范围小的
localDate.isLeapYear(); //可用来判断是否为闰年
(6)java.time.Period(年月日) java.time.Duration(秒、纳秒) java.time.temporal.ChronoUnit(计算两个日期的时间间隔,所有单位)
Period period = Period.between(时间1,时间2); //时间2-时间1的时间间隔
period.getXxx(); //获取具体的时间间隔,如getYears
period.toTotalMonths(); //两个时间差的总月数
ChronoUnit.YEARS.between(时间1,时间2); //可计算所有类型的时间间隔
该类包含用于操作数组的各种方法(例如排序和搜索)。 此类还包含一个静态工厂,允许将数组视为列表。几乎全是静态方法。
返回类型 方法名(参数)
(1) 把数组拼接成字符串 String toString(数组);
(2) 二分查找查找元素 int binarySearch(数组, 元素);//返回结果为(负的插入点-1)
(3) 拷贝数组 int[] copyOf(原数组, 新数组长度); //不限为整型数组
(4) 拷贝数组(指定范围) int[] copyOfRange(原数组, 起始索引, 结束索引);
(5) 填充数组 void fill(数组, 填充元素); //也可指定一段范围的填充fill(数组, 起始索引, 结束索引, 填充元素);
(6) 数组排序(升序) void sort(数组); //数据<7:插入排序;数据>7时先分治递归调用插入排序,最后合并排序。
(7) 按某种规则排序数组 void sort(数组, 排序规则); //若没有指定排序规则,则调用(6),重写的compare方法:o1-o2升序,o2-o1降序
(8) 将数组转换成List List<T> Arrays.asList(T...a);
Collections是集合的工具类,将集合转换成数组:集合对象.toArray();
和集合对象.toArray(T[] a)
,空参会返回Object类型数组,有参会返回T[]类型数组(且底层会比较传入的数组长度和集合长度大小,若数组长度大,则直接复制到数组中;若集合长度大,则会重新创建一个新的数组复制并返回)。
(1) 批量添加单列集合List和Set元素 boolean addAll(Collection<T> c, T...elements); //elements是可变形参
(2) 打乱List集合顺序 void shuffle(List<?> list);
(3) List集合排序 void sort(List<T> list);
(4) List集合按指定规则进行排序 void sort(List<T> list, Comparator<T> c);
(5) List集合二分查找元素 void binarySearch(List<T> list, T key);
(6) List集合拷贝 void copy(List<T> dest, List<T> src);
(7) List集合填充 void fill(List<T> list, T obj);
(8) 求单列集合最大值 void max(Collection<T> coll); //根据默认的自然顺序进行依次比较,T需要有比较方法
(9) List集合交换元素 void swap(List<?> list, int i, int j);
该工具类用于将Stream流收集起来,即stream流对象.collect(Collectors collector);
,Collectors提供一些用不同方法收集流的收集器。
(1) 求流的平均数 Double Collectors.averagingInt(ToIntFunction<? super T> mapper); //double和long类似
(2) 求流中元素的个数 Long Collectors.counting();
(3) 求流中最大值 Optional<T> Collectors.maxBy(Comparator<? super T> comparator); //传入比较器
(4) 求流中元素的总和 IntSummaryStatistics Collectors.summingInt(ToIntFunction<? super T> mapper); //返回一个统计数据
(5) 指定分隔符 String Collectors.joining(CharSequence delimiter); //同时可以指定前后缀
(6) 按某条件分组 Map<K,List<T>> Collectors.groupingBy(Function<? super T,? extends K> classifier);
同时该分组还可以有第二个参数,即对第一次分组后的数据再进行收集器的进一步处理
(7) 对第一个参数进行某种收集器处理 Collectors.mapping(Function<? super T,? extends U> mapper, Collector<? super U,A,R> downstream)
(1) 直接赋值 String str = "superm`超";
(2) 空白构造 String str = new String();
(3) 字符串构造 String str = new String(字符串对象);
(4) 字符数组构造 String str = new String(字符数组);
(5) 字节数组构造 String str = new String(字节数组);
(1) 完全相等 boolean result = str1.equals(str2);
(2) 忽略大小写 boolean result = str1.equalsIgnoreCase(str2);
(3) 比较大小 int result = str1.compareTo(str2); //str1str2为1
(1) 字符串的长度 int length = str.length();
(2) 索引返回字符 char c = str.charAt(索引值);
(3) 截取字符串 String sub = str.substring(起始索引, 结束索引); //左闭右开
(4) 截取字符串 String sub = str.substring(起始索引); //从起始索引截止到末尾
(5) 值的替换 String newStr = str.replace(旧值,新值);
(6) 转为字符数组 char[] arr = str.toCharArray(); //若要修改字符串内容,可转为字符数组,也可截取字符串
(7) 字符串开头 boolean flag = str.startsWith("字符串"); //判断字符串是否以某字符串开头,若是则返回true
(8) 按值分割 String[] strArr = str.split("分割的值");
(9) 转为大写 String str1 = str.toUpperCase();
(10) 去除空格 String str1 = str.trim(); //去除字符串前边和后边的空格
注:如果有很多字符串变量拼接,不要直接+。在底层会创建多个对象,浪费时间,浪费性能。
StringBuilder sb = new StringBuilder();
(2)StringBuilder sb = new StringBuilder(“字符串”);
(1) 求长度 int length = sb.length();
(2) 转换为String String str = sb.toString();
(3) 反转容器内容 sb.reverse(); //对容器本身内容进行修改
(4) 末尾添加数据 sb.append(); //添加的数据可以是任意类型的字符串形式
(5) 获取sb容量大小 int capacity = sb.capacity();
(6) 在指定位置插入 sb.insert(int offset, char c)
注:StringBuilder拼接时都放在容器里,不会创建很多无用的空间,节约内存。StringBuilder默认创建一个长度为16的字节数组,若超出则扩容为原来的
容量*2+2;若扩容之后还不够就以实际长度为准。除非超过int范围21亿多就会报长度超出限制的错误。
StringBuffer与StringBuilder的方法是完全一样的,StringBuffer方法是用synchronized修饰线程同步的,因此是线程安全的,而StringBuilder用于多个线程是不安全的。
import java.util.StringJoiner;
StringJoiner sj = new StringJoiner(间隔符号);
(2)StringJoiner sj = new StringJoiner(间隔符号, 开始符号, 结束符号);
(1) 添加数据 sj.add(); //只能添加字符串,返回对象本身
(2) 求长度 int length = sj.length();
(3) 返回字符串 String str = sj.toString(); //该返回的字符串是拼接之后的结果
Collection是单列集合的祖宗接口,它的功能是全部单列集合都可以继承使用的。Interface Collection< E >。
(1) 添加对象到集合中 boolean add(E e); //添加到末尾
(2) 清空集合 void clear();
(3) 删除集合中对象 boolean remove(E e);
(4) 判断集合是否包含某对象 boolean contains(Object obj); //底层是通过equals方法来判断,故对于引用型最好重写该方法
(5) 集合是否为空 boolean isEmpty(); //底层是判断size是否为0
(6) 集合的长度 int size();
Collection的遍历方式:
(1) 获取迭代器对象 Iterator<E> iterator(); //调用对象是集合,返回一个迭代器对象,默认指向当前集合的0索引
(2) 判断当前位置是否有元素 boolean hasNext(); //有元素返回true,否则返回false
(3) 获取当前位置元素并后移 E next(); //和上边的调用者都是迭代器对象
注:若hasNext()为false还调用next()会报NoSuchElementException;
迭代器遍历完毕,指针不会复位;
next()方法用一次指针就后移了,所以之前的元素就无法再次获取了;
迭代器遍历时,不能用集合的方法进行增加或删除,会出现并发异常,可通过当前迭代器的remove()方法对集合元素进行删除。
集合.forEach(Lambda表达式)
,该Lambda表达式是实现Consumer函数式接口的accept方法,该forEach遍历底层是增强for实现的。List是继承了Collection接口的接口,有序、有索引、可重复,继承了Collection接口的方法,还定义了一些有关索引操作的方法。
(1) 在集合指定位置中插入指定元素 void add(int index, E element);
(2) 删除指定索引元素并返回 E remove(int index);
(3) 修改指定索引元素并返回原来的 E set(int index, E element);
(4) 返回指定索引处的元素 E get(int index);
List的遍历在Collection的3种遍历基础上又增加了两种遍历方式:
ListIterator li = list实现类对象.listIterator();
,在列表迭代器中可以指定初始游标的位置index,同时该迭代器增加了add()方法来向集合中添加数据。List系列集合迭代器的底层关系:
实现Iterator泛型接口的内部类Itr
来进行迭代遍历(定义了三个变量:游标cursor=0,上次调用时的位置lastRet=-1,和一个用于记录创建内部类时modCount值(当集合执行某操作如add都会自加1的变量)的变量expectedModCount=modCount 。主要有三个方法:(1)hasNext():通过判断cursor指针是否等于集合size来确定当前游标是否已经到末尾;(2)next():先检查expectedModCount是否等于modCount,若不相等(在执行next方法时,若调用集合的一些操作如add就会更改modCount的值,但是expectedModCount还是初始化记录的值)就会抛出并发修改异常,再通过get(cursor)获取当前游标指向的数据并返回,返回之前要把lastRet置为cursor的值,并把游标加1后移;(3)remove():本质上仍调用了集合的remove(lastRet)方法将上次元素删除,同时更改expectedModCount为当前的modCount来避免并发修改异常;)。同时重写了listIterator()方法和listIterator(int index)方法,listIterator()方法中调用了listIterator(0),两者都是通过一个继承内部类Itr并实现ListIterator泛型接口的内部类ListItr
来实现的,该ListItr内部类比Itr多了hasPrevious()、previous()、add()和set()等方法,这些方法仍是通过对cursor配合get、add和set等方法来进行操作的。ArrayList是List接口的实现类,因此List接口中的包括其继承的Collection接口的全部方法都有实现。
ArrayList arrayList = new ArrayList<>();
E:泛型数据类型,只能是引用数据类型(包括包装类)LinkedList也是List接口的实现类,底层数据结构是双链表,查询慢,首尾操作的速度是极快的,所以多了很多首尾操作的特有API。
(1) 在列表开头插入指定元素 void addFirst(E e);
(2) 在列表尾部追加元素 void addLast(E e);
(3) 返回列表中第一个元素 E getFirst();
(4) 返回列表中最后一个元素 E getLast();
(5) 删除并返回列表中第一个元素 E removeFirst();
(6) 删除并返回列表中最后一个元素 E removeLast(); //这写方法都可用其它方法实现,如remove(size()-1);
LinkedList底层add原理:带头尾指针的双向链表。
LinkedList底层get原理:根据index值判断是从头指针向后依次查找还是从尾指针向前依次查找。
HashSet:无序、不重复、无索引,底层是用哈希表来存储数据,哈希表是一种对增删改查数据性能都较好的结构。
哈希表在JDK8之前是用数组+链表实现,JDK8之后用数组+链表+红黑树来实现。
hashCode()
方法算出来的int类型的整数。int index = (数组长度 - 1) & 哈希值;
LinkedHashSet:有序、不重复、无索引,它是HashSet的子类,有序是指可保证存储和取出的元素顺序一致。LinkedHashSet底层数据结构依然是哈希表,只是每个元素又额外的多了一个双链表的机制记录存储的顺序。
TreeSet:不重复、无索引、可排序(按照元素的默认规则排序,从小到大)。TreeSet集合底层是基于红黑树的数据结构实现排序的,增删改查性能都较好。
TreeSet两种比较方式:
(1)默认排序/自然排序:Javabean类可以实现Comparable接口来指定比较规则,重写compareTo()方法,该方法中,this代表当前要添加的数据,o代表红黑树中已经存在的数据,返回值:若为正数,代表添加的数据较大;若为负数,代表添加的数据较小;若为0,代表当前要添加的数据已经存在,则舍弃。
(2)比较器排序:创建TreeSet对象时候,传递比较器Comparator指定规则,o1代表当前要添加的数据,o2代表红黑树存在的数据。若两者都存在,则以比较器排序为准。
TreeSet<Integer> set = new TreeSet<>(new Comparator<Integer>() { //Integer只是这里泛型的一种,其它任何引用数据类型都可以
@Override
public int compare(Integer o1, Integer o2) { //Comparator是函数式接口,可用Lambda表达式来表示
return o1-o2;
}
});
Map是双列集合的祖宗接口,数据都是按键值对(Entry
)成对的。
(1) 添加元素 V put(K key, V value); //添加时如果键不存在,返回null;若键存在,则覆盖之前的value并返回
(2) 根据键删除元素 V remove(Object key);
(3) 清空数据 void clear();
(4) 判断集合是否包含指定的键 boolean containsKey(Object key);
(5) 判断集合是否包含指定的值 boolean containsValue(Object value);
(6) 根据键找对应的值 V get(Object key);
(7) 返回全部键组成的单列Set Set<K> keySet();
(8) 返回Entry对象的单列Set Set<Map.Entry<K, V>> entrySet(); //Entry是Map接口的一个内部接口,有getKey和getValue方法
(9) 判断集合是否为空 boolean isEmpty();
(10) 求集合长度,即键值对个数 int size();
Map集合的3种遍历方式:
Set> entries = map.entrySet();
获得一个键值对单列集合,再遍历单列集合得到Entry对象。map.forEach(Lambda表达式);
,该Lambda表达式实现了BiConsumer函数式接口的action方法,BiConsumer与Consumer区别在于有两个泛型变量,该forEach遍历底层是用增强for循环配合键值对遍历实现的。 HashMap是Map接口的一个实现类,特点都是由键决定的:无序、不重复、无索引,HashMap跟HashSet底层原理是一模一样的,都是哈希表结构,put方法会先创建一个Entry对象,再根据Entry对象的键计算出对应的哈希值,再进行存储,若键一样则会覆盖,同样依赖hashCode()和equals()方法来保证键的唯一,因此对于自定义的键对象,需要重写这两个方法以便保证用的是属性值而不是地址值。
HashMap底层原理:数组+链表+红黑树
LinkedHashMap是HashMap的子类,特点都是由键决定的:有序、不重复、无索引,类似于LinkedHashSet都是多了一个双向链表来记录存储的顺序。
TreeMap和TreeSet一样底层是由红黑树实现的,特点由键决定:不重复、无索引、可排序(对键排序)。
TreeMap底层原理:红黑树
注意:
(1)TreeMap添加元素时,不需要键重写hashCode()和equals()方法,只需有键的比较规则即可,HashMap需要。
(2)HashMap有红黑树,但是不需要对键实现Comparable接口或传递比较器对象,而是默认用哈希值的大小创建红黑树。
(3)HashMap效率比TreeMap效率更高一些,但对一些最坏情况,比如HashMap的元素都在一个链表中,TreeMap效率更高。
(4)Map集合中,如果键重复了,put方法会对值覆盖,putIfAbsent则不会覆盖。
(5)3种Map的选取,默认: HashMap(效率最高);如果要保证存取有序: LinkedHashMap;如果要进行排序:TreeMap。
Properties父类Hashtable(与HashMap的区别:Hashtable中大多数方法由synchronized修饰,是线程安全的,所以效率较低;Hashtable的key和value都不能为null,而HashMap的key最多可以1个为null,value可以多个为null;Hashtable初始容量为11,扩容为2n+1,而HashMap初始容量为16,扩容为2n;Hashtable的父类为Dictionary,而HashMap父类为AbstractMap;JDK1.8后HashMap底层比Hashtable底层的数组+链表多了红黑树,即当链表超过8且数组长度大于64就转换为红黑树),Properties类表示一组持久的属性, Properties可以保存到流中或从流中加载,属性列表中的每个键及其对应的值都是一个字符串。直接Properties properties = new Properties();
创建即可,不用泛型,它可以是任何类型,但最好用String类型。
(1) 通过InputStream基本流读取属性列表(键值对) void load(InputStream inStream);
(2) 通过Reader基本流读取属性列表(键值对) void load(Reader reader);
(3) 通过OutputStream基本流写出属性列表(键值对) void store(OutputStream out, String comments); //comments为注释信息
(4) 通过Writer基本流写出属性列表(键值对) void store(Writer writer, String comments);
如果不想让别人修改集合中的内容,就可以定义不可变集合,这种集合只能查询,不能增删改。
(1) 创建一个具有指定元素的List集合对象 List<E> List.of(E...elements);
(2) 创建一个具有指定元素的Set集合对象 Set<E> Set.of(E...elements); //参数一定要保证唯一性
(3) 创建一个具有指定元素的Map集合对象 Map<K,V> Map.of(E...elements); //两个元素组成一个键值对,键不能重复,最多有10个键值对
(4) 创建一个具有指定元素的Map集合对象 Map<K,V> Map.ofEntries(Map.Entry<? extends K,? extends V>... entries);
如Map.ofEntries(map对象.entrySet().toArray(new Map.Entry[0]));来获取一个不可变map对象。
或直接用 不可变map对象 = Map.copyOf(可变map对象); //这个传的无论是可变还是不可变,返回就是不可变
Stream流是JDK8新特性,是用于操作数据源(集合、数组等)所生成的容器。java.util.Stream与java.io是完全不同的东西。
Stream流使用步骤:
(1) 单列集合 default Stream<E> stream(); //Collection中的默认方法
(2) 双列集合 无法直接使用Stream流,可以通过keySet()或entrySet()转换为单列集合再使用Stream流
(3) 数组(Arrays) public static <T> Stream<T> stream(T[] array); //Arrays工具类中的静态方法,Arrays.stream(数组名);
(4) 多个数据(Stream) public static <T> Stream<T> of(T...values); //Stream接口中的静态方法,Stream.of(values1...);
注:如果用Stream.of(数组名);来获取Stream流,数组必须是引用数据类型,若是基本数据类型则会将整个数组作为一个数据放入Stream流,而不是每个元素
(1) 过滤 Stream<T> filter(Predicate<? super T> predicate); //重写该函数式接口的test方法
(2) 获取前几个元素 Stream<T> limit(long maxSize);
(3) 跳过前几个元素 Stream<T> skip(long n);
(4) 元素去重,依赖(hashCode和equals方法) Stream<T> distinct(); //由于底层是通过HashSet来实现的,因此要有hashCode和equals方法
(5) 合并a和b两个流为一个流(静态方法) Stream<T> concat(Stream a, Stream b); //a和b类型一致,不一致则合并的流是它们父类类型
(6) 转换流中的数据类型 Stream<R> map(Function<T,R> mapper); //重写该函数式接口的apply方法(仅一个参数,即流元素)
(7) 流排序 Stream<T> sorted(); //按自然顺序排序,也可传入比较器对象指定比较规则
注意1:中间方法,会返回新的Stream流对象,原来的Stream流只能使用一次,因此一般使用链式编程。
注意2:修改Stream流中的数据,不会影响原来集合或数组中的数据。
(2)终结方法:最后一步,调用完毕之后,不能调用其它方法。
(1) 遍历流 forEach(Consumer<? extends T> action); //重写该函数式接口中的accept方法(仅一个参数)
(2) 统计 count(); //返回值long类型
(3) 最大最小值 max(); //返回值Optional类型
(4) 收集流中的数据,放到数组中 toArray(); //空参返回的是Object[]数组
toArray(IntFunction<A[]> generator);//返回A[]数组,重写该函数式接口的apply(int value)方法,vaule代表流中元素的个数,直接返回new A[value]即可
(5) 收集流中的数据,放到集合中 collect(Collector collector)
1. 放入List集合 Collectors.toList();
2. 放入Set集合 Collectors.toSet();
3. 放入Map集合 Collectors.toMap(键的生成规则,值的生成规则); //各自重写函数式接口的apply方法(仅一个参数,即流元素),键不能重复
所有的异常类是从 java.lang.Exception 类继承的子类,异常分为编译时异常和运行时异常,错误不是异常。
try {
可能出现异常的代码;
} catch (异常类名 变量名) { //可有多个catch捕获多重异常,可以用|同时捕获多个异常
异常的处理代码;
} finally { //可选,无论是否发生异常,finally代码块中的代码总会被执行。
程序代码;
}
throws
:写在方法声明处,告诉调用者该方法可能会出现那些异常,运行时异常可省略不写;throw
:写在方法内部,手动抛出异常给调用者,下边的程序不会再执行,如抛出一个运行时异常throw new RunTimeException(“这是一个运行时异常”);,然后调用者再对异常进行捕获。定义在java.lang.Throwable中的方法。printStackTrace()方法最为常用,信息最全。
(1) 返回异常的详细字符串描述 String getMessage();
(2) 返回异常的类型和描述 String toString();
(3) 把异常的错误信息输出在控制台 void printStackTrace(); //仅仅打印信息,不会停止程序运行
JDK7之前所有被打开的系统资源,比如流、文件或者 Socket 连接等,都需要被开发者手动关闭,否则将会造成资源泄露。JDK7 之后,Java 新增的 try-with-resource 语法糖来打开资源,并且可以在语句执行完毕后确保每个资源都被自动关闭 。try 用于声明和实例化资源,catch 用于处理关闭资源时可能引发的所有异常。try中可以声明多个资源,方法是使用分号 ; 分隔各个资源,最后会自动以相反的顺序关闭这些资源。JDK9之后可以在try外边声明资源,在try括号中写上变量名即可。这些资源必须实现AutoCloseable接口。
try (resource declaration) {
// 使用的资源
} catch (ExceptionType e1) {
// 异常块
}
File类是 java.io包中唯一代表磁盘文件本身的对象,也就是说,如果希望在程序中操作文件和目录,则都可以通过 File 类来完成。File 类定义了一些方法来操作文件,如新建、删除、重命名文件和目录等。File 类不能访问文件内容本身,如果需要访问文件内容本身,则需要使用输入/输出流。
(1) 根据文件路径创建File对象 File(String pathName); //如果pathName是文件夹则代表目录,若是具体文件则代表文件
(2) 根据父路径名和子路径名创建File对象 File(String parent, String child);
(3) 根据父路径对应File对象和子路径名创建File对象 File(File parent, String child);
(1) 判断File是否为文件夹 boolean isDirectory();
(2) 判读File是否为文件 boolean isFile();
(3) 判断File是否存在 boolean exists(); //存在返回true
(4) 求文件的大小(字节数量) long length(); //只能求文件的大小,不能求文件夹的大小(可以遍历文件夹,累加文件大小)
(5) 求文件的绝对路径 String getAbsolutePath();
(6) 求定义时使用的路径 String getPath(); //即构造函数时的路径
(7) 求文件的名称,带后缀 String getName(); //文件夹是没有后缀的
(8) 获取父路径File对象 File getParentFile();
(9) 求文件的最后修改时间(毫秒) long lastModified();
(1) 创建一个新的空的文件 boolean createNewFile(); //如果当前路径的File存在则返回false创建失败;若父级路径不存在,抛IO异常;若路径无后缀,则创建一个无后缀的文件
(2) 创建单级文件夹 boolean mkdir();
(3) 创建多级文件夹 boolean mkdirs();
(4) 删除文件、空文件夹(不走回收站) boolean delete(); //有内容的文件夹无法删除
(1) 获取当前路径下所有内容 File[] listFiles(); //返回File数组
(2) 获取当前路径下所有内容 String[] list(); //返回路径字符串数组
(3) 过滤当前路径下所有内容 String[] list(FilenameFilter filter); //根据过滤器返回路径字符串数组
(4) 过滤当前路径下所有内容 File[] listFiles(FileFilter filter); //根据过滤器返回File数组,只有一个参数pathName
(5) 过滤当前路径下所有内容 File[] listFiles(FilenameFilter filter);//根据过滤器返回File数组,两个参数dir和name(父+子)
(6) 获取全部的盘符 File[] File.listRoots();
最重要的listFiles方法的注意点:
(1)当调用者File表示的路径不存在、是文件、需要权限才能访问的文件夹时,返回null。
(2)当调用者File表示的路径是一个空文件夹时,返回一个长度为0的数组。
(3)当调用者File表示的路径是一个有内容的文件夹时,将里面所有文件和文件夹(包含隐藏文件)的路径放在File数组中返回。
IO流用于读写文件中的数据(可以读写文件,或网络中的数据…)。流在关闭时先开的流最后关闭,随用随创建,什么时候不用了就关闭。
利用InputStream类的方法可以从输入流中读取一个或一批字节。
(1) 从输入流中读取一个8位字节 int read(); //将8位字节转换为0-255整数并返回,若到输入流末尾,则返回-1
(2) 从输入流中读取字节到字节数组 int read(byte[] b); //返回读取的字节数
(3) 从输入流中读取字节到字节数组指定位置 int read(byte[] b, int off, int len);
(4) 关闭输入流 void close(); //InputStream本身的close不执行任何操作,子类会重写
(5) 获取可以从输入流中读取的字节数 int available();
(6) 从输入流中跳过指定数目的字节 long skip(long n); //返回跳过的字节数
(7) 在输入流当前位置设置标记 void mark(int readLimit); //readLimit指定了最多被设置标记的字节数
(8) 判断输入流是否允许设置标记 boolean markSupported(); //允许则返回true
(9) 将输入流的指针返回到上次mark的位置 void reset();
FileInputStream可以从文件系统的某个文件中获取输入字节。在创建FileInputStream类的对象时,如果找不到指定的文件将拋出 FileNotFoundException异常,该异常必须捕获或声明拋出。
(1) 通过File对象创建 FileInputStream(File file);
(2) 通过路径名创建 FileInputStream(String name); //底层也是通过路径创建File对象实现的,若文件不存在则直接报错FileNotFoundException
BufferedInputStream底层自带8192字节大小的缓冲区来提高效率,需要把基本流包装成高级流,提高读取数据性能。基本流会在高级流的底层关闭,所以不需要手动去关闭。 缓冲区可以减少内存读取和写入硬盘的次数,而内存自身运算速度非常快,可先将数据尽量多的存入缓冲区,在内存内部进行处理。
(1) 通过InputStream基本流对象创建 BufferedInputStream(InputStream in); //默认缓冲区8192字节
(2) 通过InputStream基本流对象创建并指定缓冲区的大小 BufferedInputStream(InputStream in, int size);
反序列化流(ObjectInputStream)可以把序列化到本地文件中的对象,读取到程序中,又称为对象操作输入流。但是对于已经序列化的对象来说,如果更改了Javabean类中的成员变量等信息,在反序列化时就会因为和之前的类不同(可以理解为Javabean的版本不同)而出现错误,可以定义一个private static final serialVersionUID = 版本号L
变量来指定版本(IDEA可以设置自动生成版本号)。如果不想把某属性被序列化,则可用transient
来修饰。如果调用readObject读取对象的个数比序列化流写入的对象个数多,则会爆EOFException异常,因此若序列化多个同类对象,可以将它们保存在集合中。
(1) 通过InputStream基本流对象创建 ObjectInputStream(InputStream in);
(2) 读取序列化数据为对象 Object readObject(); //需要强制转换为指定的对象
解压流(ZipInputStream)可以读取zip压缩文件中的文件夹和文件。
(1) 通过InputStream基本流创建 ZipInputStream(InputStream in);
(2) 通过InputStream基本流和指定字符集创建 ZipInputStream(InputStream in, Charset charset);
(3) 读取下一个ZIP文件条目ZipEntry ZipEntry getNextEntry(); //若没有了就返回null,ZipEntry有isDirectory()方法
(4) 关闭当前ZIP文件条目以读取下一个 void closeEntry();
利用OutputStream类的方法可以向输出流中写入一个或一批字节。
(1) 向输出流中写入一个字节 void write(int b); //超过1个字节整数范围会乱码
(2) 向输出流中写入一个字节数组 void write(byte[] b);
(3) 向输出流中写入字节数组的一部分 void write(byte[] b, int off, int len);
(4) 关闭输出流(系统会释放于输出流相关资源) void close(); //OutputStream本身close()不执行任何操作,其子类会重写
(1)可以将数据存入String字符串中,再调用getBytes()方法将字符串转换为字节数组,最后写入输出流。
(2)换行符:Windows(\r\n
);Linux(\n
);Mac(\r
)。
在创建FileOutputStream类的对象时,如果指定的文件不存在,则创建一个新文件;如果文件已存在,则清除原文件的内容重新写入。目标文件所在父级目录必须存在,否则会拋出FileNotFoundException 异常。且目标文件的名称不能是已存在的目录。
(1) 通过File对象创建 FileOutputStream(File file); //会清空文件,重新写入
(2) 通过File对象创建并指定是否追加 FileOutputStream(File file,boolean append); //append为true则会在文件末尾追加
(3) 通过路径名创建 FileOutputStream(String name);
(4) 通过路径名创建并指定是否追加 FileOutputStream(String name,boolean append);
BufferedOutputStream底层自带8192字节大小的缓冲区来提高效率,需要把基本流包装成高级流,提高写出数据性能。
(1) 通过OutputStream基本流对象创建 BufferedOutputStream(OutputStream in); //默认缓冲区8192字节
(2) 通过OutputStream基本流对象创建并指定缓冲区的大小 BufferedOutputStream(OutputStream in, int size);
序列化流(ObjectOutputStream)可以把Java对象写到本地文件中,又称为对象操作输出流。使用序列化流将对象保存到文件时会出现NotSerializableException异常,因此需要让Javabean类实现Serializable接口
,Serializable接口没有抽象方法,即标记型接口,一旦实现这个接口就表示当前的Javabean类可以被序列化。序列化后的文件是不可以被修改的,否则就无法反序列化为原来的对象了。
(1) 通过OutputStream基本流创建对象 ObjectOutputStream(OutputStream out);
(2) 将对象序列化(写出)到文件中 void writeObject(Object obj);
压缩流(ZipOutputStream)用于以zip文件格式写入文件。,
(1) 通过OutputStream基本流创建 ZipOutputStream(OutputStream in);
(2) 通过OutputStream基本流和指定字符集创建 ZipOutputStream(OutputStream in, Charset charset);
(3) 把ZipEntry对象写入压缩包 void putNextEntry(ZipEntry e);
//首先要创建一个ZipEntry对象new ZipEntry(String name),name是压缩包里的路径,再调用putNextEntry放入压缩包,
//此时无数据,因此还需要通过ZipOutputStream的写入方法将数据写入到ZipEntry,即写到压缩包对应的文件中
(4) 关闭当前ZIP文件条目以写入下一个 void closeEntry();
打印流只能写出数据,特有的写出方法可以将数据原样写出,特有的写出方法可以实现自动刷新和自动换行(写出+换行+刷新),字节流底层没有缓冲区,所以开不开自动刷新都一样(构造方法中有autoFlush这一变量来确定)。System.out就是特殊的打印流,系统中的标准输出流,由虚拟机创建,默认指向控制台,且不能关闭(关闭就不能用了)。
(1) 通过OutputStream基本流对象/File对象/路径名创建 PrintStream(OutputStream/File/String);
(2) 通过路径名和字符集创建 PrintStream(String fileName, Charset charset); //还有其它构造方法
(3) 写出任意数据并自动刷新,自动换行 void println(Xxx xxx);
(4) 写出任意数据,不换行 void print(Xxx xxx);
(5) 写出带有占位符的语句 void printf(String format, Object... args);
Reader类是所有字符流输入类的父类。Reader类同样包含close()、mark()、skip()和reset()等方法。字符流底层也是字节流,默认也是一个字节一个字节读取,遇到中文就会读取多个字节(GBK:2个字节;UTF-8:3个字节),读取之后方法底层会根据字符集进行解码并转换成十进制。
(1)创建字符输入流对象:关联文件,并创建缓冲区(长度为8192的字节数组)。
(2)读取数据:判断缓冲区是否有数据可以读取,若无,则从文件中获取数据到缓冲区,尽可能的装满缓冲区,文件也没数据了就返回-1;若有,则直接从缓冲区中读取。
(1) 从输入流中读取一个字符 int read(); //会把读取的字符转换成0-65535的整数,返回-1证明到输入流的末尾了
(2) 从输入流中读取若干字符到字符数组 int read(char[] cbuf); //返回读取的字符数
(3) 从输入流中读取若干字符到字符数组指定位置 int read(char[] cbuf, int off, int len); //返回实际读取的字符数
(1) 通过File对象创建 FileReader(File file);
(2) 通过File对象和指定字符集创建 FileReader(File file, Charset charset); //Charset.forName("GBK")
(3) 通过路径创建 FileReader(String fileName);
(4) 通过路径和指定字符集创建 FileReader(String fileName, Charset charset);
字符缓冲输入流BufferedReader特有方法:String readLine();
可以读取一整行数据,如果没有数据可以读了,则返回null,但是不会把回车换行读到内存中。底层会创建长度为8192的字符数组作为缓冲区,而FileReader自带的是8192大小的字节数组。
(1) 通过Reader基本流对象创建 BufferedReader(Reader in);
(2) 通过Reader基本流对象创建并指定缓冲区大小 BufferedReader(Reader in, int sz);
转换流是字符流和字节流之间的桥梁,JDK11之后可用FileReader(File file, Charset charset);来指定字符集,但若字节流想要使用字符流特有的方法如BufferedReader的readLine()方法,仍可以通过字符转换输入流将字节流转换成字符流,再用BufferedReader高级输入流包装它转换成BufferedReader对象。
(1) 通过InputStream基本输入流创建默认字符集 InputStreamReader(InputStream in);
(2) 通过InputStream基本输入流创建指定字符集 InputStreamReader(InputStream in, String charsetName);
Writer类是所有字符输出流的父类。Writer类也包含close()方法(若缓冲区有数据则会将缓冲区数据写入文件最后断开关联)。当缓冲区8192个字节数组满了会自动写入关联的文件中。
(1) 向输出流中写入一个字符 void write(int c); //会根据整数c查找对应的字符集对应的字符
(2) 把字符数组写入输出流 void write(char[] cbuf);
(3) 把字符数组某部分写入输出流 void write(char[] cbuf, int off, int len);
(4) 向输出流中写入一个字符串 void write(String str);
(5) 向输出流中写入字符串的一部分 void write(String str, int off, int len);
(6) 向输出流中追加一个字符 Writer append(char c);
(7) 向输出流中追加字符序列 Writer append(CharSequence csq);
(8) 向输出流中追加字符序列的一部分 Writer append(CharSequence csq, int start, int end);
(9) 将缓冲区中数据刷新到文件中 void flush(); //关联并没有断
(1) 通过File对象创建 FileWriter(File file);
(2) 通过File对象和指定是否追加 FileWriter(File file, boolean append);
(3) 通过File对象和指定字符集 FileWriter(File file, Charset charset);
(4) 通过File对象、是否追加和字符集 FileWriter(File file, Charset charset, boolean append);
(5) 通过路径创建 FileWriter(String fileName);
(6) 通过路径和指定是否追加 FileWriter(String fileName, boolean append);
(7) 通过路径和指定字符集 FileWriter(String fileName, Charset charset);
(8) 通过路径、是否追加和字符集 FileWriter(String fileName, Charset charset, boolean append);
字符缓冲输出流BufferedWriter特有方法:void newLine();
跨平台的换行。
(1) 通过Writer基本流对象创建 BufferedWriter(Writer in);
(2) 通过Writer基本流对象创建并指定缓冲区大小 BufferedWriter(Writer in, int sz);
(1) 通过OutputStream基本输入流创建默认字符集 OutputStreamWriter(OutputStream in);
(2) 通过OutputStream基本输入流创建指定字符集 OutputStreamWriter(OutputStream in, String charsetName);
字符打印流底层有缓冲区,想要自动刷新需要在构造函数中指定。基本用法和字节打印流一样。
(1) 通过Writer基本流对象/File对象/路径名创建 PrintWriter(Writer/File/String);
(2) 通过路径名和字符集创建 PrintWriter(String fileName, Charset charset);
(3) 通过OutputStream基本流对象创建 PrintWriter(OutputStream out); //也可加上autoFlush,Charset
(4) 通过Writer基本流对象并指定是否自动刷新 PrintWriter(Writer out, boolean autoFlush); //这个和(3)构造最常用
(5) 写出任意数据并自动刷新,自动换行 void println(Xxx xxx);
(6) 写出任意数据,不换行 void print(Xxx xxx);
(7) 写出带有占位符的语句 void printf(String format, Object... args);
乱码出现的原因:
(1)读取数据时未读完整个汉字。
(2)编码和解码的方式不统一。
如何不产生乱码:
(1)不要用字节流读取文本文件。
(2)编码解码使用同一个码表,同一个编码方式。
Commons-io的jar包中封装了很多关于IO流的操作,可以导入jar包或Maven导入依赖来操作。
(1) 复制文件 void copyFile(File srcFile, File destFile);
(2) 复制文件夹 void copyDirectory(File srcDir, File destDir); //原封不动的拷贝
(3) 复制文件夹 void copyDirectoryToDirectory(File srcDir, File destDir); //拷贝到了destDir的里面
(4) 删除文件夹 void deleteDirectory(File directory);
(5) 清空文件夹 void cleanDirectory(File directory);
(6) 读取文件数据为字符串 String readFileToString(File file, Charset encoding);
(7) 写出操作 void write(File file, CharSequence data, String encoding);
(1) 复制文件 int copy(Inputstream input, Outputstream output);
(2) 复制大文件 int copyLarge(Reader input, Writer output);
(3) 读取数据 String readLines(Reader input);
(4) 写出数据 void write(String data, OutputStream output);
进程:一个进程包括由操作系统分配的内存空间,包含一个或多个线程。一个进程一直运行,直到所有的非守护线程都结束运行后才能结束。
线程:一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。一个线程不能独立的存在,它必须是进程的一部分。
多线程可以提高对CPU的利用率,提高效率。
并发:在同一时刻,有多个指令在单个CPU上交替
执行。
并行:在同一时刻,有多个指令在多个CPU上同时
执行。
new Thread(()-{线程执行内容}).start();
,可以直接用Lambda表达式实现Runnable的匿名表达式。Java中的线程是抢占式调度,具有随机性,优先级越大则抢到CPU的概率越大。守护线程:当其它非守护线程执行完毕之后,守护线程会陆续结束。
(1) 获取线程的名字 String getName();//如果没有给线程设置名字,默认名字为Thread-序号
(2) 设置线程的名字(构造方法也可设置名字) void setName();
(3) 获取当前线程的对象(静态方法) Thread Thread.currentThread(); //JVM开启后运行的main线程
(4) 让线程休眠指定的时间,单位毫秒(静态方法) void Thread.sleep(long time);
(5) 设置线程的优先级 void setPriority(int newPriority); //最大优先级为10,最低为1,默认为5
(6) 获取线程的优先级 int getPriority();
(7) 设置为守护线程 void setDaemon(boolean on); //on为true就会设置成守护线程
(8) 出让线程/礼让线程(静态方法) void Thread.yield(); //出让当前CPU执行权,但是也会执行
(9) 插入线程/插队线程 void join(); //等待这个线程死亡,再继续执行下边的线程
注意:JVM中没有运行状态,因为它把线程交给操作系统了,JVM就不管了。
由于线程是抢占式调度,CPU随时可能被其它线程抢走,因此会导致数据不安全(某程序还未执行完就被其它线程抢走,数据可能被其它线程更改,当该线程再次执行时,数据就不是之前的数据了)。同步代码块可以把操作共享数据的代码锁起来, synchronized(锁) {操作共享数据的代码}
,锁对象必须是唯一的,可以用static修饰的成员变量,也可用自身类.class的字节码文件充当锁。
(1)锁是默认打开的,当有一个线程进去时,锁会自动关闭,即使其它线程抢到了CPU资源,也无法进去,只能等待。
(2)里面的代码全部执行完毕,线程出来,锁自动打开。
同步方法就是把synchronized关键字加到方法上,修饰符 synchronized 返回值类型 方法名(方法参数) {...}
,同步方法是锁住方法里面所有的代码,锁对象不能自己指定(Java已经规定好的,对于非静态方法:this;对于静态方法:当前类的字节码文件对象)。
为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock(Lock是接口不能直接实例化)。可通过Lock lock = new ReentrantLock();
来实例化,通过lock()
方法来获得锁(即手动上锁),通过unlock()
方法来释放锁(即手动开锁),可以把unlock放在finally中执行。
死锁:当锁嵌套使用时,可能会导致线程各自拿着自己的锁不放而又再等其它线程释放锁的无法继续执行的状况。
等待唤醒机制又叫生产者消费者模式。线程A和B共享一块缓冲区,生产者A往缓冲区中放数据,而消费者B从缓冲区拿数据;如果缓冲区满了,生产者就无法继续放数据而等待,当消费者拿走数据后就会唤醒生产者继续放数据;如果缓冲区为空,消费者就因无数据可拿而等待,当生产者放入数据后就会唤醒消费者可以拿数据了。缓冲区的作用就是为了平衡生产者和消费者的处理能力,起到一个数据缓存的作用,同时也达到了一个解耦的作用。
(1)解耦:生产者和消费者之间通过一个共享的缓冲区进行通信,彼此之间并不直接依赖,这种解耦合使得系统更加灵活,能够适应不同的负载和不同的需求。
(2)并发性:生产者和消费者可以并发地执行,这可以充分利用系统的资源,提高系统的吞吐量和响应速度。
(3)缓冲作用:通过缓冲区,可以平衡生产者和消费者之间的速度差异,避免生产者和消费者之间出现因速度不一致而产生的死锁和饥饿问题。
(4)可扩展性:生产者消费者模式易于扩展,可以根据实际需求增加生产者和消费者的数量,或者使用多个缓冲区提高系统的并发性能。
(1) 当前线程等待,直到被其他线程唤醒 void wait();
(2) 随机唤醒单个线程 void notify();
(3) 唤醒所有线程 void notifyAll();
阻塞队列
实现等待唤醒机制:实现Iterable、Collection、Queue和BlockingQueue接口的实现类阻塞队列ArrayBlockingQueue(底层是数组,有界,需手动指定长度)和LinkedBlockingQueue(底层是链表,无界,最大值为int的最大范围)。可以通过阻塞队列的put()
方法把数据放入队列,通过take()
方法从队列中拿走数据,注意:put方法和take方法底层都是定义了锁的,因此不用再定义锁了,但是put和take方法外的程序是没有锁的。生产者和消费者必须共用一个阻塞队列才可以实现等待唤醒机制。
线程池是一种利用池化技术思想来实现的线程管理技术,主要是为了复用线程、便利地管理线程和任务、并将线程的创建和任务的执行解耦开来。可以创建线程池来复用已经创建的线程来降低频繁创建和销毁线程所带来的资源消耗。
Executors
:线程池的工具类通过调用方法返回不同类型的线程池对象。(1) 创建一个没有上限的线程池 ExecutorService Executors.newCachedThreadPool();
(2) 创建一个有固定数量的线程池 ExecutorService Executors.newFixedThreadPool(int nThreads);
(1) 提交实现Runnable接口的任务 Future<?> submit(Runnable task); //同样可以用Lambda表示式来实现
(2) 提交实现Callable接口的任务 Future<T> submit(Callable<T> task);
利用ThreadPoolExecutor类的构造方法来创建自定义的线程池。最大线程数=核心线程数+临时线程数,当核心线程数都在使用,且队列中排队等待的任务满了,才会使用临时线程,而当用了临时线程和队满仍然不够时,就会拒绝服务任务。
4核8线程:Intel研发的超线程技术可把处理器内部的一个物理CPU模拟成两个逻辑CPU,4核8线程就是将四个物理核心模拟成八个逻辑核心,同时会有四核支持八线程的操作,因此最大并发数就为8。
线程池大小选择:CPU密集型(计算多,读取文件少):最大并行数+1(加1是候补,防止前边有线程出问题,这个线程就可以代替);I/O密集型(读取文件多,计算少):最大并行数 x 期望CPU利用率 x 总时间(CPU计算时间+等待时间) / 计算时间,可用thread dump工具来进行测试计算时间和等待时间。
ThreadPoolExecutor(int corePoolSize, //核心线程数,不能小于0
int maximumPoolSize, //最大线程数,不能小于0且大于等于核心线程数
long keepAliveTime, //临时线程最多不工作的时间,超过就会销毁,不能小于0
TimeUnit unit, //临时线程最多不工作的时间单位,如TimeUnit.SECONDS
BlockingQueue<Runnable> workQueue, //任务队列,即阻塞队列,不能为null
ThreadFactory threadFactory, //线程工厂,如Executors.defaultThreadFactory(),不能为null
RejectedExecutionHandler handler);//任务拒绝策略,如new ThreadPoolExecutor.AbortPolicy(),静态内部类,不能为null
C/S架构:客户端/服务器(Client/Server),在用户本地需要下载并安装客户端程序,在远程有一个服务器端程序。
B/S架构:浏览器/服务器(Browser/Server),只需要一个浏览器,用户通过不同的网址来访问不同的服务器。
该类代表着IP地址,底层会根据输入的是IPv4还是IPv6创建对应的子类并返回。
(1) 根据IP地址创建对象 InetAddress InetAddress.getByAddress(byte[] addr);
(2) 根据主机名创建对象 InetAddress InetAddress.getByName(String host);
(3) 根据主机名和IP地址创建对象 InetAddress InetAddress.getByAddress(String host, byte[] addr);
(4) 获取本地主机对象 InetAddress InetAddress.getLocalHost();
(5) 获取IP地址字符串 String getHostAdress();
(6) 获取此IP地址的主机名 String getHostName();
(1) 空参构造,随机绑定本机可用的端口 DatagramSocket();
(2) 指定端口 DatagramSocket(int port);
(3) 创建指定IP地址和端口的UDP套接字 DatagramSocket(int port, InetAddress laddr);
(1) 构造数据报包用于发送 DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port);
(2) 构造数据报包用于发送 DatagramPacket(byte[] buf, int offset, int length, SocketAddress address);
(3) 构造数据报包用于发送 DatagramPacket(byte[] buf, int length, InetAddress address, int port);
(4) 构造数据报包用于发送 DatagramPacket(byte[] buf, int length, SocketAddress address);
(5) 构造对象用于接收数据包 DatagramPacket(byte[] buf, int length); //或者可以通过set方法设置内容作为数据报包发送出去
(6) 构造对象用于接收数据包 DatagramPacket(byte[] buf, int offset, int length);
成员方法(都有对应的get方法)
(1) 设置端口号 void setPort(int iport);
(2) 设置数据 void setData(byte[] buf);
(3) 设置数据 void setDate(byte[] buf, int offset, int length);
(4) 设置数据包长度 void setLength(int length);
(5) 设置要发送的IP地址 void setAddress(InetAddress iaddr);
(6) 设置远程主机的套接字 void setSocketAddress(SocketAddress address);
(7) 获取发送或接收的偏移量 int getOffset();
发送时数据包指定的端口和接受时数据报套接字的端口要一致
。 UDP的单播、组播和广播
(1)单播:一个设备只发送信息给另一台设备。
(2)组播:消息发送给一组设备,预留的组播IP:224.0.0.1-224.0.0.255,创建MulticastSocket组播套接字对象,在接收数据时,应该先加入指定IP的那一组才能接收数据joinGroup(InetAddress mcastaddr)。
(3)广播:消息发送给整个局域网,IP:225.225.225.225
UUID:表示不可变通用唯一标识符,UUID是由一组32位数的16进制数字所构成,以连字号分为五段,形式为8-4-4-4-12的32个字符,String str = UUID.randomUUID().toString().replace("-", "");
反射允许对封装类的字段(成员变量),方法(成员方法)和构造函数(构造方法)的信息进行编程访问。JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个属性和方法;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。前提:必须先要获取到该类的Class字节码文件对象。反射的作用:获取一个类里面所有的信息,获取到了之后,再执行其他的业务逻辑;结合配置文件,动态的创建对象并调用方法。
以下三种方法获取Class对象的完全一样的。
Class.forName("全类名");
,最为常用。类名.class;
,一般当作参数,如同步代码块可用它来代替锁。对象.getClass();
,获取对象后才可以使用。万物皆对象, 构造方法对象(Constructor)、字段即成员变量(Field)和成员方法(Method)。
(1) 通过Class对象获取所有public修饰的构造方法对象 Constructor<?>[] getConstructors();
(2) 通过Class对象获取所有的构造方法对象(包括私有) Constructor<?>[] getDeclaredConstructors();
(3) 通过Class对象获取单个public修饰的构造方法对象 Constructor<?> getConstructor(Class<?>...parameterTypes);//如int.class
(4) 通过Class对象获取单个构造方法对象(包括私有) Constructor<?> getDeclaredConstructor(Class<?>...parameterTypes);
(5) 获取构造方法的访问权限 int getModifiers(); //如public返回1,private返回2
(6) 获取Class名 String getName(); //根据构造方法对象获得对应的Class名
(7) 获取构造方法的全部参数 Parameter[] getParameters(); //获取的什么构造方法对象就对应什么参数
(8) 临时取消对访问权限的校验 void setAccessible(boolean flag);//如对于private修饰的构造方法,就可以利用(8)创建对象
(9) 利用构造方法对象来创建对象 T newInstance(Object... initargs); //构造方法对象应有的参数,
(1) 通过Class对象获取所有public修饰的成员变量 Field[] getFields();
(2) 通过Class对象获取所有成员变量(包括私有) Field[] getDeclaredFields();
(3) 通过Class对象获取单个public修斯的成员变量 Field getField(String name);
(4) 通过Class对象获取单个成员变量(包括私有) Field getDeclaredField(String name);
(5) 获取成员变量对象的名字 String getName();
(6) 获取成员变量对象的类型 Class<?> getType();
(7) 获取某对象成员变量的值 Object get(Object obj); //参数为某对象,获取的值就是该对象成员变量的值,可以强转
(8) 修改某对象成员变量的值 void set(Object obj, Object value);
(1) 通过Class对象获取所有public修饰的成员方法 Method[] getMethods(); //父类中public修饰的方法也会获取
(2) 通过Class对象获取所有的成员方法(包括私有) Method[] getDeclaredMethods(); //不会获取父类中的方法,但会获取本类私有的
(3) 通过Class对象获取单个public修饰的成员方法 Method getMethod(String name, Class<?>...parameterTypes); //name为方法的名字
(4) 通过Class对象获取单个成员方法(包括私有) Method getDeclaredMethod(String name, Class<?>...parameterTypes);
(5) 获取成员方法对象抛出的异常 Class[] getExceptionTypes();
(6) 运行某对象的成员方法 Object invoke(Object obj, Object...args); //执行obj的此方法并传递参数(若没有就不写)返回返回值
Java动态代理是一种机制,允许在运行时创建代理对象,这些代理对象能够在不改变原始对象的情况下对其进行增强或修改其行为。在Java中,动态代理通常用于实现面向切面编程(AOP)的概念,它可以用来拦截方法调用并执行一些额外的操作,例如日志记录、性能监测等。
Java动态代理通常使用Java自带的java.lang.reflect.Proxy类来实现,其使用了Java反射机制来动态生成代理对象。
(1)要使用动态代理,需要定义一个接口,原始对象要实现该接口中的方法。
(2)然后使用Proxy类的静态方法newProxyInstance()来创建代理对象。这个方法接受三个参数:
类加载器(原始对象.getClass().getClassLoader()
);
要代理的接口列表(new Class[]{原始对象类实现的接口名.class}
)
一个InvocationHandler对象,它负责拦截方法调用并执行相应的操作,该对象如下所示:
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//proxy为代理的对象,method为代理的方法,args为对应的参数
Object result = method.invoke(target, args); //target为要代理的对象
return result;
}
}
Ctrl+/
Ctrl+Shift+/
Ctrl+Alt+M
Alt+Enter
Alt+Insert
Ctrl+Alt+L
Ctrl+D
Ctrl+X
Shift+F6
Ctrl+Alt+T
Ctrl+Alt+U
Ctrl+Alt+V
Ctrl+P
Alt+左键
Ctrl+N
Ctrl+F12
Ctrl+Shift+方向键
Ctrl+H
ptg
any-rule
@Override
@FunctionalInterface
Apache Commons官方网站
Hutools官网
参考API文档