Featured image of post java

java

本文阅读量

基础

介绍

java之父:詹姆斯·高斯林(James Gosling)

java主要做企业服务器端软件的开发,占据90%以上的市场份额

服务端开发主要有以下几点:

  1. 数据响应
  2. 数据管理
  3. 业务分析
  4. 推荐算法
  5. 服务降级
  6. 集群脑裂
  7. 安全认证
  8. 权限管理
  9. 性能优化
  10. 事务管理

各种名词

JDK

java development kit,java开发者工具包,必须安装JDK才能使用java

Java技术平台

javaSE

java的核心和基础,是其他两个平台的基础,也可以开发桌面应用程序

javaEE

企业服务端开发的一套解决方案,可用于服务端应用开发

javaME

针对移动应用的解决方案,可开发小型或移动设备中的应用

jdk组成

jdk主要由三部分构成:JVM,核心类库,开发工具

JVM:java虚拟机,真正运行java程序的地方,就是因为jvm的存在,才能使得java成为一门跨平台的语言

核心类库:java自己写好的程序,方便程序员调用的

其中JVM核心类库构成了java的运行环境(JRE)

基础语法

基本数据类型

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// 整型
byte        1字节     -128~127
short       2字节     
int(默认)    4字节     21亿
long        8字节     19位数     long lg = 2343453234232L;// long类型的数据必须在后面加上L/l

// 浮点型
float           4
double(默认)     8            float ft = 3.14F; // float类型需要以f/F结尾

// 字符型
char         2

// 布尔型
boolean      1

同类型转换

自动类型转换

1
2
3
4
5
6
// 类型范围小的变量,可以直接赋值给类型范围大的变量
byte a = 12;
int b = a;

char ch = 'b';
int it = ch;

表达式的自动类型转换

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
byte a = 10;
int b = 20;
long c = 30;
// 小范围类型的变量,会自动转换成表达式中较大范围的类型,再参与运算
// 表达式的最终结果类型由表达式中的 最高类型决定
long result = a + b + c;


// 在表达式中,byte、short、char是`直接转换成int`类型参与运算的
byte a1 = 10;
byte a2 = 20;
// byte a3 = a1 + a2; // 报错,因为,在表达式中,byte、short、char是`直接转换成int`类型参与运算的
int a3 = a1 + a2;
  1. 表达式的最终结果类型由表达式中的最高类型决定
  2. 在表达式中,byte、short、char是直接转换成int类型参与运算的

强制类型转换

1
2
3
4
5
6
// 大范围类型的变量 => 小范围类型的变量
int a = 20;
byte b = (byte)a;

int i = 1500;
byte j = (byte)i; // Alt+enter, 强制类型转换可能出现数据溢出(失真)

浮点型强制转换成整型,直接丢掉小数部分,保留整数部分返回

变量

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// 数据类型  变量名称  =  数据;
int age = 18;

// 变量定义的时候可以不赋初始值,但在使用时,变量里必须有值
double price;
price = 15.4;

// 二进制,以`0B`或`0b`开头

// 八进制,以`0`开头

// 十六进制,以`0X`或`0x`开头

// 关键ASCII值
a   =>  97
A   =>  65
0   =>  48

运算符

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// `+`符号与字符串运算的时候是用作`连接符`使用的,原则:`能算则算,不能算则连接在一起`
int a = 5;
System.out.println("abc"+a); // abc5
System.out.println(a+5); // 10
System.out.println("abc"+a+'a'); // abc5a
System.out.println('a'+a+"abc"); // 102abc


// 自增自减
int i = 10;
int rs = ++i; // ++/--在前,先自加后赋值
System.out.println(i); // 11
System.out.println(rs); // 11

int j = 10;
int rs2 = j++; // ++/--在后,先赋值后自加/自减
System.out.println(j); // 11
System.out.println(rs2); // 10


// 逻辑与   &    全为true,结果才为true

// 逻辑或   |    有一个为true,结果就为true

// 逻辑非   !

// 逻辑异或  ^    前后结果一致,返回false,前后结果不同,返回true

// 短路与   &&    左边只要出现false,右边就不执行

// 短路或   ||    左边只要出现true,右边就不执行

流程控制

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// if
if() {
  
} else if() {
  
} else{
  
}

// switch
switch(表达式){
  case 值1:
  case 值2: // 穿透现象
    执行代码1;
    break;
   ...
   case 值n-1:
    执行代码n-1;
    break;  
  default:
    执行代码n;
}

循环结构

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// for循环
for(int i = 0;i<3;i++){
	System.out.println("hello world");
}

// while循环
while(循环条件){
  循环体语句(被重复执行的代码);
  迭代语句;
}

// do while循环
do{
  循环体语句;
  迭代语句;
} while (循环条件);


// 死循环
// for死循环
for(;;){
  System.out.println("hello world");
}
// while死循环
while(true){
  System.out.println("hello world");
}
// do-while死循环
do{
  System.out.println("hello world");
} while(true);

数组

用来存一批同种类型的数据的容器

数组是一种引用数据类型

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// 静态数组
// 完整格式
int[] ages = new int[]{12,16,18};

// 简化格式
int[] ages = {12,16,18};
// 以上代码也可以这样写
int ages[] = {12,16,18};

// 动态数组:数组定义时先不存入具体的元素值,`只确定数组存储的数据类型和数组的长度`
int[] arr = new int[3];
// 动态数组默认值
byteshortcharintlong      默认值0
floatdouble                    默认值0.0
boolean                         默认值false
引用类型数组等                 默认值null

方法

1
2
3
4
修饰符 返回值类型 方法名(形参列表) {
  方法体代码(需要执行的功能代码)
    return 返回值;
}

好处

  1. 提高了代码的复用性,提高了开发效率
  2. 每个方法对应一个功能,这样使程序的逻辑更清晰(可读性、维护性更好)

为什么方法要放到栈内存中执行

栈特点:先进后出

  1. 确保方法调用其他方法后可以回来
  2. 确保方法执行完毕后可以释放栈内存

参数传递

基本类型和引用类型的参数传递都是值传递,不同的是基本数据类型的参数传输的是存储的数据值,而引用数据类型的参数传输存储的地址值

方法重载

方法名相同,参数不同(参数个数不同、参数类型不同、参数类型顺序不同)

return

return在无返回值的方法中也可使用,作用:立即跳出并结束当前方法的执行

可变参数

1
2
3
public static void sum(int...nums){
  // 可变参数在方法内部本质就是一个数组
}
  1. 可变参数在形参列表中只能出现一个
  2. 可变参数必须放到形参列表的最后面

面向对象

  1. 一个代码文件中,可以有多个class类,但只能一个用public修饰,且public修饰的类名必须称为代码文件名
  2. 垃圾对象:某个对象没有一个变量引用它(对象设置为null)

this

1
2
// 用在方法中,代表当前对象
// 在类方法中,访问类属性(用来解决对象的成员变量与方法内部变量的名称一样时,导致访问冲突问题)

构造器

特点

构造器名称与类名一致,且没有返回值

案例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public class Student{
  // 无参构造器
  public Student() {}
  // 有参构造器
  public Student(String name) {}
}

Student s = new Student();// 这句代码执行了2步操作:
// 1、new Student创建对象
// 2、()匹配调用无参构造器

作用

常用于对对象成员变量(属性)进行初始化赋值

注意

  1. 类在设计时,如果不写构造器,java会为类自动生成一个无参构造器
  2. 一旦定义了有参构造器,java就不会帮我们的类自动生成无参构造器了,此时就建议自己手写一个无参构造器出来

子类构造器

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Animal{
    public Animal(){
        System.out.println("父类Animal的无参构造器执行了");
    }
    public Animal(String name){
        System.out.println("父类Animal的有参构造器执行了");
    }
}

public class Wolf extends Animal {
    public Wolf() {
//        super(); // 写不写都不影响,默认都会调用父类的无参构造器,如果父类没有无参构造器就会报错
    }
   public Wolf(String n){
        // super(); // 写不写都不影响,默认都会先调用父类的无参构造器,如果父类没有无参构造器就会报错
         super(n); // 我们可以手动去调用父类的有参构造器,这样即使没有无参构造器,也不会报错了
        System.out.println("子类Wolf的有参构造器执行了"); // 父类Animal的无参构造器执行了    子类Wolf的有参构造器执行了
    }
}


public static void main(String[] args){
  Wolf w1 = new Wolf();
  Wolf w2 = new Wolf("七匹狼");
}

子类的全部构造器,都会先调用父类的构造器,在执行自己,那是因为:子类构造器会在代码最前方调用super()方法

this()调用兄弟构造器

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public class Student {
  public Student() {}
  public Student(String name,int age){
      // this调用兄弟构造器
      this(name,age,"默认学校");
  }
  public Student(String name, int age, String school) {
    ...
  }
}

this()super()都是调用构造器,这俩不能同时出现,且必须在构造器的第一行

不能同时出现因为:super()会出现重复调用的情况

封装

就是对某个类进行设计时,应该把要处理的数据,以及处理这些数据的方法,设计到一个类中

继承

1
public class B extends A{}

A类称为父类(基类或超类),B类称为子类(派生类)

好处

  1. 减少重复性代码书写,提高代码的复用性
  2. 为多态提供支持

多层继承

java是单继承的,java中的类不支持多继承,但是支持多层继承

1
2
3
public class M{}
public class N extends M{}
public class P extends N{}

成员调用:this,super

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public class Fu{
  String name = "父类名称";
}
public class Zi extends Fu{
  String name = "子类名称";
  
  public void showName(){
    String name = "局部名称";
    System.out.println(name); // 局部名称
    System.out.println(this.name); // 子类名称
    System.out.println(super.name); // 父类名称
  }
}

多态

多态在继承/实现情况下的一种现象,表现为:对象多态、行为多态(同样的形态,在不同的对象中表现是不同的)

条件

  1. 有继承/实现关系

    Student,Teacher都继承People

  2. 存在父类引用子类对象

    People p1是父类,引用的是子类Student或Teacher对象

  3. 存在方法重写

    Student的run方法重写了People的run方法

对象多态表现形式

1
2
3
People p1 = new Student(); // 父类(People)引用子类对象(new Student())
Perple p2 = new Teacher(); // 父类(People)引用子类对象(new Teacher())
Student p3 = new Student(); // 不是多态,不是:父类引用子类对象

行为多态

1
2
3
4
5
6
7
People p1 = new Student();
// 成员方法:编译看左(People中是否有run方法),运行看右(运行Student中的run方法)
p1.run();//行为多态  
// 成员属性:编译看左,运行看左
System.out.println(p1.name);
Perple p2 = new Teacher();
p2.run();//行为多态

好处

  1. 在多态形式下,右边对象是解耦合的,更便于扩展和维护

    解耦合:我们将每个功能或模块,开发成一个个的,当我们需要哪个就使用哪个

  2. 定义方法时,使用父类类型的形参,可以接收一切子类对象,扩展型更强、更便利

    1
    2
    3
    
    public void go(Animal c){
      c.cry(); 
    }
    

类型转换

多态条件下,为了多态实例可以调用子类独有的功能(p调用Teacher子类的独有的方法)

1
2
3
4
// 自动类型转换
People p = new Teacher();
// 强制类型转换(People必须与Teacher有继承关系)
Teacher t = (Teacher)p;

类型判断

1
2
3
4
// 为了避免类型转换异常(ClassCastException)出现
if(a instanceof Cat){
  Cat c1 = (Cat)a;
}

实体类

特点

  1. 实体类中的成员变量都要私有,并且要对外提供相应的get与set方法
  2. 实体类中必须要有一个公共的无参构造器

作用

  1. 可以用于存取对象的数据
  2. 实体类只负责数据的存取,对于数据的业务处理应该交给另一个类的对象来处理(分层思想:一种软件设计思想)

static

静态变量

使用static修饰变量,被称为静态变量

没有static修饰的变量,被称为实例变量(对象变量)

1
2
3
4
5
6
# 静态变量
类名.变量名(推荐)
对象名.变量名(不推荐)
  
# 实例变量
对象.变量名

静态变量属于类,在计算机中只有一份,会被类的全部对象共享。存储在对内存中

实例变量,属于每个对象

静态方法

使用static修饰的成员方法,被称为静态方法

没有static修饰的成员方法,被称为实例方法

1
2
3
4
5
6
# 静态方法
类名.类方法(推荐)
对象名.类方法(不推荐)
  
# 实例变量
对象.类方法

静态方法常用于工具类(工具类中都是一些类方法,每个方法都是用来完成一个功能的)中

注意:工具类没有创建对象的需求,建议将工具类的构造器进行私有化

代码块

一个类由5部分组成:成员变量、构造器、方法、代码块、内部类

静态代码块

1
2
3
static{
  // 初始化数据
}

特点:类加载时自动执行,由于类只会加载一次,所以静态代码块也只会执行一次

作用:完成类的初始化,例如:对类变量的初始化赋值

实例代码块

1
2
3
{
  // 初始化赋值
}

特点:每次创建对象时自动执行,在构造器前执行

作用:对实例变量进行初始化赋值

final

final可以修饰方法变量

使用final修饰的类,被称为最终类,最终类不能被继承,常见于工具类

使用final修饰的方法,被称为最终方法,最终方法不能被重写

使用final修饰的变量,只能被赋值一次

1
2
3
public static void buy(final double z){ // 使用final保证该参数无法被修改
  
}

final修饰基本数据类型的变量,变量存储的数据不能被改变

fianl修饰引用类型变量,变量存储的地址不能被改变,但地址所指向对象的内容是可以被改变的

常量

使用static final修饰的成员变量

1
public static final String CIRCLE_PAI = "3.14";

常量性能并不会下降?

因为,程序在编译后,出现常量的地方全部会被替换成设置的字面量,这样可以保证使用常量和直接使用字面量的性能是一样的

abstract

抽象方法

使用abstract修改的方法,我们称为抽象方法

1
2
3
public abstract class A{
  public abstract void go();
}

抽象类

使用abstract修改的类,我们称为抽象类

抽象类注意事项:

  1. 抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类
  2. 类该有的成员(成员变量、方法、构造器)抽象类都可以有
  3. 抽象类不能创建对象,仅作为一种特殊的父类,让子类继承并实现
  4. 一个类继承抽象类,必须重写完抽象类的全部抽象方法,否则这个类也必须定义成抽象类

好处

  1. 简化代码(抽象类中的抽象方法,对应的方法体不用写)
  2. 强制子类重写(更好的支持了多态)

应用场景

模版方法设计模式

Interface

使用interface定义的类,我们称为接口,接口是用来被类实现(implements)的,实现接口的类我们称为实现类

接口最重要的特点:接口不能创建对象

1
2
3
4
5
// 实现类实现多个接口,必须重写全部接口的全部抽象方法,否则这个类必须是抽象类
// 实现类名以`Impl`结束
修饰符 class 实现类 implement 接口1,接口2,接口3,...{
  
}

好处

  1. 弥补了单继承的不足,类可以同时实现多个接口(面向接口编程时软件开发中目前很流行的开发模型,能更灵活的实现解耦合)

    1
    
    public class Student extends People implements Driver,Doctor{}
    
  2. 让程序可以面向接口编程,这样既不用关心实现的细节,也可以灵活方便的切换各种实现

多继承

接口与接口是多继承的,一个接口可以同时继承多个接口

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
interface A{
  void a();
}
interface B{
  void b();
}
interface C{
  void c();
}

// 实例类D
class D implements A,B,C{
	// 实现A,B,C中所有方法
}


// 多继承写法
interface A extends B,C{
  void a();
}
class D implements A{
	// 实现A,B,C中所有方法
}

注意

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// jdk1.8之前,接口内只能定义`常量`和`抽象方法`
public interface A{
  // 常量:接口中定义常量可以省略public static final不写,默认会加上
  String ERROR_CODE = "10000";
  
  // 抽象方法:接口中定义抽象方法可以省略 public abstract不写,默认会加上
  void run();
}


// jdk8开始,接口新增了三种形式的方法
public interface A{
  // 1、默认方法(普通方法、实例方法),必须用default修饰
  // 默认会用public修饰
  // 必须用接口的实现类的对象来调用
  default void run(){
    go();
    System.out.println("run方法在执行");
  }
  
  // 2、私有方法(私有的实例方法)(JDK9开始才有的)
  // 只有当前接口内部的默认方法或私有方法来调用
  private void go(){
    System.out.println("go方法在执行");
  }
  
  // 3、静态方法
  // 默认会用public修饰
  // 接口的静态方法必须用接口名本身调用 A.inAddr();
 	static void inAddr(){
    System.out.println("inAddr方法在执行");
  }
}

// 新增3种方法目的:增强了接口的能力,更便于项目的扩展和维护
//(假设我们设计的接口A,已经设计好了抽象方法,有1000个类实现了接口A。这时我们的项目新增了一个功能,我们需要在接口A中新增一个抽象方法(jdk8之前),那我们需要修改这1000个实现类,这时候代码维护性就很差了。

// 而新增的3种方法后,我们可以将这个新增方法,使用这3种方法写到接口A中,直接调用就可以了,不用对1000个实现类进行修改了)

修饰符

修饰符 作用
private 只能本类使用
缺省 本类、同一个包中的类访问
protected 本类,同一个包中的类、子孙类中访问
public 任意位置访问

Object类

Object类是java所有类的祖宗类,我们写的任何一个类,都是object的子类或子孙类

重写

当子类觉得父类中的某个方法不好用,或者无法满足自己的需求时,子类可以重写一个方法名称、参数列表一样的方法,去覆盖父类的这个方法,这就是方法重写

注意

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// 1、使用Override注解,好处:
// 1.安全:java编译器会检查我们方法 重写的格式是否正确
// 2.代码的可读性更好(看到Override就会直到这是重写方法)
public class Tigger extends Animal{
  @Override
  public void run(){}
}

// 2、子类重写父类方法时,访问权限必须大于或等于父类该方法的权限

// 3、重写的方法返回值类型,必须与被重写方法的返回值类型一致,或则范围更小

// 4、私有方法、静态方法不能被重写,如果重写会报错的
// 1.私有的方法无法被继承,既然没有继承就没有重写之说了。
// 2.静态方法时属于父类的
// 3.当我们去掉`Override`后,方法是覆盖而不是重写

内部类

定义在一个类内部的类,我们称为内部类

成员内部类

就是类中的普通成员,类似普通的成员变量、成员方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class Outer{
  public class Inner{
    
  }
}
// 1、使用内部类,Inner内部类是外部类的对象持有的
Outer.Inner in = new Outer().new Inner();

// 2、成员内部类的实例方法中,可以直接访问外部类的实例成员、静态成员(因为是先有了外部实例,再有内部实例,因此可以调用外部类的实例成员)
public class Outer{
  public static String schoolName = "学校";
  public static void inAddr(){
    System.out.println("我们在济南");
  }
  private String hobby;
  private void run(){
    System.out.println("run");
  }
  public class Inner{
   public void show(){
     // 成员内部类中,可以直接访问外部类的静态成员
     System.out.println(schoolName);
     // 成员内部类中,可以直接访问外部类的静态方法
     inAddr();
     // 成员内部类中,可以直接访问外部类的实例成员
     System.out.println(hobby);
     // 成员内部类中,可以直接访问外部类的实例方法
     run();
   } 
  }
}


// 3、获取当前外部类对象,格式:外部类名.this
public class People{
  private int heartBeat = 110;
  public class Heart{
    private int heartBeat = 95;
    public void show(){
      int heartBeat = 80;
      System.out.println(heartBeat); // 80
      System.out.println(this.heartBeat); // 95
      System.out.println(People.this.heartBeat); // 110 注意这里写法 
    }
  }
}

静态内部类

有static修饰的内部类,属于外部类自己持有,静态内部类也只加载一次

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public class Outer{
  // 静态内部类
  public static class Inner{}
}

// 1、直接使用静态内部类
Outer.Inner in = new Outer.Inner();
// 2、注意
// 2.1 静态成员内部类中,可以直接访问外部类的静态成员、静态方法
// 2.2 静态成员内部类中,不可以直接访问外部类的实例成员、实例方法

局部内部类(鸡肋语法,没有什么用)

定义在方法中、代码块中、构造器里等执行体中的类

1
2
3
4
public static void main(String[] args){
  // 局部内部类(鸡肋语法,没有什么用)
  class A{}
}

匿名内部类(重点)

就是一种没有名字的局部内部类,本质是一个子类,并会立即创建一个子类对象

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 匿名类作为一个参数传递
interface Swimming{
 	void swim();
}
public class Trial{
  public static void main(String[] args){
    // Trail类中的匿名类在编译后形成的文件名是:Trial$1.class(当前类名+$+编号)
    Swimming s1 = new Swimming(){
      @Override
      public void swim(){
        System.out.print("学生在有用");
      }
    };
    go(s1);
  }
 
  public static void go(Swimming s){
    System.out.println("开始....");
    s.swim();
     System.out.println("结束....");
  }
}

Lambda表达式

Lambda表达式是JDK8开始新增的一种语法形式,只能简化函数式接口(有且仅有一个抽象方法的接口)的匿名内部类

作用:用于简化内名内部类的代码写法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 使用`@FunctionalInterface`注解的接口必定是函数式接口
@FunctionalInterface
interface Swimming{
  void swim();
}
Swimming s1 = new Swimming(){
  @override
  public void swim(){
    System.out.println("yyyy");
  }
}
// 使用Lambda表达式简化
Swimming s1 = () -> {
  System.out.println("yyyy");
}

// Lambda省略规则
// 1、参数类型可以省略不写
Arrays.setAll(scores,(int index) -> {
  return scores[index]+10;
});
Arrays.setAll(scores,(index) -> {
  return scores[index]+10;
});
// 2、如果只有一个参数,参数类型可以省略,同时()也可以省略
Arrays.setAll(scores,index -> {
  return scores[index]+10;
});
// 3、如果Lambda表达式中的 方法体代码只有一行代码, 可以省略大括号不写,同时要省略分号!此时,如果这行代码是return语句,也必须去掉return不写
Arrays.setAll(scores,index -> scores[index]+10);

为什么可以使用Lambda简化代码呢?

因为简化后的代码可以通过上下文推断出真实的代码形式。

方法引用

用于进一步简化Lambda表达式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
// 1、静态方法引用:类名::静态方法
// lambda表达式中只是调用一个静态方法,并且前后参数形式一致,可以使用
// Comparable必须是函数式接口
public class Student implements Comparable<Student>{
  public static int compareByHeight(Student s1,Student s2){
    return Double.compare(a1.getHeight(),a2.getHeight());
  }
}
Student[] students = new Student[4];
// Lambda写法
Arrays.sort(student,(a1,a2)->Double.compare(a1.getHeight(),a2.getHeight()));
// 静态方法引用写法
Arrays.sort(students,Student::compareByHeight);


// 2、实例方法引用:实例名::实例方法
// Lambda表达式里只是调用一个实例方法,并且前后参数的形式一致,就可以使用
// Test类中的compare方法
public int compare(Student s1,Student s2){
  retrun Double.compare(s1.getHeight(),s2.getHeight());
}
Test t = new Test();
// Lambda写法
Arrays.sort(students,(a1,a2)->t.compare(a1,a2));
// 实例方法引用写法
Arrays.sort(students,t::compare);


// 3、特定类型的方法引用:类型::方法
// Lambda表达式里只是调用一个实例方法,并且前面参数列表中的第一个参数是作为方法的主调,后面的所有参数都是作为该实例方法的入参的,则此时就可以使用特定类型的方法引用
// 原始写法
Arrays.sort(names,new Comparator<String>(){
  @Override
  public int compare(String s1,String s2){
    return s1.compareToIgnoreCase(s2);
  }
});
// Lambda写法
Arrays.sort(names,(s1,s2) -> s1.compareToIgnoreCase(s2));
// 特定类型的方法引用
Arrays.sort(names, String::compareToIgnoreCase);


// 4、构造器引用:类型::new
// Lambda表达式里只是在创建对象,并且前后参数情况一致,就可以使用构造器引用
@FunctionalInterface
interface Create{
  Car createCar(String name);
}

class Car{
  private String name;
}
// 原始写法
Create c1 = new Create(){
  @Override
  public Car createCar(String name){
    return new Car(name);
  }
};
// 原始写法简化
Create c1 = (String name) -> {
  return new Car(name);
};
// Lambda写法
Create c1 = name -> new Car(name);
// 如果某个Lambda表达式里只是在创建对象,并且前后参数情况一致,就可以使用构造器引用
Create c1 = Car::new;

枚举

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// 枚举类的第一行必须罗列的是枚举对象的名称
public enum A{
  X,Y,Z;// 这些名称,本质是常量,每个常量都会记住枚举类的一个对象,可以通过查看反编译后的代码:public static final A X = new A();
  private String name;
}
// 使用枚举
A a1 = A.X; // 等同于 A.valueOf("X");
// 获取对应的枚举的索引
a1.ordinal();
// 获取枚举类的全部对象,返回结果为数组
A[] a = A.values();

特点

  1. 枚举类第一行只能罗列一些名称,这些名称都是常量,并且每一个常量记住的都是枚举类的一个对象
  2. 枚举类的构造器都是私有的(写不写都只能是私有的),因此,枚举类对外不能创建对象A a = new A()会报错
  3. 枚举类都是最终类,不可以被继承
  4. 枚举类中,从第二行开始,可以定义类的其他各种成员
  5. 编译器为枚举类新增了结果方法,并且枚举类都是继承:java.lang.Enum类的,从Enum类也会继承到一些方法
1
2
3
4
// 枚举写的单例
public Enum B{
  X;
}

枚举类相对于常量缺点:性能差点,因为常量是字面量,而枚举类需要new对象,相对来说性能有点影响。

泛型

作用:在编译阶段,约束所能操作的数据类型,并自动进行检查,这样可以避免强制类型转换,及其可能出现的异常

泛型的本质:**把具体的数据类型作为参数传递给类型变量 **

1
ArrayList<String> list = new ArrayList<String>();// jdk1.7之后后面的ArrayList<String>中的String可以不写

泛型类

将类型作为参数传递给类,我们就把这种类称为泛型类

泛型变量E,建议使用大写字母,常用有:E,T,K,V

1
2
3
public class ArrayList<E>{
  
}

泛型接口

1
2
3
public interface A<E,T>{
  ....
}

泛型方法

携带<E>的泛型类型的方法,我们称为泛型方法

1
2
3
4
5
6
修饰符 <类型变量,类型变量...> 返回值类型 方法名(形参列表){
  
}
public static <T> T printArray(T[] arr){
  
}

好处:让方法更通用(如上方代码可以同时接收Student与String数组),同时避免对类型进行强制转换(传递什么类型返回什么类型)

通配符与泛型上下限

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class Car{}
class TSL extends Car{}
class LX extends Car{}

// 通配符: ?
public static void go(ArrayList<?> tsls){} // 可以传递任意类型
// 泛型上限:能接收的必须是Car或者其子类
public static void go(ArrayList<? extends Car> tsls){}
// 泛型下限:能接收的必须是Car或者其父类
public static void go(ArrayList<? super Car> tsls){}

注意:

  1. 泛型是工作在编译阶段的,一旦程序编译成class文件,class文件中就不存在泛型了,这就是泛型擦除
  2. 泛型不能直接支持基本数据类型,只能支持对象类型(引用数据类型)

包装类

作用:把基本类型的数据包装成对象,这样就可以在泛型中使用了

基本数据类型 对应包装类(引用数据类型)
byte Byte
short Short
int Integer
long Long
char Character
float Float
double Double
boolean Boolean
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
int a = 12;
// 1、手动包装:int成对象
Integer it1 = new Integer(a); // 过时了,jdk9就淘汰了
Integer it2 = Integer.valueOf(a); // 推荐,缓存了-128到127之间的数据成为一个个对象(不会每次都new),如果不在这个范围,就重新new一个返回

// 2、自动装箱机制:基本类型的数据可以直接变成对象赋值给包装类
Integer it3 = 12; // 等同于上面的手动包装

// 3、自动拆箱机制:包装类的对象可以直接赋值给基本数据类型
int it8 = it7;
System.out.println(it8); // 128

新功能

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// 把基本数据类型转换成字符串
// 1、方式1
int a1 = 32;
String rs = Integer.toString(a1);

// 2、方式2
Integer i = 23;
String rs2 = i.toString();

// 3、方式3
int a2 = 23;
String rs3 = a2 + "";

// 把字符串数值转换成对应的基本数据类型
String ageStr = "23";
int age = Integer.parseInt(ageStr); // 等同于Integer.valueOf(ageStr);


String scoreStr = "99.5";
int score = Double.parseDouble(scoreStr);// 等同于Integer.valueOf(scoreStr);

包是分门别类管理程序的,类似于文件夹

1
package com.snailsir.javabean;
  1. 同一个包下的类,可以互相直接调用
  2. 如果调用其他包下的程序,则必须要导包,才可以访问!导包格式:import 包名.类名
  3. 如果调用的包名下的对象名重复,此时默认只能导入一个,另一个必须带包名访问

正则

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
qq.matches("[1-9]\\d{5,}"); // 是否能匹配正则表达式

// Pattern根据正则获取内容
// 1、获取Pattern对象
Pattern pattern = Pattern.compile("正则规则");
// 2、获取匹配器对象
Matcher matcher = pattern.matcher(data);
// 3、获取匹配对象
while(matcher.find()){
  // 获取正则匹配的内容
  String info = matcher.group();
  // 获取分组匹配的内容,使用()包起来的正则表达式
  String info = matcher.group(1);// 我们这时候就可以获取()匹配的内容
}

// 贪婪匹配
String regex1 = "欢迎(.+)光临"; // 贪婪匹配,最大范围匹配,只会匹配出一个
String regex2 = "欢迎(.+?)光临"; // 非贪婪匹配,会匹配出多个

异常

常见异常

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
String name = null;
System.out.println(name.length()); // NullPointerException 空指针异常

System.out.println(10/0);// ArithmeticException 数学操作异常

Object o = "snailsir";
Integer i = (Integer)o; // ClassCastException 类型转换异常

String s = "23a";
int it = Integer.valueOf(s); // NumberFormatException 数字转换异常

自定义异常

异常分为两类:

  • 运行时异常:RuntimeException及其子类,编译阶段不会出现错误提醒,运行时会出现的异常(如:数据索引越界异常)
  • 编译时异常:编译阶段就会出现错误提醒(如:日期解析异常)

自定义运行异常

1
2
3
4
5
6
7
8
//1、继承RuntimeExcepition
public class AgeIllegalRuntimeException extends RuntimeException{
  // 2、重写构造器
  public AgeIllegalRuntimeException(){}
  publci AgeIllegalRuntimeException(String message){
    super(message);
  }
}

自定义编译异常(逐渐被抛弃,代码干扰太多)

1
2
3
4
5
6
7
8
//1、继承Excepition
public class AgeIllegalRuntimeException extends Excepition{
  // 2、重写构造器
  public AgeIllegalRuntimeException(){}
  publci AgeIllegalRuntimeException(String message){
    super(message);
  }
}

常用包String

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 创建String对象
String name = "snailsir";

String s1 = new String(); // 等同于 s1 = "";

char[] chars = {'a','b','c','中','国'};
String s3 = new String(chars);

byte[] bytes = {97,98,99,65,66,67};
String s4 = new String(bytes);

// 常用方法
String str = "snail蜗牛sir";
str.length(); // 获取字符串长度:10
str.charAt(2); // 获取指定索引对应的字符,索引从0开始
char[] chars = str.toCharArray(); // 把字符串转换成数组
//判断字符串内容是否相同
String str1 = "abc";
String str2 = new String("abc");
System.out.println(str1 == str2); // false 比较str1与str2对应的内存地址是否相同
System.out.println(str1.equals(str2)); // true 比较的是str1与str2的内容是否相同
t1.equalsIgnoreCase(t2); //忽略大小写进行字符串内容比较
str.substring(5, 7); // 截取字符串:蜗牛
str.substring(7); // 从索引7开始截取到末尾
str.replace("垃圾","**").replace("TM","xx"); // 字符串替换,返回替换后的字符串
str.contains("sir"); // 判断是否包含sir字符串
str.startsWith("snail"); // 判断字符串是否以snail开头
str.endsWith("snail"); // 判断字符串是否以snail结尾
String[] res = str.split("s");// 分割字符串成数组
String[] names = s1.split("\\w+");
String result s1.replaceAll("\\w+","-"); // 正则替换

string对象的内容不可改变,被称为不可变字符串对象

以""方式写出的字符串对象,会在堆内存中的字符串常量池中存储

我们每次试图改变字符串对象,实际上是产生了新的字符串对象,变量每次都是指向了新的字符串对象,之前字符串对象的内容确实是没有改变,因此说string的对象是不可变的

通过new 方式创建字符串对象,每new一次都会产生一个新的对象放在堆内存中,而以"“创建的字符串,会存储在字符串常量池中,且相同内容字符串只存储一次

1
2
3
String str1 = "abc";
String str2 = "abc";
System.out.println(str1 == str2); // true

这样做主要是为了节省内存

集合

Collection

单列集合,每个元素只有一个值

Collection是单列集合的祖宗,它规定的方法(功能)是全部单列集合都会继承的

常用方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
Collection<String> list = new ArrayList<String>();
// 1、添加数据
list.add("java");
// 2、清空集合
list.clear();
// 3、判断集合是否为空
list.isEmpty(); // true
// 4、删除集合中的某个数据:默认只能删除第一个
list.remove("java"); // true
// 5、判断集合中是否包含某个数据
list.contains("java"); // true
// 6、获取集合的大小(元素个数)
list.size(); // 3
// 7、把集合转化成数组
Object[] array = list.toArray();
// 8、合并两个集合的数据
c1.addAll(c2); //把c2集合中的数据添加到c1中去

collection遍历

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 1、迭代器:遍历集合的专用方式(数组没有迭代器)
ArrayList<String> list = new ArrayList<String>(); 
// 1.1 获取集合的迭代器对象
Iterator<String> it = list.iterator(); // 迭代器游标指针处于第一个位置
it.next(); //  1.2 取数据,然后游标指针下移一位
...
it.next(); // 抛异常:NoSuchElementException

// hasNext()判断当前位置是否有元素存在
while(it.hasNext()){
  System.out.println(it.next());// 取数据,然后在将游标指针下移一位
}

// 2、增强for(既可以遍历集合也可以遍历数组),本质就是迭代器遍历集合的简化写法
// 2.1 增强for只能读取数据,不能修改数据(快捷键:list.for+回车键)
for(String s:list){
  System.out.println(s);
}

// 3、Lambda表达式
// 3.1 原始写法
list.forEach(new Consumer<String>(){
  @Override
  public void accept(String s){
    System.out.println(s);
  }
});
// 3.2 Lambda简化写法
list.forEach(s->System.out.println(s));
// 3.3 方法引用简化写法
list.forEach(System.out::println);

并发修改异常

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// 1、使用迭代器处理
Iterator<String> it = list.iterator();
while(it.hasNext()) {
  String name = it.next();
  if(name.contains("枸杞")){
    // list.remove(name);// 使用集合删除数据方法,会出现异常:ConcurrentModifiactionException   源码中:modCount 与 exceptedModCount不一致,每删除一个数据,modCount就会自加
    it.remove();// 必须调用迭代器自己的删除方法,才不会出现bug
  }
}

// 2、使用增强for遍历集合并删除含有枸杞的元素,必然出错,而且无法解决
// 因为增强for的本质就是迭代器,而增强for无法拿到迭代器,因此无法解决

// 3、Lambda遍历集合并删除含有枸杞的元素,必然出错,而且无法解决

//4、如果是ArrayList带索引的集合,我们可以使用for循环删除后,再退一步(i--),或者从后面倒着百年路并删除

List

特点:有序、可重复、有索引,list的常用实现类ArrayListLinkedList也具备这些特点

1
2
3
4
5
// list特有功能
list.add(1,"Go");// 1、给指定索引位置插入一个数据
list.remove(2); // 2、删除指定索引位置数据,返回被删除数据
list.set(2,"C++"); // 3、修改指定索引位置处的数据
list.get(2); // 4、获取指定索引位置处的数据

遍历方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// 1、for循环
for(int i=0;i<list.size();i++){
  System.out.println(list.get(i));
}
// 2、迭代器
Interator<String> it = list.iterator();
while(it.hasNext()){
  System.out.println(it.next());
}
// 3、增强for
for(String s:list){
	System.out.println(s);
}
// 4、Lambda表达式
list.forEach(s -> System.out.println(s));
list.forEach(System.out::println);

ArrayList实现类

ArrayList是基于数组实现的

特点:

  1. 查询速度快(是根据索引查询数据快):查询数据通过地址值和索引定位,查询任意数据耗时相同

    只需要获取起始索引地址,然后+要查询的索引值*单位长度,就获取到了要查询索引的地址,就可以获得相应的数据了

  2. 删除效率低:可能需要把后面很多的数据进行前移

  3. 添加效率极低:可能需要把后面很多的数据后移,再添加数据;同时也可能需要进行数组的扩容

底层原理:

  1. 利用无参构造器创建的集合,会在底层创建一个默认长度为0的数组(elementData)

  2. 添加第一个元素时,底层会创建一个新的长度为10的数组(size会记录当前集合的大小,同时也是下一个要添加数据的索引)

  3. 在我们要存第11个数据时,会扩容1.5倍(在底层重新创建一个1.5倍的新数组),把之前的数据复制进新数组,并添加第11个元素

    如果数组已经满了,我们批量添加数据(addAll())的长度+10>10*1.5,这个时候新创建的数组长度=批量添加数据(addAll())的长度+10

场景:

  • 适合根据索引查询数据的场景,如:根据随机索引取数据(高效)!或者数据量不是很大时
  • 不适合数据量大的同时又要频繁的进行增删操作

LinkedList实现类

基于双链表实现的

单链表:

链表中的结点(Node)是独立的对象,在内存中是不连续的,每个结点包含数据值和下一个结点的地址(手牵手)

我们把第一个结点称为头结点

结点有两部分组成下一个结点地址

添加数据步骤:

  1. 当我们要添加第一个数据到头结点时,会先将数据存储到头结点的结构中,然后会将第二个结点的地址存储到头结点的下一个结点地址结构中
  2. 当我们添加第二个数据时,数据会存储到第二个节点的结构中,然后会将第三个结点的地址存储到第二个结点的下一个结点地址结构中

特点:

  1. 查询慢,无论查询哪个数据都要从头开始找
  2. 链表增删相对快

双链表:

结点(Node)有三部分组成前一个结点地址,下一个结点地址

头结点由^(头指针)下一个结点地址组成

尾结点由下一个结点地址^(尾指针)组成

典型的牺牲内存获取效率

特点

查询慢,增删相对较快,但对首尾元素进行增删改查的速度是极快的

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// 特有方法:因为对首尾元素进行增删改查的速度是极快的,所以新增了很多首尾操作的特有方法

// 1、做队列
LinkedList<String> queue = new LinkedList<>();
queue.addLast("第1个人");// 入队
queue.addLast("第2个人");// 入队
queue.removeFirst(); // 出队: 第1个人
queue.removeFirst(); // 出队: 第2个人

// 2、做栈
stack.push("第1发子弹"); // 入栈:push是对addFirst方法的包装
stack.addFirst("第2发子弹");
stack.pop(); // 出栈:pop是对removeFirst方法的包装: 第2发子弹
stack.removeFirst()// 出栈:第1发子弹

set

特点:无序(添加数据的顺序和获取出的数据顺序不一致)、不重复无索引

基于哈希表实现的

set要用到的常用方法,基本上就是Collection提供的,自己几乎没有额外新增一些常用功能

set的实例类常用的有HashSetTreeSet

HashSet

特点:无序、不重复、无索引

哈希值:就是一个int类型的随机值,java中每个对象都有一个哈希值(java中的所有对象,都可以调用Object类提供的hashCode方法,返回该对象自己的哈希值)

java对象哈希值特点:

  • 同一个对象多次调用hashCode()方法返回的哈希值是相同的
  • 不同的对象,他们的哈希值一般不相同,但也有可能会相同(哈希碰撞)

哈希表:是一种增删改查数据,性能都较好的数据结构,在jdk8之前,哈希表=数组+链表,jdk8开始,哈希表=数组+链表+红黑树

JDK8之前

  1. 第一次添加数据时,创建一个默认长度为16的数组,默认加载因子是0.75,数组名table

  2. 使用原始的哈希值数组的长度做与运算(和求余类似),计算出应存入的位置

  3. 存入前,判断要存入位置是否为null,如果是null直接存入

  4. 如果不是null,表示已经存有元素,则调用equals方法与对应位置的链表内数据一一进行比较,如果有相等的,不存数据,没有相等,存入数组

    jdk8之前,新元素存入数组,占老元素位置,老元素挂新元素在下面,形成链表

    jdk8开始,新元素直接挂在老元素下面

加载因子作用:判断什么时候进行扩容。16*0.75 = 12,当存满12个元素后,就要扩容。

扩容成原来的2被,并开始迁移数据

JDK8开始

当链表长度超过8,且数组长度>=64时,自动将链表转成红黑树

红黑树:左边放小的数据,右边放大的数据,基于二分查找来查询数据

HashSet去重:为了保证能将数据一致的内容去重,需要重写hashCode()与equals()方法

LinkedHashSet

HashSet的一个子类

特点:有序、不重复、无索引

底层原理:基于哈希表(数组、链表、红黑树)实现的,那它是怎么实现有序的?

LinkedHashSet每个元素都额外的多了一个双链表的机制记录它前后元素的位置(牺牲内存获取效率)基于LinkedHashMap实现

TreeSet

特点:可排序(默认升序)、不重复、无索引

底层基于红黑树实现的排序

排序注意事项:

  • 对于数值类型:Integer,Double,默认按照数值本身的大小进行升序排序
  • 对于字符串类型:默认按照首字符的编号升序排序
  • 对于自定义类型,如Student对象,TreeSet默认是无法直接排序的

自定义排序:

  1. 让自定义的类实现Comparable接口,重写里面的compareTo方法来指定比较规则
  2. 通过调用TreeSet集合有参数构造器,可以设置Comparator对象(比较器对象,用于指定比较规则)

Map

双列集合(键值对集合),每个元素包含两个值(键值对)

set集合是基于map实现的

特点:map集合的所有键是不允许重复的,

常用方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
Map<String, Integer> map = new HashMap<>();
map.put("华为手表",30);// 1、添加数据
map.put(null,null);
map.put(null,2); // 后面重复的键会覆盖前面的整个数据

map.size(); // 2、获取集合的大小(元素个数)
map.clear(); // 3、清空集合
map.isEmpty(); // 4、判断集合是否为空 true
map.get("华为手表")// 5、根据键获取对应的值
map.remove("雨伞"); // 6、根据键删除整个数据,返回删除数据对应的值。
map.containsKey("雨伞");// 7、判断是否包含某个键
map.containsValue(1);// 8、判断是否包含某个值
Set<String> keys = map.keySet();// 9、获取map集合的全部键,存储到一个Set集合中
Collection<Integer> values = map.values();// 10、获取map集合的全部值,存储到一个Collection集合中

遍历方式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Map<String, Integer> map = new HashMap<>();
// 1、根据键查找对应的值
// 1.1 获取map集合的全部键
Set<String> keys = map.keySet();
// 1.2 循环根据键提取值
for(String key:keys){
  Integer value = map.get(key);
  System.out.println(key + "==>" + value);
}

// 2、根据键值对进行遍历
// 将map集合转换成set集合,在转换的过程中,将map的键和值封装成一个Entry对象,放到set集合中
set<Map.Entry<String,Integer>> entries = map.entrySet();//将map集合转换成set集合[(华为手表=30),(java入门=1)...]
for(Map.Entry<String,Integer> entry:entries){
  System.out.println(entry.getKey() + "==>" + entry.getValue);
}

// 3、Lambda表达式
map.forEach(new BiConsumer<String,Integer>(){
  @Override
  public void accept(String key,Integer value){
     System.out.println(key + "==>" + value);
  }
});
// 简化
map.forEach((key,value) -> {
  System.out.println(key + "==>" + value);
});

HashMap

Map集合的一个实现类

特点:无序、不重复、无索引(这些特点是由键决定的)

底层原理:

HashMap与HashSet的底层原理一样,都是基于哈希表实现的

Set系列集合的底层就是基于Map实现的,只是Set集合中的元素只要键数据,不要值数据而已

LinkedHashMap

是HashMap的子类

特点:有序、不重复、无索引(这些特点是由键决定的)

底层原理

底层数据结构依然是基于哈希表实现的,只是每个键值对元素中又额外的多了一个双链表的机制记录元素顺序(保证有序)

LinkedHashSet集合的底层原理就是LinkedHashMap

TreeMap

Map集合的另一个实现类

特点:按照大小默认升序排序、不重复、无索引(这些特点是由键决定的)

底层原理

TreeMap与TreeSet集合的底层原理是一样的,都是基于红黑树实现的排序

自定义排序规则

  1. 让类实现Comparable接口,重写比较规则
  2. TreeMap集合有一个有参构造器,支持创建Comparator比较器对象,以便用来指定比较规则

Object类

是所有类的祖宗类,因此,java中所有类的对象都可以直接使用Object类中提供的一些方法

Objects

Objects是一个工具类,提供了很多操作对象的静态方法给我们使用

1
2
3
4
5
6
// 1、判断对象是否为null,为null返回true
Objects.isNull(s1);
// 2、判断对象是否不为null
Objects.nonNull(s1);
// 3、判断两个对象是否相等
Objects.equals(s1,s2); // 先做非空判断,再比较两个对象,因此更安全可靠,在比较两个对象时,建议使用

StringBuilder

是一个装载可变字符串的容器

StrinigBuilder比String更适合做字符串的修改操作,效率会更高,代码也会更简洁

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// 1、创建StringBUilder对象
// StringBuilder str = new StringBuilder(); // str = "";
StringBuilder str = new StringBuilder("snailsir"); // str2 = "snailsir";

// 2、拼接字符串
str.append(" like ").append(" money "); // 链式编程

// 3、反转内容
str.reverse();

// 4、获取长度
System.out.println(str.length());

// 5、把StringBuilder对象转换成String对象,因为开发中,我们需要的大部分都是String类型
String result = str.toString();

StringBufferStringBuilder用法一模一样,但是StringBuffer是线程安全的,StringBuilder是线程不安全的,

StringJoiner

Jdk8开始才有的,与StringBuilder一样,也是用来操作字符串的,也可以看做是一个容器,创建后里面的内容是可变的

1
2
3
4
StringJoiner str = new StringJoiner(",","[","]"); // arg1: 间隔符 arg2:开始符号 arg3: 结束符号
for(int i = 0;i<arr.length;i++){
  str.add(Inter.toString(arr[i]));
}

Math

是一个工具类,里面提供的都是对数据进行操作的一些静态方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 获取参数绝对值
System.out.println(Math.abs(-12)); // 12


// 向上取整
System.out.println(Math.ceil(4.0001)); // 5.0

// 向下取整
System.out.println(Math.floor(4.999)); // 4.0

// 四舍五入
System.out.println(Math.round(3.4999)); // 3


// 获取两个int值中的较大值
System.out.println(Math.max(10,20)); // 20

// 返回a的b次幂的值
System.out.println(Math.pow(2,3)); // 2的3次方 8.0

// 返回值为double的随机值,范围为[0.0,1.0)
System.out.println(Math.random());

Runtime

代表程序所在的运营环境(jvm)

Runtime是一个单例类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
Runtime jre = Runtime.getRuntime();

// 终止当前运行的虚拟机,该参数用作状态代码,按照惯例,非零状态代码表示异常终止
// jre.exit(0);// 正常退出虚拟机

// 获取虚拟机能够使用的处理器数
jre.availableProcessors();

// 返回java虚拟机中的内存总量,字节数
System.out.println(jre.totalMemory()/1024.0/1024.0+"MB");

// 返回java虚拟机中的可用内存
System.out.println(jre.freeMemory()/1024.0/1024.0+"MB");

// 启动软件
Process pro = jre.exec("QQ");
// 关闭软件
pro.destroy();

System

System代表程序所在的系统,也是一个工具类

1
2
3
4
5
6
7
8
// 退出java虚拟机
// System.exit(0);

// 统计运行时间
long time1 = System.currentTimeMillis();
// 要统计性能的程序
long time2 = System.cu();
System.out.println("运行消耗时间:"+(time2-time1)/1000.0+"s");

BigDecimal

用于解决浮点型运算时,出现结果失真的问题

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
double a = 0.1;
double b = 0.3;
// 1、把数据包装成BigDecimal对象
BigDecimal a1 = new BigDecimal(a); // 不使用这个,无法解决double类型运算失真的问题。这个只能用来处理大的数据,能保证即使很大的double数据(超过double范围)也能运算,但是无法解决失真问题。
BigDecimal a1 = new BigDecimal(Double.toString(a)); // 可以解决失真问题:原理是将小数部分拆分成整数,然后在运算,最后在将运算结果拼接起来
BigDecimal b1 = BigDecimal.valueOf(a);// 阿里巴巴公司更推荐我们使用valueOf方法保证浮点型数据为BigDecimal对象,与上方的本质是一样的,valueOf就是对上面的方法进行的封装

// 2、加法
BigDecimal res = a1.add(b1); // 对象信息在堆里面
double result = res.doubleValue(); // 基本数据在栈里面,我们在这里将数据从堆里放到栈里,这样就减少了从栈跨堆去找数据的性能浪费

a1.substract(b1); // 3、减法
a1.multiply(b1); // 4、乘法
a1.divide(b1); // 5、除法,报错:Non-terminating decimal expansion,因为0.1/0.3的结果是0.33333无限循环小数,并不是一个精确的结果,因此就会报错
a1.divide(b1,2,RoundingMode.HALF_UP); // 6、除法:divide(除数,保留几位小数,舍入模式)

日期

Java.time包下的类

LocalDate

获取本地日期对象:年月日(不可变对象)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
LocalDate ld = LocalDate.now();// 获取当前日期
ld.getYear();// 获取当前日期的年份
ld.getMonthValue();// 获取当前日期的月份
ld.getDayOfMonth();// 获取当前日期是该月份的第几天
ld.getDayOfYear();// 获取当前日期是今年的第几天
ld.getDayOfWeek().getValue();// 获取是周几

// withYear,withMonth,withDayOfMonth,withDayOfYear
LocalDate ld2 = ld.withYear(2018).withMonth(12);// 修改年份与月份

// plusDays,plusWeeks
LocalDate ld3 = ld.plusYears(2).plusMonths(22);// 增加2年与2月

// minusDays,minusWeeks
LocalDate ld4 = ld.minusYears(2).minusMonths(22);// 减去2年与2月

// 获取指定日期的LocalDate对象
LocalDate ld5 = LocalDate.of(2099,12,12);

ld4.equals(ld5); // 判断2个日期对象是否相等
ld4.isAfter(ld5); // 判断ld4是否在ld5后面
ld4.isBefore(ld5); // 判断ld4是否在ld5前面

LocalTime

获取本地时间对象:时分秒纳秒(不可变对象)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
LocalTime lt = LocalTime.now(); // 获取当前时间
lt.getHour(); // 获取当前时间的小时
lt.getMinute(); // 获取当前时间的分钟
lt.getSecond(); // 获取当前时间的秒
lt.getNano(); // 获取当前时间的纳秒

// withSecond,withNano
LocalDate lt2 = ld.withHour(21).withMinute(12);// 修改小时与分钟

// plusSeconds,plusNanos
LocalDate lt3 = ld.plusHours(2).plusMinutes(2);// 增加2小时与2分钟

// minusSeconds,minusNanos
LocalDate lt4 = lt.minusHours(2).minusMinutes(2);// 减去2小时与2分钟

// 获取指定日期的LocalTime对象
LocalDate lt5 = LocalTime.of(12,12,12);

lt4.equals(lt5); // 判断2个时间对象是否相等
lt4.isAfter(lt5); // 判断lt4是否在lt5后面
lt4.isBefore(lt5); // 判断lt4是否在lt5前面

LoalDateTime

本地日期:年月日时分秒

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
LocalDateTime ldt = LocalDateTime.now(); // 获取当前日期
ldt.getYear(); // 获取当前年份
ldt.getMonthValue(); // 月
ldt.getDayOfMonth(); // 日
ldt.getDayOfYear(); // 一年第几天
ldt.getDayOfWeek().getValue(); // 周几
ldt.getHour(); // 时
ldt.getMinute(); // 分
ldt.getSecond(); // 秒
ldt.getNano(); // 纳秒

ldt.withYear(2018).withMinute(12);//修改年分钟
ldt.plusYears(2).plusHours(2);// 增加2年2小时
ldt.minusYears(2).minusHours(2); //减去2年2小时
// 获取指定日期的LocalDateTime对象
LocalDateTime ld8 = LocalDateTime.of(2099,12,12,12,12,12);

ldt8.equals(ldt9);
ldt8.isAfter(ldt);
ldt8.isBefore(ldt);

//LocalDateTime 拆分成LocalDate和LocalTime
LocalDate ld = ldt.toLocalDate();
LocalTime lt = ldt.toLocalTime();

//LocalDate和LocalTime合并成LocalDateTime
LocalDateTime ldt10 = LocalDateTime.of(ld,lt);

ZoneId时区

1
2
3
4
5
6
// 1、获取系统默认的时区
ZoneId zoneId = ZoneId.systemDefault();
String name = zoneId.getId(); // 默认时区的名字 等价于 zoneId.toString();

// 2、获取java支持的全部时区Id
Set<String> zoneIds = ZoneId.getAvailableZoneIds();

ZonedDateTime

ZonedDateTime与LocalDateTime功能一模一样

1
2
3
4
5
6
// 1、根据时区获取ZonedDateTime对象
// 1.1 设置时区 
ZoneId americaZid = ZoneId.of("America/New_York");
// 1.2 获取ZonedDateTime对象 ZonedDateTime.now(Clock.systemUTC());
ZonedDateTime americaNow = ZonedDateTime.now(americaZid);// ZonedDateTime.now() 获取系统默认时区的ZonedDateTime对象
// 1.3 可以像LocalDateTime一样操作时间了

Instant

做代码性能分析,或者记录用户的操作时间点,推荐Instant代替Date

1
2
3
Instant now = Instant.now();
now.getEpochSecond();//获取总秒数
now.getNano(); // 不够1s的纳秒数

DateTimeFormatter(时间格式化器)

用于时间的格式化和解析

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
//1、创建日期时间格式化器对象
DateTimeFormatter dtf = DateTimeFormatter.ofPatter("yyyy-MM-dd HH:mm:ss EEE a"); // EEE:周几 a:上午/下午
//2、对时间进行格式化
// 2.1 创建一个LocalDateTime对象
LocalDateTime ldt = LocalDateTime.now();
// 2.2 格式化日期
String res = dtf.format(ldt); // 或者使用获取格式化后的日期:ldt.format(dtf);


// 2、将指定日期格式化成LocalDateTime对象
// 2.1 定义指定日期
String dateStr = "2023-11-11 11:11:11";
// 2.2 根据指定日期格式,获取DateTimeFormatter对象
DateTimeFormatter dtf2 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); // 日期格式化器与这个时间格式一模一样
// 2.3 获取LocalDateTime对象
LocalDateTime ldt2 = LocalDateTime.parse(dateStr,dtf2);

Period

用于计算两个LocalDate对象相差的年数、月数、天数

1
2
3
4
5
6
7
LocalDate start = LocalDate.of(2021,3,15);
LocalDate end = LocalDate.of(2021,10,15);
// 创建Period对象,封装两个日期对象
Period period = Period.between(start,end);
period.getYears(); // 相差年份:0
period.getMonths(); // 相差月份:6
period.getDays(); // 相差天数:0

Duration

用于计算两个时间对象相差的天数、小时数、分数、秒数、纳秒数;支持LocalTime、LocalDateTime、Instant等时间

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
LocalDateTime start = LocalDateTime.of(2021,11,11,11,11,11);
LocalDateTime end = LocalDateTime.of(2021,11,11,11,11,11);

// 创建Duration对象
Duration duration = Duration.between(start,end);
duration.toDays(); // 相差天数
duration.toHours(); // 相差多少小时
duration.toMinutes(); // 相差多少分
duration.toSeconds(); // 相差多少秒
duration.toMinllis(); // 相差多少毫秒
duration.toNanos(); // 相差多少纳秒
duration.toHoursPart(); // 将小时进行取余操作,倒计时常用

Arrays

用来操作数组的一个工具类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
int[] arr = {12,45,23,65,89};
// 1、数组转换成字符串
Arrays.toString(arr);
// 2、拷贝数组的内容到一个新的数组,并返回新数组
int[] arr2 = Arrays.copyOfRange(arr,1,3); // (源数组,int from,int to)  :  [45,23,65]
// 3、给数组扩容
int[] arr3 = Arrays.copyOf(arr,arr.length*2); // 对数组arr扩容1倍(要扩容的数组,int 扩容到多大)
// 4、修改数组中的每个数据,再存入
double[] scores = {99.5,23,78.5,93,29};
Arrays.setAll(scores,index -> scores[index] + 10);
// 5、对数组进行排序的操作
Arrays.sort(scores);// 升序排序

对象排序

  1. 让对象所在的类实现规则接口Comparable,重写compareTo方法,来指定比较规则

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    
    public class Student implements Comparable<Student>{
      // 指定大小规则
      @Override
      public int compareTo(Student o){
        // 比较规则:左>右 返回正整数,
        // 左<右 返回负整数
        // 左=右 0
        //if(this.age > o.age){
        //  return 1;
        //} else if (this.age < o.age){
        //  return -1;
        //}
        // return 0;
        // 代码优化
        return this.age - o.age; // 升序
     		// return o.age - this.age; // 降序
      }
    }
    
    // 完成上面步骤后,在进行排序操作就不会报错了e
    Array.sort(students);
    
  2. sort存在重载的方法,支持自带Comparetor比较器对象来直接指定比较规则(如果两个方式都存在,优先执行方式2的–就近原则)

    1
    2
    3
    4
    5
    6
    7
    8
    
    // public static <T> void sort(T[] a,Comparator<? super T> c)
    Arrays.sort(students,new Comparator<Student>{
      @Override
      public int compare(Student o1,Student o2){
        return o1.getAge() - o2.getAge(); // 升序
        // return Double.compare(o1.getHeight(),o2.getHeight()); // 升序
      }
    })
    

Collections

是一个用来操作集合的工具类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// 1、为集合批量添加数据 public static <T> boolean addAll(Collection<? super T> c,T...elements)
List<String> names = new ArrayList<>();
Collections.addAll(names,"徐凤年","红薯","青鸟","姜泥");

// 2、打乱List集合中的元素顺序 public static void shuffle(List<?> list)
Collections.shuffle(names);

// 3、对list集合中的元素进行升序排序 public static <T> void sort(List<T> list) 
List<Student> students = new ArrayList<>();
// s1
// s2
Collections.addAll(students,s1,s2,s3,s4);
// 排序方式1:让对象的类实现Comparable接口,重写compare方法,指定大小比较规则
Collections.sort(students);

//  4、public static <T> void sort(List<T> lsit,Comparator<? super T> c)
// 对list集合中元素,按照比较器对象指定的规则进行排序
// 方式2:指定Comparator比较器对象,再指定比较规则
Collections.sort(students,((o1,o2)->Double.compare(o2.getHeight(),o2.getHeight()))); // 降序

Stream流

用于操作集合或者数组的数据

对比集合或数组的优势

Steam流大量的结合了Lambda的语法风格来编程,提供了一种更强大,更加简单的方式操作集合或数组中的数据,代码更简洁,可读性更好

使用步骤:

  1. 获取Stream流,Stream流代表一条流水线,并能与数据源建立连接
  2. 使用流水线各种方法对数据进行处理(过滤(filter),排序(),去重())、计算,得到最终结果
  3. 将处理的结果,遍历、统计、收集到一个新集合中返回

获取Stream流

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
Collection<String> list = new ArrayList<>();
// 1、获取集合的Stream流
Stream<String> s1 = list.stram();

Map<String,Integer> map = new HashMap<>();
// 2、获取Map集合的stream流
// 2.1获取键流
Stream<String> keys = map.keySet().stream();
// 2.2获取值流
Stream<String> values = map.values().stream();
// 2.3获取键值对流
Stream<Map.Entry<String,Integer>> entrys = map.entrySet().stream();

String[] names = {"张无忌","周芷若","赵敏"};
// 3、获取数组的Stream流
Stream<String> as1 = Arrays.stream(names); // 另外一种写法:Stream<String> as1 = Stream.of(names);

Stream流常用方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
List<String> list = new ArrayList<>();

// 1、过滤方法:filter
list.stream().filter(s->s.startsWith("张") && s.length() == 3);

// 2、排序方法:sorted
list.stream().sorted(); // 方式1:list对象类实现Comparable接口,指定比较规则
list.stream().sorted((m1,m2)-> Double.compare(m2.getScore(),m1.getScore())); // 方式2:list对象类没有实现Comparable接口

// 3、排序后取前几个:limit
list.stream().sorted().limit(3);

// 4、跳过前几个:skip
list.stream().sorted().skip(3);

// 5、去重:distinct
list.stream().sorted().distinct();// 需要重写对象类的hashCode和equals方法,才可以使用

// 6、把流上的数据加工成新数据:map
list.steam().map(m -> m.getName() + "==>" + m.getScore());

// 7、把两个流合并一起:concat
Stream<String> s1 = Stream.of("张三","李四","西门吹雪");
Stream<String> s2 = Stream.of("楚留香","石观音");
Steam<String> allStream = Stream.concat(s1,s2);

Stream<Integer> s3 = Stream.of(12,11,43);
Steam<Object> allStream = Stream.concat(s1,s3);// 使用Object获取合并后的流

终结流方法

终结方法:是调用完成后,不会返回新的stream流了,没法继续使用stream流了

1
2
3
4
5
6
7
List<Movie> movies = new ArrayList<>();
// 1、forEach
movies.stream().forEach(System.out::println);
// 2、count
long count = movies.stream().skip(2).count(); // 总数-2
//3、取最大值,最小值
Movie max  = movies.steam().max(((o1,o2)->Double.compare(o1.getScore(),o2.getScore()))).get();

收集Stream流(collection)

stream流,只是我们一种手段,一种方便操作集合/数组的手段

而我们需要的结果是:集合/数组(开发的目的)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
List<String> list = new ArrayList<>();
// 获取stream流,只能使用一次
Stream<String> stream = list.stream();

// 1、收集到list集合 
List<String> newList = stream.filter(s->s.startsWith("张") && s.length()==3).collection(Collectors.toList());
// List<String> newList = stream2.filter(s->s.startsWith("张") && s.length()==3).toList(); // jdk16才支持

// 2、收集到set集合
Set<String> newset = stream3.filter(s->s.startsWith("张") && s.length()==3).collection(Collectors.toSet());

// 3、收集到数组中去

// 4、收集到map集合(拓展)
List<Movie> movies = new ArrayList<>();
Movies.add(new Movie("摔跤吧,爸爸",9.5,"阿米尔汗"));
...
Map<String,Doubel> map = movies.steam().limit(3).collect(Collectors.toMap(m1-> m1.getName(), m2->m2.getScore(),(v1,v2)->v1));// (v1,v2)->v1)避免重复,如果重复了,前面的覆盖后面,(v1,v2)->v2:后面覆盖前面

为什么集合还是能转换成字符串集合,如:Set<String>,而不转化成Object对象呢?

数组是强类型,数组类型是固定死的,集合是泛化的,最终还是Object,引用String集合也能接其他类型,而String数组只能接收字符串

File

是一种将数据长久保存到硬盘的技术

File是java.io.包下的类

创建File对象

1
2
3
4
// 1、方式1
File f1 = new File("D:/images/晴空.png");  // file对象路径可以是文件,也可以是文件夹(文件夹路径不存在,可以创建出来f1.createNewFile())
// file对象路径,可以是绝对路径也可以是相对路径
File f4 = new File("cn.snailsir.pkg/src/晴空.png");// 相对路径:文件位置,项目下的cn.snailsir.pkg包下的src下的晴空.png文件

常用方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
File f = new File("E:\\resource\\meinv.png");

f.exists();// 判断当前文件对象,对应的文件路径是否存在
f.isFile();// 判断当前文件对象,是否是文件
f.isDirectory(); // 判断当前文件对象,是否是文件夹
f.getName(); // 获取文件的名称,包括后缀:meinv.png
f.length(); // 获取文件大小(字节数)
f.getPath(); // 获取创建文件对象时,使用的路径
f.getAbsolutePath(); //获取创建文件对象时,使用的绝对路径

long time = f.lastModified();// 获取文件最后修改时间
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// 将long类型的时间戳转换成LocalDateTime对象
LocalDateTime dateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(time),ZonedId.systemDefault());
// 使用DateTimeFormatter对象dtf将LocalDateTime格式化为指定格式的字符串时间
String formattedTimed = dtf.format(dateTime);
//String formattedTimed = LocalDateTime.ofInstant(Instant.ofEpochMilli(time),ZonedId.systemDefault()).format(dtf);

文件创建与删除

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// 1、创建文件
File f1 = new File("D:\\a.txt"); // 文件不存在
System.out.println(f1.createNewFile());// true

// 2、创建文件夹
File f2 = new File("D:\\picture\\aa\\bbb"); // 文件夹不存在
System.out.println(f2.mkdir());// false,只能创建1级目录 D:\\picture

// 3、创建文件夹
File f3 = new File("D:\\picture\\aa\\bbb"); // 文件夹不存在
System.out.println(f3.mkdirs());// true,递归创建

// 4、删除文件和空文件夹
f3.delete();

遍历文件夹

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// list,获取当前目录下所有的”一级文件名称(文件或文件夹名称)“到一个字符串数组中
File f = new File("E:\\pictures");
String[] names = f.list();
for(String name:names){
  System.out.println(name);
}
// listFiles() 获取当前目录下所有的“一级文件对象”到一个文件对象数组中去返回(重点)
File f1 = new File("E:\\pictures");
File[] files = f1.listFiles();
for(File file:files){
  System.out.println(file.getAbsolutePath()); 
}
  • 使用listFiles(),主调是文件,或者路径不存在时,返回null(new File("不存在的路径或文件").listFiles()返回null)
  • 使用listFiles(),主调是空文件夹,返回一个长度为0的数组(new File("空文件夹").listFiles().length()结果为0l)
  • 使用listFiles(),主调是文件夹,并且里面有隐藏文件时,将里面所有文件和文件夹的路径放在file数组中返回,包括隐藏文件
  • 使用listFiles(),主调是文件夹,但是没有权限访问该文件夹是,返回null

文件搜索(递归)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static void searchFile(File dir,String fileName){
  // 1、卫语句
  if(dir == null || !dir.exists() || dir.isFile()) {
    return;
  }
  // 2、获取当前目录下的全部一级文件对象
  File[] files = dir.listFiles();
  if(files == null || files.length() == 0){
    return;
  }
  // 3、遍历全部一级文件对象
  for(File file:files){
    //4、判断当前file对应的文件对象是文件还是文件夹
    if(file.isFile()){
      if(file.getName().contains(fileName)){
        System.out.println(file.getAbsolutePath());
      }
    }else{
      searchFile(file,fileName); 
    }
  }
}

字符集

ASCII字符集

标准ASCII使用1个字节存储一个字符,首位是0,总共可表示128个字符

GBK

汉字编码扩展规范,包含了2w多个汉字等字符

GBK中一个中文字符编码按2个字节的形式存储

GBK兼容了ASCII字符集

GBK规定:汉字的第一个字节的第一位必须是1,因此以0开头的就是对ASCII字符集的兼容

Unicode字符集(统一码,万国码)

unicode是国际组织制定的,可以容纳世界上所有的文字、符号的字符集

UTF-32

4个字节表示一个字符

UTF-8

是Unicode字符集的一种编码方案,采取可变长编码方案,共分4个长度区:1个字节、2个字节、3个字节、4个字节

英文字符、数字等只占1个字节(兼容标准ASCII编码),汉字字符占用3个字节

那怎么区分是1字节长度还是2字节长度3字节长度4字节长度呢?

1字节长度: 0xxxxxxx,以0开头

2字节长度:110xxxxx 10xxxxxx 以110开头,第二个以10开头

3字节长度:1110xxxx 10xxxxxx 10xxxxxx 以1110开头,第二个以10开头,第三个以10开头

4字节长度:11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 以11110开头,第二个以10开头,第三个以10开头,第三个以10开头

编码与解码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
String str = "我爱学习java!";
// 1、编码
byte[] bytes = str.getBytes(); // 默认用平台编码UTF-8
System.out.println(Arrays.toString(bytes));

byte[] bytes2 = str.getBytes("GBK"); // 指定GBK编码
System.out.println(Arrays.toString(bytes2));

// 2、解码
String rs = new String(bytes); // 默认用平台编码UTF-8解码
System.out.println(rs);

String rs2 = new String(bytes,"GBK"); // 指定用GBK编码解码
System.out.println(rs2);

IO流

IO流用于读写数据的(可以读写文件、网络中的数据…)

I指Input,称为输入流,负责把数据读到内存中去

O指Output,称为输出流,负责写数据出去

IO原始流分类

按流的方向:输入流与输出流

按流中数据的最小单元:

字节流:适合操作所有类型的文件,如:音频、视频、图片、文本文件的复制、转移等

字符流:只适合操作纯文本文件,如:读写txt、java文件等

字节输入流

抽象类: InputStream

实现类:FileInputStream

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// 1、创建文件字节输入流管道与目标文件接通
// InputStream is = new FileInputStream(new File("cn.snailsir.study/src/aa.txt")); // 完整写法
InputStream is = new FileInputStream("cn.snailsir.study/src/aa.txt"); // 简洁写法,实际就是对完整方法的封装

// 2、读取字节
// 2.1 每次读取一个字节返回
int b;
while((b = is.read()) != -1) { // is.read()每次读取一个字节返回,如果没有字节可读,返回-1
  System.out.println((char)b);
}

// 缺点:
// 1.性能差,每次一个一个字节读取,性能很差
// 2.无法避免读取汉字输出乱码问题,会截断汉字的字节
  

// 2.2 每次读取多个字节
byte[] buffer = new byte[3];// 可以读取3个字节的数组
int len;
while((len = is.read(buffer)) != -1){ // 读取3个字节并存储到数组中
  String rs = new String(buffer,0,len); // new String(buffer)在最后一次读取可能会出现bug,如果最后一次读取只有2个字节,那么rs的结果是3个字节,因为最后一个字节是上一个桶剩下的
  System.out.print(rs);
} 

// 性能较好
// 同样无法避免读取汉字输出乱码问题

// 2.3 一次性读取全部字节
// 2.3.1 定义一个字节数组与源文件一模一样大小(jdk9之前写法)
File f = new File("cn.snailsir.study/src/aa.txt");
long size = f.length();
byte[] buffer = new byte[(int)size];
// 2.3.2 读取内容
int len = is.read(buffer);
String rs = new String(buffer);

// jdk9开始写法
byte[] buffer = is.readAllBytes();
String rs = new String(buffer);

// 不能读取大文件内容,会内存泄漏,解决了输出中文乱码问题

字节输出流

抽象类:OutputStream

实现类:FileOutputStream(文件字节输出流)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// 1、创建一个文件字节输出流管道与目标文件接通
OutputStream os = new FileOutputStream("cn.snailsir.study/src/aa.txt",true); // 设置为true,是追加管道,不设置为覆盖管道


// 2 写数据到管道中
// 2.1 每次写一个字节 public void write(int a)
os.write('a');
os.write('中'); // 乱码 因为一个中文占3个字节,而这里只写入一个字节

// 2.2 写一个字节数组的数据 write(byte[] buffer)
byte[] bytes = "abc我爱中国".getBytes();
os.write(bytes);

// 2.3 写字节数组的一部分
os.write(bytes,3,12);// 我爱中国,3:开始位置 12:长度

// 3、关闭通道  io流管道属于系统资源,会占用内存和响应的IO资源(总线),用完之后必须关闭管道,以释放占用的系统资源
os.close();// 关闭通道,同时刷新缓存区内容到赢怕中去(但字节输出流:没有提供缓存区)

字符输入流

抽象类:Reader

实现类:FileReader

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// 1、创建文件字符输入流管道与源文件接通
// public FileReader(File file)  new FileReader(new File("cn.snailsir.study/src/aa.txt"));
// public FileReader(String filePath)
Reader fr = new FileReader("cn.snailsir.study/src/aa.txt");

// 2、读取字符
// 2.1 每次读取一个字符
int b;
while((b=fr.read()) != -1){ // fr.read() 每次读取一个字符返回,没有字符可读返回-1
  System.out.println((char)b);
}
// 解决了中文乱码问题,但是性能较差

// 2.2 每次读取多个字符
char[] buffer = new char[3];
int len; // 记住每次读取多少个字符
while((len = fr.read(buffer)) != -1){
    String rs = new String(buffer,0,len);
    System.out.print(rs);
} 
// 避免了中文乱码,性能可以

字符输出流

抽象类:Writer

实现类:FileWriter

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// 1、创建一个文件字符输出流管道与目标文件接通
Write fw = new FileWrite("cn.snailsir.study/src/aa.txt",true); // 设置为true为追加管道,不设置为覆盖管道

// 2、写字符数据出去
// 2.1 写一个字符
fw.write(97); // a
fw.write('中'); // 无乱码
fw.write("\r\n".getBytes()); 

// 2.2 写一个字符串出去
fw.write("我爱你中国");
fw.write("我爱你中国",0,3);// 我爱你 0: 开始位置,3: 长度

// 2.4 写一个字符数组出去
char[] chars = "我爱你中国".toCharArray();
fw.write(chars);
fw.write(chars,6,2); // 6:开始位置 2:长度
  

资源释放

jdk7开始,提供了以下更简单的资源释放方案

该资源(try内部)使用完毕后,会自动调用其close()方法,完成对资源的释放

1
2
3
4
5
6
7
8
try(
  // 资源:一般指的是最终实现了`AutoCloseable`接口
	OutputStream os = new FileOutputStrem("aa.txt");
){
  
}catch(Exception e){
  e.printStackTrace();
}

IO缓冲流

对原始流进行包装,以提高原始流读写数据的性能

字节缓冲输入流:BufferedInputStream

字节缓冲输入流自带了8KB缓冲池

字节缓冲输出流:BufferedOutputStream

字节缓冲输出流自带了8KB缓冲池

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
try(
	// 1、创建字节输入流管道与源文件接通
	InputStream is = new FileInputStream("cn.snailsir.study/src/aa.txt");
  // 使用高级缓存字节输入流包装低级的字节输入流
  InputStream bis = new BufferedInputStream(is);
  
  // 2、创建字节输出流管道与目标文件接通
	OutputStream os = new FileOutputStream("cn.snailsir.study/src/aa-bak.txt");
  // 使用高级缓存字节输出流包装低级的字节输出流
  OutputStream bos = new BufferedOutputStream(os);
){
    // 3、准备一个字节数组
  byte[] buffer = new byte[1024];
  // 4、转移数据
  int len;
  while((len = bis.read(buffer)) != -1){
    bos.writ(buffer,0,len);
  } 
  	
}catch(Exception e){
  e.printStackTrace();
}

字符缓冲输入流:BufferedReader

字符缓冲输出流自带了8K(8192个字符)缓冲池

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// 1、创建字符输入流管道与源文件接通
Reader fr = new FileReader("cn.snailsir.study/src/aa.txt");
// 使用高级缓存字节输入流包装低级的字节输入流
BufferedReader br = new BufferedInputStream(fr);

char[] buffer = new char[1024]; // 一次读入1024个字符
int len;
  while((len = br.read(buffer)) != -1){
    String rs = new String(buffer,0,len);
    System.out.print(rs);
} 

// 缓存字符输入流多了一个按照行读取内容的功能
String line;
while((line = br.readLine()) != NULL){
    System.out.println(line);
}

字符缓冲输出流:BufferedWriter

字符缓冲输出流自带了8K(8192个字符)缓冲池

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
try(
	// 1、创建一个文件字符输出流管道与目标文件接通
	// Write fw = new FileWrite("cn.snailsir.study/src/aa.txt"); // 覆盖管道
	Write fwo = new FileWrite("cn.snailsir.study/src/aa.txt",true); // 追加管道
  // 使用高级缓存字符输出流包装低级的字符输出流
  BufferedWriter fw = new BUfferedWriter(fwo); // 追加管道
){
    // 2、开始写字符数据出去
  	// 2.1 写一个字符出去: public void write(int a)
    fw.write('a');
    fw.write(97);
    fw.write('中');
    fw.wtite("\r\n".getBytes()); // 换行
  	fw.newLine(); // 换行新增
  
  	// 2.2 写一个字符串出去
  	fw.write("我爱你中国");
  
  	// 2.3 写字符串一部分出去 public void write(String s,int pos,int len)
  	fw.write("我爱你中国",0,3);// 我爱你
  
    // 2.4 写一个字符数组出去:public void write(char[] chars)
  	char[] chars = "我爱你中国".toCharArray();
  	fw.write(chars)
    
    // 2.5 写每一个字符数组的一部分 public void write(char[] chars,int pos,int len)
    fw.write(chars,6,2);
  
  	//fw.flush();// 刷新内存缓存数据到硬盘上
  	
}catch(Exception e){
  e.printStackTrace();
}

缓存字符输出流多了一个换行的功能newLine

原始流、缓冲流的性能分析

1
2
3
4
5
6
// 使用低级的字节流按照一个个字节的形式复制文件:慢的无人让人忍受,直接淘汰,禁止使用
// 使用低级的字节流按照字节数组的形式复制文件:还可以,但是相对较慢
// 使用高级的缓冲字节流按照一个个字节的形式复制文件:还是特别慢,不推荐使用
// 使用高级的缓冲字节流按照字节数组的形式复制文件:极快,推荐使用

调节字节数组大小默认8k),可以提高效率但并不是越大越好

IO转换流

为了解决不同编码读取时会乱码的问题

字符输入转换流:InputStreamReader

原理:先获取文件的原始字节流,再将其按真实的字符集编码转成字符输入流,这样字符输入流中的字符就不乱码了

字符输出转换流:OutputStreamWriter

原理:获取字节输出流,再按照指定的字符集编码将其转换成字符输出流,以后写出去的字符就会用该字符集编码了

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
try(
	// 1、得到GBK文件的原始字节输入流
  InputStream is = new FileInputStream("aa.txt");
  // 2、通过字符输入转换流把原始字节流按照指定编码转换成字符输入流
  // public InputStreamReader(InputStream is,String charset) 把原始的字节输入流,按照指定字符集编码转成字符输入流(重点)
  Reader isr = new InputStreamReader(is,"GBK");
  // 3、把字符输入流包装成高级的缓冲字符输入流
  BufferedReader br = new BufferedReader(isr);
){
  // 4、按照行数读取
  String line;
  while((line = br.readLine()) != null){
    System.out.println(line)
  }
}catch(Exception e){
  e.printStackTrace();
}



try(
	// 1、创建一个文件字节输出流通向目标文件
  OutputStream is = new FileOutputStream("out.txt");
  // 指定写出去的编码时GBK
  // public OutputStreamWriter(OutputStream os,String charset) 把原始的字节输出流,按照指定编码转成字符输出流
  Reader isr = osw OutputStreamWriter(is,"GBK");
  // 2、把字符输出流包装成高级的缓冲流
  BufferedWrite bw = new BufferedWriter(osw);
){
  // 4、写入数据
  bw.write("xxxx");
  bw.newLine();
  bw.write("yyyy");
  bw.newLine();
  bw.write("zzzz");
}catch(Exception e){
  e.printStackTrace();
}

IO打印流

PrintStream字节输出流

PrintWriter 字符输出流

作用:可以实现更方便、更高效的打印数据出去,能实现打印啥出去就是啥出去。(我们可以将日志打印到指定的文件中去)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
try(
  // public PrintStream(OutputStream/File/String); 打印流直接通向字节输出流/文件/文件路径
  // public PrintStream(String fileName,Charset charset); 可以指定写出去的字符编码
  // public PrintStream(OutputStream out,boolean autoFlush); 可以指定实现自动刷新
  // public PrintStream(OutputStream out,boolean autoFlush,String encoding); 可以指定实现自动刷新,并可指定字符的编码
	PrintStream ps = new PrintStream("ps.txt");
  // PrintWriter pw = new PrintWriter(new FileWriter("ps.txt"),true); // 追加 与PrintStream使用方法一模一样
){
  // 以下打印的内容会原封不动的存储到ps.txt文件中
  // public void println(Xxx xx) 打印任意类型的数据出去
  // public void write(int/String/char[]/...) 可以支持写字符数据出去
  ps.println(6666); 
  ps.println('a');
  ps.println(true);
  ps.println("我爱你中国");
}catch(Exception e){
  e.printStackTrace();
}

PrintStream与PrintWriter区别

  • 打印数据的功能上是一模一样的:都是使用方便,性能高效
  • PrintStream继承自字节输出流OutputStream,因此支持写字节数据的方法
  • PrintStream继承自字符输出流Writer,因此支持写字符数据出去

IO数据流

常用于通讯领域

数据输出流:DataOutputStream

允许把数据和其类型一并写出去

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
try(
	DataOutputStream dos = new DataOutputStream(new FileOutputStream("out.txt"));
){
  // 写数据和类型出去
  dos.writeByte(97); 
  dos.writeBoolean(true); 
  dos.writeInt(1000); 
  dos.writeChar('a'); 
  dos.writeUTF("我爱你中国"); 
}catch(Exception e){
  e.printStackTrace();
}

数据输入流:DataInputStream

用于读取数据输出流写出去的数据

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
try(
  // 创建高级的特殊数据输入流管道包装低级的字节输入流管道
	DataInputStream dis = new DataInputStream(new FileInputStream("out.txt"));
){
  // 开始读取,顺序要与写入流顺序一致
  byte b = dis.readByte(); 
  System.out.println(b);
  
  boolean b1 = dis.readBoolean();
  System.out.println(b1);
  
  int i = dis.readInt();
  System.out.println(i);
  
  char c = dis.readChar();
  System.out.println(c);

  String s = dis.readUTF();
  System.out.println(s);
}catch(Exception e){
  e.printStackTrace();
}

IO序列化流

对象序列化:ObjectOutputStream

1
2
3
4
5
6
7
8
9
// 把java对象写入到文件中去
// 准备一个对象
Student s = new Student("snailsir",27,"123456"); // 注意:Student要想实现序列化,就必须实现Serializable接口 public class Student implements Serializable{}
// 创建对象字节输出流管道与目标文件管道
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("obj.txt"));
// 开始写对象出去
oos.writeObject(s);
// 关闭资源
oos.close();

对象反序列化:ObjectInputStream

1
2
3
4
5
6
7
// 把文件里的java对象读出来
// 创建对象字节输入流管道
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("obj.txt"));
// 读取对象进来(对象反序列化)
Student s = (Student)ois.readObject();
// 关闭资源
ois.close();

特殊文件

Properties文件

特点

  • 都只能是键值对
  • 键不能重复
  • 文件后缀一般是.properties结尾的

我们使用Properties实例来对Properties文件进行操作

Propertites实例

  • 是一个Map集合(键值对集合),但我们一般不会当集合使用
  • 核心作用:Properties是用来代表属性文件的,通过Properties可以读写属性文件里的内容

读取propertites文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 1、创建属性集对象,代表一个属性文件
Properties properties = new Properties();
System.out.println(properties);

// 2、加载属性文件信息到属性集对象中去
// public void load(InputStream is)
// properties.load(new FileInputStream("users.properties"));  // 字节流
// public void load(Reader reader)
properties.load(new FileReader("users.properties"));  // 字符流
System.out.println(properties); // 发现中文乱码,properties对中文识别不好,需要我们对编辑器去做一下配置

// 根据键取值
// System.out.println(properties.get("mysql"));
// public String getProperty(String key)
System.out.println(properties.getProperty("mysql"));

// 获取所有键名
// public Set<String> stringPropertyNames();
Set<String> keys = properties.stringPropertyNames();
for(String key:keys){
  String value = properties.getProperty(key);
  System.out.println(key + "==>" + value);
}

// 遍历数据
proerties.forEach((k,v) ->{
  System.out.println(k + "===>" + v);
});

写入数据到properties中

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// 1、创建属性集对象
// public Properties() 用于构建Properties集合对象(空容器)
Properties properties = new Properties();

// 2、存入数据
// public Object setProperty(String key,String value) 保存键值对数据到Properties对象中去
properties.setProperty("admin","66666");
properties.setProperty("xiaozhao","wuji");

// 3、转存到文件
// public void store(OutputStream os,String comments) 把键值对数据,通过字节输出流写入到属性文件里去
// public void store(Writer w,String comments) 把键值对数据,通过字符输出流写入到属性文件里去
properties.store(new FileOutputStream("user.properties","this is properties file comment"));

xml文件

全称(Extensible Markup Language,可扩展标记语言)

本质是一种数据的格式,可以用来存储复杂的数据结构和数据关系

特点

  • xml中的标签成对出现
  • xml标签名可以自己定义,但必须要正确的嵌套
  • xml中只有一个根标签
  • xml中的标签可以有属性
  • 如果一个文件中放置的是xml格式的数据,这个文件就是xml文件,后缀一般要写成.xml

注意:

特殊数据区:<![CDATA[里面内容随便写,不用担心字符转义问题]]

作用

本质是一种数据格式,可以存储复杂的数据机构和数据关系

应用场景

经常用来作为系统的配置文件;或者作为一种特殊的数据结构,在网络中进行传输

读取xml文件内容

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// 使用Dom4J解析xml文件

// 1、创建一个SaxReader解析器对象
// public SAXReader();
SAXReader saxReader = new SAXReader();

// 2、把xml文件读成一个Document文档对象
// public Document read(String url) 
// public Document read(InputStream is) 通过字节输入流读取XML文件
Document document = saxReader.read("contact.xml");

// 3、文档对象中包含了xml的全部数据,提供了方法获取数据
// Element getRootElement() 获得根元素对象
Element rootElement = document.getRootElement();
System.out.println(rootElement.getName()); // 获取根元素名称

// 4、提取子元素对象
List<Element> sonEles = rootElement.elements();
for (Element sonEle:sonEles){
  System.out.println(sonEle.getName());
}

// 5、获取指定单个子元素对象
Element userEle = rootElement.element("user");
System.out.println(userEle.elementText("name"));

// 6、获取子元素的属性对象 id=xx
Attribute idAttr = contactEle.attribute("id");
System.out.println(idAtrr.getValue()); // 对应属性id的值
System.out.println(contactEle.attributeValue("id")); // 直接拿属性id的值

// 7、文本值
// 通过父元素拿到子元素文本值
System.out.println(contactEle.elementText("name"));
System.out.println(contactEle.elementTextTrim("name")); // 去掉空格

// 先拿到元素对象,再提取其文本值
Element emailEle = contactEle.element("email");
System.out.println(emailEle.getText());

写入数据到xml中

把数据拼接成xml格式,然后用IO流写进去

1
2
3
4
5
6
7
8
9
StringBuilder sb = new StringBuilder();
sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n");
sb.append("<user>\r\n");
sb.append("\t<name>").append("张三").append("</name>\r\n");
sb.append("</user>\r\n");

PrintStream ps = new PrintStream("user.xml");
ps.println(sb);
ps.close();

线程

java多线程

网络

网络编程

单元测试

单元测试

反射

反射

注解

注解

使用 Hugo 构建
主题 StackJimmy 设计