更多详情内容请访问:JavaSE 系列文章导读

1、Java 语言概述

1.1 Java 语言特性

  1. 简单性:Java 程序员不需要操作复杂的指针,只支持单继承
  2. 面向对象:Java 将重点放在数据(即对象)和对象的接口上
  3. 分布式:Java 有一个丰富的例程库,用于处理像 Http 和 FTP 之类的 TCP / IP 协议
  4. 健壮式:Java 引入了自动垃圾回收机制(GC 机制),Java 线程启动了一个单独的垃圾回收线程
  5. 安全性与可移植性:编译时的错误检验。类装载检查,把本机上的类和网络资源类相分离,在调入类的时候进行检查。字节码校验,沙箱机制,限定访问权限
  6. 体系结构中立:Java 编译器生成与特定计算机体系结构无关的字节码,可以很容易地在任何机器上解释执行,而且还可以动态地转换成本地机器代码
  7. 动态性:库中可以自由地添加新方法和实例变量,而对客户端却没有任何影响
  8. 高性能:与那些解释型的高级脚本语言相比,Java 的性能还是较优的
  9. 解释型:Java 程序在 Java 平台上被编译为字节码格式,在运行时,Java 平台中的 Java 解释器对这些字节码进行解释执行
  10. 多线程:Java 语言支持多个线程的同时执行,并提供多线程之间的同步机制

1.2 Java 基本语法

1.2.1 变量

  1. 变量的概念:
    • 内存中的一个存储区域
    • 该区域的数据可以在同一类型范围内不断变化
    • 变量是程序中最基本的存储单元。包含变量类型变量名存储的值
  2. 使用变量注意:
    • Java 中每个变量必须先声明后使用
    • 基本数据类型的变量如果是临时变量,只要定义了就会分配内存空间;如果作为对象的属性,只要该对象不实例化就不会分配内存空间
  3. 分类:按声明的位置不同
    • 在方法体外,类体内声明的变量称为成员变量存储在堆中(堆内存的对象中)
    • 在方法体内声明的变量称为局部变量存储在栈中(作用的范围结束,变量空间会自动释放)
    • image-20220215013821226

1.2.2 基本数据类型

image-20220215015302990

  1. 自动类型转换:容量小的类型自动转换为容量大的数据类型

    image-20220215020156168

    多种数据类型混合运算的时候,先转换成容量最大的那一种再做运算

  2. 强制类型转换:

    • 字符串不能直接转换为基本类型,但通过基本类型对应的包装类则可以实现把字符串转换成基本类型
    • boolean类型不可以转换为其它的数据类型
    • 通过截断小数部分将浮点值转换为整型,如想对浮点数进行舍入计算,需要使用 Math.round 方法,注意:Math.round() 方法是先+0.5的基础上再向下约等于

1.2.3 运算符

当参与“/”运算的两个操作数都是整数时,表示整数除法;否则,表示浮点除法

  1. 自增与自减运算符

    符号 说明 用例 结果
    - - i--:先运算后取值
    --i:先取值后运算
    a = 2; b = a--;
    a = 2; b = --a;
    a = 1; b = 2;
    a = 1; b = 1;
    ++ i++:先运算后取值
    ++i:先取值后运算
    a = 2; b = a++;
    a = 2; b = ++a;
    a = 3; b = 2;
    a = 3; b = 3;

    image-20220221142048833

  2. 逻辑运算符

    操作符 例子 结果 描述
    ^ true ^ false TRUE 异或运算符,只要两边的操作数不同,结果就是true
  3. 数学函数与常量

    Math 类提供了一些方法是整数有更好的运算安全性。如果一个计算溢出,数学运算符只是悄悄地返回错误的结果而不作任何提醒。

    如果基本的整数和浮点数精度都不能够满足需求,那么可以使用 java.math 包中两个很有用的类:BigIntegerBigDecimal

1.2.4 控制语句

  1. 转向语句:

    • break

      1. 默认情况下只能终止离它“最近”的“一层”循环。break 语句终止的是内部循环,不影响外部循环的执行

      2. 还可以用来终止指定的循环,可以给每个循环设置标识,标签必须放在希望跳出的最外层循环之前,并且必须紧跟一冒号

        read_data:
        while(...){
            ...;
            break read_data;
        }
    • continue:终止当前本次循环,直接进入下一次循环继续执行,用法和 break 类似

  2. 返回语句:return 直接结束整个方法,不管这个 return 处于多少层循环之内

  3. 选择语句:

    • if...else
    • switch-case
      1. case 子句中的值必须是常量
      2. 如果判断的具体数值不多,而且符合 byteshortcharintString枚举等几种类型,建议使用swtich语句(效率稍高);对区间判断,对结果为 boolean 类型判断,使用 if,if 的使用范围更广
  4. 循环语句:① while;② do...while;③ for(foreach)

1.3 数组

1.3.1 数组概述

  1. 数组本身是引用数据类型,而数组中的元素可以是任何数据类型
  2. 创建数组对象会在内存中开辟出一整块连续的空间,而数组名中引用的是这块连续空间的首地址
  3. 数组的长度一旦确定就不能修改

1.3.2 一维数组的使用

  1. 声明方式:type[] 变量名;
  2. 初始化:
    • 动态初始化:数组声明且为数组元素分配空间与赋值的操作分开进行,例:int[] arr = new int[3];arr[0] = 3;
    • 静态初始化:在定义数组的同时就为数组元素分配空间并赋值,例:int arr[] = new int[]{1,2,3};int[] arr = {3,9,8};
  3. 初始值:
    • 对于基本数据类型而言,默认初始值各有不同
    • 对于引用数据类型而言,默认初始值为 null

1.3.3 多维数组

对于多维数组,以二维数组举例。可以看成是一维数组 array1 又作为另一个一维数组 array2 的元素而存

在。其实,从数组底层的运行机制来看,其实没有多维数组

1.3.4 数组的拷贝

Java 中允许将一个数组变量拷贝到另一个数组变量。这时,两个变量将引用同一个数组。

如果希望将一个数组的所有值拷贝到一个新的数组中去,可以使用以下两种方法:

  • 新数组 = Arrays.copyOf(老数组, 新数组长度);
  • System.arraycopy(拷贝源, 第几个元素开始拷贝, 拷贝目标, 从目标第几个位置开始拷贝, 拷贝原数组长度);

1.3.5 数组存储原理

image-20220219015730447

1.3.6 Arrays 工具类

方法 作用
static boolean equals(int[] a, int[] b) 判断两个数组是否相等
static String toString(int[] a) 输出数组信息
static void fill(int[] a, int val) 将指定的 int 值分配给指定的 int 数组的每个元素
staitc void sort(int[] a) 对数组进行排序
static int binarySearch(in[] a, int key) 对排序后的数组进行二分法检索指定的值

2、面对对象编程

2.1 包装类

image-20220221151214656

【面试题】如下两个题目输出结果相同吗?各是什么?

Object o1 = true ? new Integer(1) : new Double(2.0);
System.out.println(o1); // 1.0

Object o2;
if (true)
    o2 = new Integer(1);
else
    o2 = new Double(2.0);
System.out.println(o2); // 1

三目运算符比较基本数据类型,所以在编译阶段自动拆箱为 int 和 double 类型,由于三目运算符要求 表达式2表达式3 类型一致,所以在编译阶段自动类型提升(即 int 自动类型转换为 double 类型),再自动装箱为 Object

【面试题】

Integer i = new Integer(1);
Integer j = new Integer(1);
System.out.println(i == j); // false

//Integer 内部定义了 IntegerCache 结构,IntegerCache 中定义了 Integer[],
//保存了从 -128~127 范围的整数。如果我们使用自动装箱的方式,给 Integer 赋值的范围在 -128~127 范围内时,可以直接使用数组中的元素,不用再去 new 了。目的:提高效率
Integer m = 1;
Integer n = 1;
System.out.println(m == n); // true

Integer x = 128; // 相当于 new 了一个 Integer 对象
Integer y = 128; // 相当于 new 了一个 Integer 对象
System.out.println(x == y); // false

2.2 内存解析

2.2.1 JVM 简单内存结构

image-20220215223821895

  1. 堆(Heap),此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。这一点在 Java 虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配。GC 管理的主要区域。可以处于物理上不连续的内存空间。
  2. 栈(Stack),是指虚拟机栈。虚拟机栈用于存储局部变量等局部变量表存放了编译期可知长度的各种基本数据类型对象引用。 方法执行完,自动释放。
  3. 方法区(Method Area),用于存储已被虚拟机加载的类信息、常量(字符串常量池)、静态变量、即编译器编译后的代码等数据。

2.2.2 JVM 执行过程

image-20220716210416096

一个 Java 文件从编码开始到执行需要经过 4 个阶段:

  1. 编译阶段:首先 .java 文件经过了 Javac 进行编译成了.class 文件
  2. 加载阶段:紧接着 .class 文件经过了类加载器加载到 JVM 的内存当中
  3. 解释阶段:class 字节码经过了字节码解释器解析成系统可以识别到的指令码
  4. 执行阶段:向硬件设备发送指令码来进行操作

2.3 类的成员

2.3.1 权限修饰符

权限修饰符 同一类下 同一包下(邻居关系,无继承) 不同包下(有继承关系) 不同包(且非子类)
public
protected
default(缺省)
private

注意:default 并不是关键字,而是根本不写

2.3.2 方法

  1. 方法的重载(overload)

    • 定义:在同一类中定义的多个同名方法。通过参数列表来区分不同的方法,与修饰符和返回值类型已经抛出异常类型无关
    • 条件:同类、同名、不同参(个数/顺序/类型)
  2. 可变个数的形参

    • 声明格式:方法名(参数的类型名 ...参数名)
    • 可变个数形参的方法与同名的方法之间,彼此构成重载
    • 方法的参数部分有可变形参,需要放在声明的最后,且最多只能声明一个可变个数形参
  3. 方法的参数传递:Java 里方法的参数传递方式只有一种:值传递即将实际参数值的副本(复制品)传入方法内,而参数本身不受影响

    • 形参是基本数据类型:将实参基本数据类型变量的“数据值”传递给形参

      image-20220216004238347

    • 形参是引用数据类型:将实参引用数据类型变量的“地址值”传递给形参

      image-20220216004136171

2.3.3 构造器

父类构造器不可被子类继承

2.3.4 代码块

  1. 作用:对 Java 类或对象进行初始化
  2. 分类:
    • 静态代码块
      1. 可以有输出语句。可以对类的属性、类的声明进行初始化操作
      2. 不可以对非静态的属性初始化。即:不可以调用非静态的属性和方法
      3. 静态代码块的执行要先于非静态代码块
      4. 静态代码块随着类的加载而加载,且只执行一次
    • 非静态代码块
      1. 可以有输出语句。可以对类的属性、类的声明进行初始化操作
      2. 除了调用非静态的结构外,还可以调用静态的变量或方法
      3. 每次创建对象的时候,都会执行一次。且先于构造器执行

2.3.5 内部类

  • 成员内部类
    1. 和外部类不同,内部类还可以声明为 private 或 protected,可以在内部定义属性、方法、构造器等结构
    2. 可以调用外部类的结构
    3. 内部类可以声明为 static 的,但此时就不能再使用外层类的非 static 的成员变量,如果访问外部类的成员变量必须通过外部类的实例访问
    4. 可以声明为 abstract 类 ,因此可以被其它的内部类继承。同时也可以声明为 final 的
    5. 非 static 的成员内部类中的成员不能声明为 static 的,只有在外部类或 static 的成员内部类中才可声明 static 成员
    6. 外部类访问成员内部类的成员,需要 内部类.成员内部类对象.成员 的方式
    7. 当想要在外部类的静态成员部分使用内部类时,可以考虑内部类声明为静态的
  • 局部内部类
    1. 内部类仍然是一个独立的类,在编译之后内部类会被编译成独立的 .class 文件,但是前面冠以外部类的类名和 $ 符号,以及数字编号
    2. 只能在声明它的方法或代码块中使用,而且是先声明后使用。除此之外的任何地方都不能使用该类
    3. 局部内部类可以使用外部类的成员,包括私有的;可以使用外部方法的局部变量,但是必须是final的
    4. 局部内部类可以使用外部方法的局部变量,但是必须是final的
    5. 局部内部类和局部变量地位类似,不能使用 public、protected、缺省、private
    6. 局部内部类不能使用 static 修饰,因此也不能包含静态成员

2.4 OOP(面对对象)三大特征

2.4.1 封装

程序设计追求高内聚,低耦合

  • 高内聚 :类的内部数据操作细节自己完成,不允许外部干涉
  • 低耦合 :仅对外暴露少量的方法用于使用

2.4.2 继承

  • 继承的作用:

    1. 继承的出现减少了代码冗余,提高了代码的复用性
    2. 继承的出现,更有利于功能的扩展
    3. 继承的出现让类与类之间产生了关系,提供了多态的前提
  • 方法的重写(override):

    1. 子类重写的方法必须和父类被重写的方法具有相同的方法名称、参数列表
    2. 子类重写的方法返回值类型不能大于父类被重写方法的返回值类型,例如:父类方法返回值类型为该父类;其继承子类重写该方法返回值类型可以是其子类自身
    3. 子类重写的方法使用的访问权限不能小于父类被重写的方法的访问权限
    4. 子类方法抛出的异常不能大于父类被重写方法的异常
    5. 声明为 final 和 static 的方法不能被重写,但是 static 方法可以再次被声明(不是重写)

2.4.3 多态

对象的多态性:父类的引用指向子类的对象。可以直接应用在抽象类和接口上。

Java 引用变量有两个类型:编译时类型运行时类型

编译时,看左边;运行时,看右边。若编译时类型和运行时类型不一致,就出现了对象的多态性:

  • “看左边”:看的是父类的引用(父类中不具备子类特有的方法)
  • “看右边”:看的是子类的对象(实际运行的是子类重写父类的方法)

一个变量只能有一种确定的数据类型,一个引用类型变量可能指向(引用)多种不同类型的对象

一个引用类型变量如果声明为父类的类型,但实际引用的是子类对象,那么该变量就不能再访问子类中添加的属性和方法,例如:

Student student = new Student();
student.school = "哈佛"; // 合法
Person person = new Student();
person.school = "哈佛"; // 非法,Person 类没有 school 成员变量

2.5 关键字

2.5.1 this

  1. this 是一个关键字,是一个引用,保存内存地址指向自身,任何一个堆内存的 java 对象都有一个 this

  2. this 出现在实例方法中其实代表的是当前对象。

  3. this 不能使用在静态方法中。

  4. this. 大部分情况下可以省略,但是用来区分局部变量和实例变量的时候不能省略。

  5. this() 这种语法只能出现在构造方法第一行,表示当前构造方法调用本类其他的构造方法,目的是代码复用。

2.5.2 super

  1. 子类中所有的构造器默认都会访问父类中空参数的构造器
  2. 当父类中没有空参数的构造器时,子类的构造器必须通过 this(参数列表) 或者 super(参数列表) 语句指定调用本类或者父类中相应的构造器。同时,只能”二选一”,且必须放在构造器的首行

2.5.3 static

  1. 在 Java 中可以用 static 修饰属性、方法、代码块、内部类存在于方法区的静态域
  2. 被修饰后的成员具备以下特点:
    • 随着类的加载而加载
    • 优先于对象存在
    • 修饰的成员,被所有对象所共享
    • 访问权限允许时,可不创建对象,直接被类调用
  3. 在 static 方法内部只能访问类的 static 修饰的属性或方法,不能访问类的非 static 的结构

2.5.4 final

  1. final 标记的类不能被继承
  2. final 标记的方法不能被子类重写
  3. final 标记的变量(成员变量或局部变量)称为常量
  4. final 修饰的引用一旦指向某个对象,则不能重新指向其他对象,但该引用指向的对象内部的数据可修改

2.5.5 abstract

  1. 修饰一个类,这个类叫做抽象类
  2. 修饰一个方法,该方法叫做抽象方法
  3. 不能用 abstract 修饰变量、代码块、构造器、私有方法、静态方法、final 的方法、final 的类

2.5.6 instanceof

x instanceof A :检验 x 是否为类 / 接口 A 的对象,返回值为 boolean 类型

2.6 Object 类

2.6.1 Object 类型

在Java中,只有基本类型不是对象。所有的数组类型,不管是对象数组还是基本类型的数组都扩展了 Object 类型

2.6.2 equals() 方法

  • 重写 equals 方法原则

    1. 对称性:如果 x.equals(y) 返回是“true”,那么 y.equals(x) 也应该返回是“true”
    2. 自反性:x.equals(x) 必须返回是“true”
    3. 传递性:如果 x.equals(y) 返回是“true”,而且 y.equals(z) 返回是“true”,那么 z.equals(x) 也应该返回是“true”
    4. 一致性:如果 x.equals(y) 返回是“true”,只要 x 和 y 内容一直不变,不管你重复 x.equals(y) 多少次,返回都是“true”
    5. 任何情况下,x.equals(null),永远返回是“false”;x.equals(和x不同类型的对象) 永远返回是“false”
    6. 复写 equals 方法的时候一般都需要同时复写 hashCode() 方法。通常参与计算 hashCode 的对象的属性也应该参与到 equals() 中进行计算
  • == 和 equals 的区别

    1. == 既可以比较基本类型也可以比较引用类型。对于基本类型就是比较值,对于引用类型就是比较内存地址
    2. equals 的话,它是属于 java.lang.Object 类里面的方法,如果该方法没有被重写过默认也是 ==String 等类的 equals 方法是被重写过的
  • 编写 equals 方法:

     public boolean equals(Object o) {
         // 第一步:检测地址值是否相等
         if (this == o) return true;
         // 第二步:检测是否为 null,是否是同一类型对象
         if (o == null || !(o instanceof MyDate)) return false;
         // 第三步:强制类型转换
         Person person = (Person) o;
         // 第四步:根据实际要求比较字段,使用 == 比较基本类型字段,使用 Object.equals() 比较对象字段
         return getAge() == person.getAge() && Objects.equals(this.name, person.name);
     }

2.6.3 hashCode() 方法

  1. 在程序运行时,同一个对象多次调用 hashCode() 方法应该返回相同的值,相等的对象必须具有相等的散列码
  2. 当两个对象的 equals() 方法比较返回 true 时,这两个对象的 hashCode() 方法的返回值也应相等;反之则不一定
  3. 对象中用作 equals() 方法比较的 Field,都应该用来计算 hashCode 值
@Override
public int hashCode() {
    return Objects.hash(变量1, 变量2, 变量3);
}

2.7 抽象类与抽象方法

  1. 含有抽象方法的类必须被声明为抽象类
  2. 抽象类不能被实例化。抽象类是用来被继承的,抽象类的子类必须重写父类的抽象方法,并提供方法体。若没有重写全部的抽象方法,仍为抽象类。

2.8 接口

  1. 继承是一个"是不是"的关系,而接口实现则是 "能不能"的关系

  2. 特点:

    • 用 interface 来定义
    • 接口中的所有成员变量都默认是由 public static final 修饰的
    • 接口中的所有抽象方法都默认是由 public abstract 修饰的
    • 接口中没有构造器
    • 接口采用多继承机制
  3. 与继承关系类似,接口与实现类之间存在多态性

  4. 接口和类是并列关系,或者可以理解为一种特殊的类。从本质上讲,接口是一种特殊的抽象类,这种抽象类中只包含常量和方法的定义(JDK7.0及之前),而没有变量和方法的实现

  5. Java 8中关于接口的改进:静态方法默认方法

  6. 如果先在一个接口中将一个方法定义为默认方法,然后又在超类或另一个接口中定义了同样的方法,就会产生一个二义性错误:

    • 超类优先:如果超类提供了一个具体方法,同名并且有相同参数的默认方法会被忽略
    • 接口冲突:如果一个接口提供了一个默认方法,另一个接口提供了一个同名而且参数类型相同的方法,必须覆盖这个方法来解决冲突

3、异常处理机制

3.1 异常概述与异常体系结构

image-20220716211500255

3.2 异常处理机制

3.2.1 try-catch-finally

  1. 捕获异常有关的信息

    • getMessage() 获取异常信息,返回字符串
    • printStackTrace() 获取异常类名和异常信息,以及异常出现在程序中的位置。返回值 void
  2. finally:不论在 try 代码块中是否发生了异常事件,catch 语句是否执行,是否有异常,是否有 return,finally 块中的语句都会被执行

  3. 当 finally 有 return 时,会直接返回。不会再去返回 try 或者 catch 中的返回值,而 finally 没有 return 时,try 和 catch 的 return 语句并不会马上执行,而是执行完 finally 代码块之后再返回 try 和 catch 里面的值

  4. try-catch-finally 语句中含有 return 语句的执行情况

    • try…catch 和 finally 是两个线程,执行顺序是先将 try 中的 return 返回值先保存起来,最后执行 finally,如果 finally 中这个返回值被改变那么最终返回的是 finally 中的返回值

      public static int test1() {
          int x = 1;
          try {
              return x;
          } finally {
              x = 2;
              return x;
          }
      }// 结果为2
    • 当 finally 里中没有 return,但是 finally 中改变了 try 中的返回值, 如果返回值是基本数据类型,最后finally 执行对返回值的改变不起作用

      public static int test1() {
          int x = 1;
          try {
              return x;
          } finally {
              x = 2;
          }
      }// 结果为1
    • 当 finally 里中没有 return,但是 finally 中改变了 try 中的返回值, 如果返回值是引用数据类型,最后finally 执行完后对引用数据类型的属性值的改变起作用的

      public static StringBuffer test3(){
          StringBuffer sb = new StringBuffer("hello");
          try {
              return sb;
          } finally {
              sb.append(" world");
          }
      } // 结果为hello world

3.2.2 throws + 异常类型

public static void main(String[] args) throws Exception {

}

3.2.3 手动抛出异常

public static void main(String[] args) {
    throw new RuntimeException();
}

3.3 用户自定义异常类

  1. 一般地,用户自定义异常类都是 RuntimeException 的子类
  2. 自定义异常类通常需要编写几个重载的构造器
  3. 自定义异常需要提供 serialVersionUID
  4. 自定义的异常通过 throw 抛出

4、常用类

4.1 字符串相关类

4.1.1 String 的特性

  1. Java 程序中的所有字符串字面值(如 "abc" )都作为此类的实例实现

  2. String 是一个 final 类,代表不可变的字符序列

  3. String 对象的字符内容是存储在一个字符数组 value[] 中的

    image-20220218010522370

  4. 通过字符串字面量的方式给一个字符串赋值,此时字符串是保存在方法区(字符串常量池)中的,字符串常量池中相同内容只会有一个

4.1.2 String 对象的创建

  1. 字符串常量存储在字符串常量池,目的是共享;字符串非常量对象存储在堆中
  2. 常量与常量的拼接结果在常量池。且常量池中不会存在相同内容的常量
  3. 只要其中有一个是变量,结果就在堆中

4.1.3 常用方法

点击展开详情

  1. int length():返回字符串的长度
  2. char charAt(int index) :返回某索引处的字符
  3. boolean isEmpty():判断是否是空字符串
  4. String toLowerCase():使用默认语言环境,将 String 中的所有字符转换为小写
  5. String toUpperCase():使用默认语言环境,将 String 中的所有字符转换为大写
  6. String trim():返回字符串的副本,忽略前导空白和尾部空白
  7. boolean equals(Object obj):比较字符串的内容是否相同
  8. boolean equalsIgnoreCase(String anotherString):与 equals 方法类似,忽略大小写
  9. String concat(String str):将指定字符串连接到此字符串的结尾。 等价于用“+”
  10. int compareTo(String anotherString):比较两个字符串的大小
  11. String substring(int beginIndex):返回一个新的字符串,它是此字符串的从 beginIndex 开始截取到最后的一个子字符串。
  12. String substring(int beginIndex, int endIndex) 返回一个新字符串,它是此字符串从 beginIndex 开始截取到 endIndex(不包含)的一个子字符串。
  13. boolean endsWith(String suffix):测试此字符串是否以指定的后缀结束
  14. boolean startsWith(String prefix):测试此字符串是否以指定的前缀开始
  15. boolean startsWith(String prefix, int toffset):测试此字符串从指定索引开始的子字符串是否以指定前缀开始
  16. boolean contains(CharSequence s):当且仅当此字符串包含指定的 char 值序列时,返回 true
  17. int indexOf(String str):返回指定子字符串在此字符串中第一次出现处的索引
  18. int indexOf(String str, int fromIndex):返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始
  19. int lastIndexOf(String str):返回指定子字符串在此字符串中最右边出现处的索引,未找到都是返回-1
  20. int lastIndexOf(String str, int fromIndex):返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索,未找到都是返回-1
  21. String replace(char oldChar, char newChar):返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 得到的。
  22. String replace(CharSequence target, CharSequence replacement):使用指定的字面值替换序列替换此字符串所有匹配字面值目标序列的子字符串。
  23. String replaceAll(String regex, String replacement) : 使用给定的 replacement 替换此字符串所有匹配给定的正则表达式的子字符串。
  24. String replaceFirst(String regex, String replacement) : 使用给定的 replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。
  25. boolean matches(String regex):告知此字符串是否匹配给定的正则表达式。
  26. String[] split(String regex):根据给定正则表达式的匹配拆分此字符串。
  27. String[] split(String regex, int limit):根据匹配给定的正则表达式来拆分此字符串,最多不超过 limit 个,如果超过了,剩下的全部都放到最后一个元素中。

4.1.4 StringBuffer 类与 StringBuilder 类

  • 常用方法

    1. StringBuffer append(xxx):提供了很多的 append() 方法,用于进行字符串拼接
    2. StringBuffer delete(int start, int end):删除指定位置的内容
    3. StringBuffer replace(int start, int end, String str):把 [start,end) 位置替换为 str
    4. StringBuffer insert(int offset, Obj xxx):在指定位置插入 xxx
    5. StringBuffer reverse() :把当前字符序列逆转
    6. public String substring(int start,int end):返回一个从 start 开始到 end 索引结束的左闭右开区间的子字符串
    7. int length():获取当前 StringBuffer 中的字符的个数
    8. int capacity():获取当前 StringBuffer 容量
  • 构造方法

    image-20220617202124549

  • 扩容原理

    StringBuffer 的底层数组结构用的是 char 类型的数组

    所以,当我们使用 StringBuffer 对象的 append(...) 方法追加数据时,如果 char 类型数组的长度无法容纳我们追加的数据,StringBuffer 就会进行扩容

    扩容时会用到 Arrays 类中的 copyOf(...) 方法,每次扩容的容量大小是原来的容量的 2 倍加 2

    点击展开详情

    append(String str) 方法源码:

    public synchronized StringBuffer append(String str) {
        toStringCache = null;
        super.append(str);
        return this;
    }

    父类的 append(String str) 方法源码:

    public AbstractStringBuilder append(String str) {
        if (str == null)
            return appendNull();
        int len = str.length();
        ensureCapacityInternal(count + len);
        str.getChars(0, len, value, count);
        count += len;
        return this;
    }

    这里的 ensureCapacityInternal(count + len) 就是一个扩容相关的方法,变量 count 是一个全局变量,并没有实际的值,变量 len 是我们追加进来的字符串的长度。

    也就是说,我们追加进来的字符串的长度会传递给 ensureCapacityInternal(int minimumCapacity) 方法

    再来看看 ensureCapacityInternal(int minimumCapacity) 方法的源码:

    private void ensureCapacityInternal(int minimumCapacity) {
        // overflow-conscious code
        if (minimumCapacity - value.length > 0) {
            value = Arrays.copyOf(value, newCapacity(minimumCapacity));
        }
    }

    其中,minimumCapacity 指我们追加进来的字符串的长度,value 是一个全局的 char 类型的数组名

    也就说,value.length 指数组的长度,那如果(minimumCapacity - value.length > 0)这个条件成立,也就意味着,char 类型数组的长度无法容纳我们追加的字符串的长度。

    这时,就需要使用 Arrays 类中的 copyOf(char[] original, int newLength) 方法进行扩容:复制指定的数组,截断或使用相应的默认值进行填充新数组长度的值是通过 newCapacity(int minCapacity)方法来计算并返回的值,该方法源码:

    private int newCapacity(int minCapacity) {
        // overflow-conscious code
        int newCapacity = (value.length << 1) + 2;
        if (newCapacity - minCapacity < 0) {
            newCapacity = minCapacity;
        }
        return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
            ? hugeCapacity(minCapacity)
            : newCapacity;
    }

    这个方法会返回一个新的容量大小(即新数组长度),每次扩容的容量大小是原来的容量的 2 倍加 2

4.2 JDK8 之前的日期时间 API

  1. java.lang.System 类:System 类提供的 public static long currentTimeMillis() 用来返回当前时间与 1970 年 1 月 1 日 0 时 0 分 0 秒 之间以毫秒为单位的时间差

  2. java.util.Date 类:

    • Date():使用无参构造器创建的对象可以获取本地当前时间
    • long getTime():返回自 1970 年 1 月 1 日 00:00:00 GMT 以来此 Date 对象表示的毫秒数
  3. java.sql.Date 类:将 java.util.Date 对象转换为 java.sql.Date 对象

    java.sql.Date sqlDate = new java.sql.Date(new java.util.Date().getTime());

  4. java.text.SimpleDateFormat 类:

    • public SimpleDateFormat(String pattern):该构造方法可以用参数 pattern 指定的格式创建一个对象,该对象调用
    • public String format(Date date):方法格式化时间对象 date
    • public Date parse(String source):从给定字符串的开始解析文本,以生成一个日期
  5. java.util.Calendar (日历)类:一个抽象基类,主用用于完成日期字段之间相互操作的功能

4.3 JDK8 中新日期时间API

新的 java.time 中包含了所有关于本地日期(LocalDate)、本地时间(LocalTime)、本地日期时间(LocalDateTime)、时区(ZonedDateTime)和持续时间(Duration)的类。

历史悠久的 Date 类新增了 toInstant() 方法,用于把 Date 转换成新的表示形式。

这些新增的本地化时间日期 API 大大简化了日期时间和本地化的管理。

LocalDateLocalTimeLocalDateTime 类是其中较重要的几个类,它们的实例是不可变的对象。

  1. now() / now(ZoneId zone) :静态方法,根据当前时间创建对象/指定时区的对象
  2. of():静态方法,根据指定日期/时间创建对象
  3. getDayOfMonth() / getDayOfYear():获得月份天数(1-31)/ 获得年份天数(1-366)
  4. getDayOfWeek():获得星期几(返回一个 DayOfWeek 枚举值)
  5. getMonth():获得月份, 返回一个 Month 枚举值
  6. getMonthValue() / getYear():获得月份(1-12) / 获得年份
  7. getHour() / getMinute() / getSecond():获得当前对象对应的小时、分钟、秒
  8. withDayOfMonth() / withDayOfYear() / withMonth() / withYear():将月份天数、年份天数、月份、年份修改为指定的值并返回新的对象
  9. plusDays() / plusWeeks() / plusMonths() / plusYears() / plusHours():向当前对象添加几天、几周、几个月、几年、几小时
  10. minusMonths() / minusWeeks() / minusDays() / minusYears() / minusHours():从当前对象减去几月、几周、几天、几年、几小时

4.4 Java 比较器

4.4.1 自然排序:java.lang.Comparable

  1. Comparable 接口强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序
  2. 实现 Comparable 的类必须实现 compareTo(Object obj) 方法,两个对象通过其方法的返回值来比较大小
    • 如果当前对象 this 大于形参对象 obj,则返回正整数
    • 如果当前对象 this 小于形参对象 obj,则返回负整数
    • 如果当前对象 this 等于形参对象 obj,则返回零
  3. 实现 Comparable 接口的对象列表(和数组)可以通过 Collections.sort() 或 Arrays.sort() 进行自动排序。实现此接口的对象可以用作有序映射中的键或有序集合中的元素,无需指定比较器
  4. 建议最好使自然排序与 equals 一致
class Goods implements  Comparable{
    private String name;
    private double price;

    //指明商品比较大小的方式:按照价格从低到高排序,再按照产品名称从高到低排序
    @Override
    public int compareTo(Object o) {
        if(o instanceof Goods){
            Goods goods = (Goods)o;
            //方式一:
            if(this.price > goods.price){
                return 1;
            }else if(this.price < goods.price){
                return -1;
            }else{
                return 0;
            }
           return Double.compare(this.price,goods.price);
        }
        throw new RuntimeException("传入的数据类型不一致!");
    }
}

public static void main(String[] args) {
    Goods[] all = new Goods[]{...}
    Arrays.sort(all);
    System.out.println(Arrays.toStrign(all));
}

4.4.2 定制排序:java.util.Comparator

  1. 当元素的类型没有实现 java.lang.Comparable 接口而又不方便修改代码,或者实现了 java.lang.Comparable 接口的排序规则不适合当前的操作,那么可以考虑使用 Comparator 的对象来排序,强行对多个对象进行整体排序的比较
  2. 重写 compare(Object o1,Object o2) 方法,比较 o1 和 o2 的大小:如果方法返回正整数,则表示 o1 大于 o2;如果返回 0,表示相等;返回负整数,表示 o1 小于 o2
  3. 可以将 Comparator 传递给 sort 方法(如 Collections.sort() 或 Arrays.sort()),从而允许在排序顺序上实现精确控制。
  4. 可以使用 Comparator 来控制某些数据结构(如有序 set或有序映射)的顺序,或者为那些没有自然顺序的对象 collection 提供排序
public static void main(String[] args) {
    Goods[] arr = new Goods[]{...};
    Arrays.sort(arr, new Comparator() {
        //指明商品比较大小的方式:按照产品名称从低到高排序,再按照价格从高到低排序
        @Override
        public int compare(Object o1, Object o2) {
            if(o1 instanceof Goods && o2 instanceof Goods){
                Goods g1 = (Goods)o1;
                Goods g2 = (Goods)o2;
                if(g1.getName().equals(g2.getName())){
                    return -Double.compare(g1.getPrice(),g2.getPrice());
                }else{
                    return g1.getName().compareTo(g2.getName());
                }
            }
            throw new RuntimeException("输入的数据类型不一致");
        }
    });
}

4.5 枚举类

4.5.1 自定义枚举类

  1. 私有化类的构造器,保证不能在类的外部创建其对象
  2. 在类的内部创建枚举类的实例。声明为:public static final
  3. 对象如果有实例变量,应该声明为 private final,并在构造器中初始化
  4. 使用 enum 定义的枚举类默认继承了 java.lang.Enum 类,因此不能再继承其他类
  5. 枚举类的构造器只能使用 private 权限修饰符
  6. 枚举类的所有实例必须在枚举类中显式列出,,分隔 ,;结尾。列出的实例系统会自动添加 public static final 修饰
  7. 必须在枚举类的第一行声明枚举类对象
public enum SeasonEnum {

    SPRING("春天", "春风又绿江南岸"),
    SUMMER("夏天", "映日荷花别样红"),
    AUTUMN("秋天", "秋水共长天一色"),
    WINTER("冬天", "窗含西岭千秋雪");

    private final String seasonName;
    private final String seasonDesc;

    private SeasonEnum(String seasonName, String seasonDesc) {
        this.seasonName = seasonName;
        this.seasonDesc = seasonDesc;
    }

    public String getSeasonName() {
        return seasonName;
    }

    public String getSeasonDesc() {
        return seasonDesc;
    }
}

4.5.2 Enum 类的主要方法

  1. values() :返回枚举类型的对象数组。该方法可以很方便地遍历所有的枚举值
  2. valueOf(String str):可以把一个字符串转为对应的枚举类对象。要求字符串必须是枚举类对象的“名字”。如不是,会有运行时异常:IllegalArgumentException
public void test02(){
    SeasonEnum value = Enum.valueOf(SeasonEnum.class, "SPRING");
    System.out.println(value); // SPRING

    SeasonEnum[] values = SeasonEnum.values();
    for (SeasonEnum s : values) {
        //...
    }
}

5、注解

5.1 元注解

  1. Retention:只能用于修饰一个 Annotation 定义,用于指定该 Annotation 的生命周期, @Rentention 包含一个 RetentionPolicy 类型的成员变量, 使用 @Rentention 时必须为该 value 成员变量指定值
    • RetentionPolicy.SOURCE:在源文件中有效(即源文件保留),编译器直接丢弃这种策略的注释
    • RetentionPolicy.CLASS:在class文件中有效(即class保留) , 当运行 Java 程序时, JVM 不会保留注解。 这是默认值
    • RetentionPolicy.RUNTIME:在运行时有效(即运行时保留),当运行 Java 程序时,JVM 会保留注释。程序可以通过反射获取该注释
  2. Target:用于指定被修饰的 Annotation 能用于修饰哪些程序元素(如果不使用,表示自定义注解可以用在任何地方)
    • CONSTRUCTOR:用于描述构造器
    • FIELD:用于描述域
    • LOCAL_VARIABLE:用于描述局部变量
    • METHOD:用于描述方法
    • PACKAGE:用于描述包
    • PARAMETER:用于描述参数
    • TYPE:用于描述类、接口(包括注解类型)或 enum 声明
  3. Documented:表示所修饰的注解在被 javadoc 解析时,保留下来
  4. Inherited:被它修饰的 Annotation 将具有继承性

5.2 JDK8 中注解的新特性

  1. JDK1.8之后,关于元注解 @Target 的参数类型 ElementType 枚举值多了两个:
    • ElementType.TYPE_PARAMETER:表示该注解能写在类型变量的声明语句中(如:泛型声明)
    • ElementType.TYPE_USE:表示该注解能写在使用类型的任何语句中
  2. 在 Java 8 之前,注解只能是在声明的地方所使用,Java8 开始,注解可以应用在任何地方

5.3 定义注解

注解的定义和我们常见的类、接口类似,只是注解使用 @interface 来定义,如下定义一个名称为 MyAnnotation的注解:

public @interface MyAnnotation {
    [public] 参数类型 参数名称n() [default 参数默认值];
}

注解中可以定义多个参数,参数的定义有以下特点:

  1. 访问修饰符必须为 public,不写默认为 public
  2. 该元素的类型只能是基本数据类型、String、Class、枚举类型、注解类型(体现了注解的嵌套效果)以及上述类型的一维数组
  3. 该元素的名称一般定义为名词,如果注解中只有一个元素,把名字起为 value(后面使用会带来便利操作)
  4. 参数名称后面的 () 不是定义方法参数的地方,也不能在括号中定义任何参数,仅仅只是一个特殊的语法
  5. default 代表默认值,值必须和第 2 点定义的类型一致
  6. 如果没有默认值,代表后续使用注解时必须给该类型元素赋值

6、泛型

6.1自定义泛型结构

  1. 泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如:<E1,E2,E3>

  2. 泛型类的构造器如下:public GenericClass(){} 而非 public GenericClass<E>(){}

  3. 泛型不同的引用不能相互赋值

    • 尽管在编译时 ArrayList<String>ArrayList<Integer> 是两种类型,但是,在运行时只有一个 ArrayList 被加载到JVM中
  4. 泛型如果不指定,将被擦除,泛型对应的类型均按照 Object 处理,但不等价于 Object

    • 经验:泛型要使用一路都用。要不用,一路都不要用
  5. 如果泛型结构是一个接口或抽象类,则不可创建泛型类的对象

  6. 在类 / 接口上声明的泛型,在本类或本接口中即代表某种类型,可以作为非静态属性的类型、非静态方法的参数类型、返回值类型。但在静态方法中不能使用类的泛型

  7. 不能使用 new E[],但是可以:E[] elements = (E[])new Object[capacity];

  8. 父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型:

    • 子类不保留父类的泛型:按需实现

      class Father<T1, T2> {}
      
      // 没有类型 擦除,等价于 class Son extends Father<Object, Object> {}
      class Son1 extends Father {}
      // 具体类型
      class Son2 extends Father<Integer, String> {}
    • 子类保留父类的泛型:泛型子类

      // 全部保留
      class Son3<A, T1, T2> extends Father<T1, T2> {}
      // 部分保留
      class Son4<A, T2> extends Father<Integer, T2> {}
  9. 泛型方法的格式:[访问权限] <泛型> 返回类型 方法名([泛型标识 参数名称]) 抛出的异常,例如

    public <E> E get(E e) throws Exception {
     return e;
    }

6.2 泛型在继承上的体现

虽然类 A 是类 B 的父类,但是 G<A>G<B> 二者不具备子父类关系,二者是并列关系。类 A 是 类 B 的父类,A<G>B<G> 的父类。

6.3 通配符的使用

  1. 使用类型通配符:?

    • 类 A 是类 B 的父类,G<A>G<B> 是没有关系的,二者共同的父类是:G<?>
    • List<?>List<String>List<Object> 等各种泛型 List 的父类
  2. 读取 List<?> 的对象 list 中的元素时,永远是安全的,因为不管 list 的真实类型是什么,它包含的都是 Object

  3. 注意点:

    • 不能用在泛型方法声明上,返回值类型前面 <> 不能使用 ?
    • 不能用在泛型类的声明上
    • 不能用在创建对象上
  4. 通配符指定上限 extends:使用时指定的类型必须是继承某个类,或者实现某个接口,即 <=

    • <? extend A>:只允许泛型为 A 及 A子类的引用调用
  5. 通配符指定下限 super:使用时指定的类型不能小于操作的类,即 >=

    • <? super B>:只允许泛型为 B 及 B父类的引用调用

7、集合

7.1 集合概述

集合不能直接存储基本数据类型,另外集合也不能直接存储 Java 对象,集合当中存储的是 Java 对象的内存地址

7.2 Collection 接口

image-20220218195838029

  1. Collection 接口是 List、Set 和 Queue 接口的父接口,该接口里定义的方法既可用于操作 Set 集合,也可用于操作 List 和 Queue 集合
  2. JDK 不提供此接口的任何直接实现,而是提供更具体的子接口(如:Set 和 List)实现

7.2.1 接口方法

  1. add(Object obj) / addAll(Collection coll):添加
  2. int size():获取有效元素的个数
  3. void clear():清空集合
  4. boolean isEmpty():是否是空集合
  5. boolean contains(Object obj):是通过元素的 equals() 方法来判断是否是同一个对象
  6. boolean containsAll(Collection c):也是调用元素的 equals() 方法来比较的。拿两个集合的元素挨个比较
  7. boolean remove(Object obj) :通过元素的 equals 方法判断是否是要删除的那个元素。只会删除找到的第一个元素
  8. boolean removeAll(Collection coll):取当前集合的差集
  9. boolean retainAll(Collection c):把交集的结果存在当前集合中,不影响 c
  10. boolean equals(Object obj):集合是否相等
  11. Object[] toArray():转成对象数组
  12. hashCode():获取集合对象的哈希值

7.3 Iterator 迭代器接口

  1. Collection 接口继承了 java.lang.Iterable 接口,该接口有一个 iterator() 方法,那么所有实现了 Collection 接口的集合类都有一个 iterator() 方法,用以返回一个实现了 Iterator 接口的对象
  2. Iterator 仅用于遍历集合
  3. 集合对象每次调用 iterator() 方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前

7.4 List接口

7.4.1 List 接口方法

  1. int indexOf(Object obj):返回 obj 在集合中首次出现的位置
  2. int lastIndexOf(Object obj):返回 obj 在当前集合中末次出现的位置
  3. Object set(int index, Object element):设置指定 index 位置的元素为 element
  4. List subList(int fromIndex, int toIndex):返回从 fromIndex 到 toIndex 位置的子集合

7.4.2 ArrayList

  1. 本质上,ArrayList 是对象引用的一个”变长”数组
  2. 初始化容量10,扩容为原容量的1.5倍
  3. ArrayList 的 JDK1.8 之前与之后的实现区别?
    • JDK1.7:直接创建一个初始容量为 10 的数组,如果此次添加导致底层 elementData 数组容量不够则扩容为原容量的1.5倍,同时将原有数组中的数据复制到新数组中
    • JDK1.8:一开始创建一个长度为 0 的数组,当添加第一个元素时再创建一个始容量为 10 的数组,后续的添加扩容操作和 JDK7 无异
  4. Arrays.asList(…):方法返回的 List 集合

7.4.3 LinkedList

  1. 对于频繁的插入或删除元素的操作,建议使用 LinkedList 类,效率较高

  2. 没有扩容机制也没有初始化容量大小

  3. 双向链表,内部没有声明数组,而是定义了 Node 类型的 first 和 last,用于记录首末元素。同时,定义内部类 Node,作为 LinkedList 中保存数据的基本结构。Node 除了保存数据,还定义了两个变量:

    image-20220218202648235

7.4.4 Vector(淘汰)

  1. Vector 是一个古老的集合,JDK1.0 就有了。大多数操作与 ArrayList 相同,区别之处在于 Vector 是线程安全的
  2. 在各种 list 中,最好把 ArrayList 作为缺省选择。当插入、删除频繁时,使用 LinkedList;Vector 总是比 ArrayList 慢,所以尽量避免使用

7.5 Set 接口

  1. Set 接口是 Collection 的子接口,set 接口没有提供额外的方法
  2. Set 判断两个对象是否相同不是使用 == 运算符,而是根据 equals() 方法
  3. 存储无序的、不可重复的数据
    • 无序性:不等于随机性。存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值决定的
    • 不可重复性:保证添加的元素按照 equals() 判断时,不能返回 true。即:相同的元素只能添加一个

7.5.1 HashSet

  1. HashSet 是 Set 接口的典型实现,大多数时候使用 Set 集合时都使用这个实现类
  2. HashSet 按 Hash 算法来存储集合中的元素,因此具有很好的存取、查找、删除性能
  3. HashSet 具有以下特点:
    • 不能保证元素的排列顺序
    • 不是线程安全的
    • 集合元素可以是 null
  4. 对于存放在 Set 容器中的对象,对应的类一定要重写 equals() 和 hashCode(Object obj) 方法,以实现对象相等规则。即:“相等的对象必须具有相等的散列码”

7.5.2 LinkedHashSet

  1. LinkedHashSet 是 HashSet 的子类
  2. LinkedHashSet 根据元素的 hashCode 值来决定元素的存储位置,但它同时使用双向链表维护元素的次序,这使得元素看起来是以插入顺序保存
  3. LinkedHashSet 插入性能略低于 HashSet,但在迭代访问 Set 里的全部元素时有很好的性能

7.5.3 TreeSet

  1. SortedSet 的子类,TreeSet 可以确保集合元素处于排序状态
  2. TreeSet底层使用红黑树结构存储数据
  3. TreeSet 两种排序方法:自然排序和定制排序。默认情况下,TreeSet 采用自然排序
    • 如果试图把一个对象添加到 TreeSet 时,则该对象的类必须实现 Comparable 接口
    • 当采用定制排序时Set set = new TreeSet(Comparator接口对象);

7.3 Map 接口

image-20220218195928409

7.3.1 Map 接口概述

  1. Map 中的 key 和 value 都可以是任何引用类型的数据
  2. Map 中的 key 用 Set 来存放,不允许重复,即同一个 Map 对象所对应的类,须重写 hashCode() 和 equals() 方法
  3. 添加、删除、修改操作:
    • Object put(Object key, Object value):将指定 key-value 添加到(或修改)当前 map 对象中
    • void putAll(Map m):将 m 中的所有 key-value 对存放到当前 map 中
    • Object remove(Object key):移除指定 key 的 key-value 对,并返回 value
    • void clear():清空当前 map 中的所有数据
  4. 元素查询的操作:
    • Object get(Object key):获取指定 key 对应的 value
    • boolean containsKey(Object key):是否包含指定的 key
    • boolean containsValue(Object value):是否包含指定的 value
    • int size():返回 map 中 key-value 对的个数
    • boolean isEmpty():判断当前 map 是否为空
    • boolean equals(Object obj):判断当前 map 和参数对象 obj 是否相等
  5. 元视图操作的方法:
  • Collection values():返回所有 value 构成的 Collection 集合
  1. 遍历方式:

    • Set<K> keySet() 获取 Map 集合所有的 key 并将其转换成一个 Set 集合。通过获取 Set 集合中的每个 key 的值,去获取每个 key 对应的 value 值

      Set<K> keys = map.keySet();
      Iteraotr<K> iterator = keys.iterator();
      while(iterator.hasNext()) {
          K key = iterator.next();
          V value = map.get(key);
      }
    • Set<Map.Entry<K,V>> entrySet() 将 Map 集合转换成 Set 集合。这种方式效率比较高,因为获取 key 和 value 都是直接从 node 对象中获取的属性值。这种方式比较适合于大数据量

      Set<Map.Entry<K,V>> set = map.entrySet();
      Iterator<Map.Entry<K,V>> iterator = set.iterator();
      while(iterator.hasNext()) {
         Map.Entry<K,V> node = iterator.next();
         K key = node.getKey();
         V value = node.getValue();
      }

7.3.2 HashMap

  1. 允许使用 null 键和 null 值,与 HashSet 一样,不保证映射的顺序(只能有一个键为null)
  2. 所有的 key 构成的集合是 Set:无序的、不可重复的。所以,key 所在的类要重写:equals() 和 hashCode()
  3. 所有的 value 构成的集合是 Collection:无序的、可以重复的
  4. 一个 key-value 构成一个 entry
  5. 所有的 entry 构成的集合是 Set:无序的、不可重复的
  6. HashMap 判断两个 key 相等的标准是:两个 key 通过 equals() 方法返回 true,hashCode 值也相等
  7. HashMap 判断两个 value 相等的标准是:两个 value 通过 equals() 方法返回 true

7.3.3 LinkedHashMap

保证在遍历 Map 元素时可以按照添加的顺序实现遍历

原因:在原有的 HashMap 底层结构基础上,添加了一对指针,指向前一个和后一个元素。对于频繁的遍历操作,此类执行效率高于HashMap。

  1. HashMap 中的内部类: Node

    static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next; 
    }
  2. LinkedHashMap 中的内部类:Entry

    static class Entry<K,V> extends HashMap.Node<K,V> {
        // 能够记录添加的元素的先后顺序
        Entry<K,V> before, after;    
        Entry(int hash, K key, V value, Node<K,V> next) {
         super(hash, key, value, next);
        } 
    }

7.3.4 TreeMap

保证按照添加的 key-value 对进行排序,实现排序遍历

  1. TreeMap 存储 Key-Value 对时,需要根据 key-value 对进行排序。TreeMap 可以保证所有的 Key-Value 对处于有序状态
  2. TreeSet底层使用红黑树结构存储数据
  3. TreeMap 的 Key 的排序:
    • 自然排序:TreeMap 的所有的 Key 必须实现 Comparable 接口,而且所有的 Key 应该是同一个类的对象,否则将会抛出 ClasssCastException
    • 定制排序:创建 TreeMap 时,传入一个 Comparator 对象,该对象负责对 TreeMap 中的所有 key 进行排序。此时不需要 Map 的 Key 实现 Comparable 接口
  4. TreeMap 判断两个 key 相等的标准:两个 key 通过 compareTo() 方法或者 compare() 方法返回0。

7.3.5 Hashtable(淘汰)

  1. Hashtable 是个古老的 Map 实现类,JDK1.0就提供了。不同于 HashMap,Hashtable是线程安全的
  2. Hashtable 实现原理和 HashMap 相同,功能相同。底层都使用哈希表结构,查询速度快,很多情况下可以互用
  3. 与 HashMap 不同,Hashtable 不允许使用 null 作为 key 和 value
  4. 与HashMap一样,Hashtable 也不能保证其中 Key-Value 对的顺序

7.3.6 Properties

  1. Properties 类是 Hashtable 的子类,该对象用于处理属性文件
  2. 由于属性文件里的 key、value 都是字符串类型,所以 Properties 里的 key 和 value 都是字符串类型

7.4 Collections 工具类

  1. 排序操作:
    • static reverse(List):反转 List 中元素的顺序
    • static shuffle(List):对 List 集合元素进行随机排序
    • static sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序
    • static sort(List,Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
    • static swap(List,int,int):将指定 list 集合中的 i 处元素和 j 处元素进行交换
  2. 查找、替换:
    • Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
    • Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素
    • int frequency(Collection,Object):返回指定集合中指定元素的出现次数
    • void copy(List dest,List src):将 src 中的内容复制到 dest 中
    • boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换 List 对象的所有旧值
  3. 同步控制:Collections 类中提供了多个 synchronizedXxx() 方法,该方法可使将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题

8、IO 流

8.1 File 类

8.1.1 File 类的使用

  1. java.io.File 类:文件和文件目录路径的抽象表示形式,与平台无关
  2. File 能新建、删除、重命名文件和目录,但 File 不能访问文件内容本身。如果需要访问文件内容本身,则需要使用输入 / 输出流
  3. 想要在 Java 程序中表示一个真实存在的文件或目录,那么必须有一个 File 对象,但是 Java 程序中的一个 File 对象,可能没有一个真实存在的文件或目录

image-20220220124935816

8.1.2 常用构造器

  1. public File(String pathname):以 pathname 为路径创建 File 对象,可以是绝对路径或者相对路径
  2. public File(String parent, String child):以 parent 为父路径,child 为子路径创建 File 对象
  3. public File(File parent, String child):根据一个父 File 对象和子文件路径创建 File 对象

8.1.3 路径分隔符

  1. 路径分隔符和系统有关:
    • windows 和 DOS 系统默认使用 \ 来表示
    • UNIX 和 URL 使用 / 来表示
  2. public static final String separator:根据操作系统,动态的提供分隔符

8.1.4 常用方法

点击展开详情

  • File 类的获取功能

    1. public String getAbsolutePath():获取绝对路径
    2. public String getPath():获取路径
    3. public String getName() :获取名称
    4. public String getParent():获取上层文件目录路径。若无,返回 null
    5. public long length() :获取文件长度(即:字节数)。不能获取目录的长度
    6. public long lastModified() :获取最后一次的修改时间,毫秒值
    7. public String[] list():获取指定目录下的所有文件或者文件目录的名称数组
    8. public File[] listFiles() :获取指定目录下的所有文件或者文件目录的 File 数组
  • File 类的重命名功能

    1. public boolean renameTo(File dest):把文件重命名为指定的文件路径
  • File 类的判断功能

    1. public boolean isDirectory():判断是否是文件目录
    2. public boolean isFile() :判断是否是文件
    3. public boolean exists() :判断是否存在
    4. public boolean canRead() :判断是否可读
    5. public boolean canWrite() :判断是否可写
    6. public boolean isHidden() :判断是否隐藏
  • File 类的创建功能

    1. public boolean createNewFile() :创建文件。若文件存在,则不创建,返回false

    2. public boolean mkdir() :创建文件目录。如果此文件目录存在,就不创建了。

      如果此文件目录的上层目录不存在,也不创建

    3. public boolean mkdirs() :创建文件目录。如果上层文件目录不存在,一并创建

  • File 类的删除功能

    1. public boolean delete():删除文件或者文件夹(Java 中的删除不走回收站)

8.2 IO 流分类

image-20220618185515331

分类 字节输入流 字节输出流 字符输入流 字符输出流
抽象基类 InputStream OutPutStream Reader Writer
访问文件 FileInputStream FileOutPutStream FileReader FileWriter
访问数组 ByteArrayInputStream ByteArrayOutPutStream CharArrayReader CharArrayWriter
访问管道 PipedInputStream PipedOutPutStream PipedReader PipedWriter
访问字符串 StringReader StringWriter
缓冲流 BufferdInputStream BufferdOutPutStream BufferdReader BufferdWriter
转换流 InputStreamReader OutPutStreamWriter
对象流 ObjectInputStream ObjectOutPutStream
过滤流 FilterInputStream FilterOutPutStream FilterReader FilterWriter
打印流 PrintStream PrintWriter
推回输入流 PushbackInputStream PushbackReader
特殊流 DataInputStream DataOutPutStream

8.3 节点流(文件流)

FileInputStream、FileOutputStream、FileReader、FileWriter

  1. 在写入一个文件时,如果使用构造器 FileOutputStream(file),则目录下有同名文件将被覆盖
  2. 如果使用构造器 FileOutputStream(file,true),则目录下的同名文件不会被覆盖,在文件内容末尾追加内容
  3. 在读取文件时,必须保证该文件已存在,否则报异常
  4. 字节流操作字节,比如:.mp3,.avi,.rmvb,mp4,.jpg,.doc,.ppt
  5. 字符流操作字符,只能操作普通文本文件。最常见的文本文件:.txt,.java,.c,.cpp 等语言的源代码。尤其注意:doc、excel、ppt 这些不是文本文件
  6. 处理文件,如果 new FileInputStream("目录地址") 会报 java.io.FileNotFoundException (拒绝访问)
public void test01() throws IOException {
    FileInputStream fis = new FileInputStream(new File("读取的文件地址"));
    FileOutputStream fos = new FileOutputStream(new File("文件输出路径"));
    byte[]  bytes = new byte[1024 * 1024];
    int readCount = 0;
    while ((readCount = fis.read(bytes)) != -1) {
        System.out.print(new String(bytes, 0, readCount));    // 控制台输出
        fos.write(bytes);
    }
    fos.flush();
    fis.close();
    fos.close();
}

8.4 缓冲流

BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter

  1. 为了提高数据读写的速度,Java API 提供了带缓冲功能的流类,在使用这些流类时,会创建一个内部缓冲区数组,缺省使用 8192 个字节(8Kb)的缓冲区
  2. 缓冲流要“套接”在相应的节点流之上
  3. 关闭流的顺序和打开流的顺序相反。只要关闭最外层流即可,关闭最外层流也会相应关闭内层节点流
public void test02() throws IOException {
    BufferedReader br = new BufferedReader(new FileReader("读取的文件地址"), 1024);
    BufferedWriter bw = new BufferedWriter(new FileWriter(new File("文件输出路径")));
    String s = null;
    while ((s = bufferedReader.readLine()) != null) {
        bufferedWriter.write(s);
    }
    bw.flush();
    br.close();
    bw.close();
}

8.5 转换流

Java API 提供了两个转换流:

  • InputStreamReader:将 InputStream 转换为 Reader

    InputStreamReader reader = new InputStreamReader(new FileInputStream(new File()))
  • OutputStreamWriter:将 Writer 转换为 OutputStream

    OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(new File()))

8.6 标准输入、输出流(了解)

打印流:PrintStreamPrintWriter,实现将基本数据类型的数据格式转化为字符串输出:

  • 提供了一系列重载的 print() 和 println() 方法,用于多种数据类型的输出
  • PrintStream 和 PrintWriter 的输出不会抛出 IOException 异常且有自动 flush 功能
  • PrintStream 打印的所有字符都使用平台的默认字符编码转换为字节。在需要写入字符而不是写入字节的情况下,应该使用 PrintWriter 类
  • System.out 返回的是 PrintStream 的实例
  • System.in 的类型是 InputStream
  • System.out 的类型是PrintStream,其是 OutputStream 的子类 FilterOutputStream 的子类
  • 重定向:通过 System 类的setIn,setOut 方法对默认设备进行改变:
    • public static void setIn(InputStream in)
    • public static void setOut(PrintStream out)
public void test03() throws IOException {
    PrintStream ps = new PrintStream(new FileOutputStream(new File("待读取文件地址")));
    System.setOut(ps);
    PrintStream printStream = System.out;
    printStream.println("hello world!");
}

8.7 对象流

对 java.io.Serializable 接口的理解

实现了 Serializable 接口的对象,可将它们转换成一系列字节,并可在以后完全恢复回原来的样子。这一过程亦可通过网络进行。这意味着序列化机制能自动补偿操作系统间的差异。不必关心数据在不同机器上如何表示,也不必关心字节的顺序或者其他任何细节。

由于大部分作为参数的类如 String、Integer 等都实现了 java.io.Serializable 的接口,也可以利用多态的性质,作为参数使接口更灵活。

用于存储和读取基本数据类型数据或对象的序列化与反序列化

ObjectOutputStream 和 ObjectInputStream 不能序列化 static 和 transient 修饰的成员变量

  1. 对象序列化机制允许把内存中的 Java 对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。当其它程序获取了这种二进制流,就可以恢复成原来的 Java 对象
  2. 序列化的好处在于可将任何实现了Serializable 接口的对象转化为字节数据,使其在保存和传输时可被还原
  3. 在进行反序列化时,JVM 会把传来的字节流中的 serialVersionUID 与本地相应实体类的 serialVersionUID 进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常(InvalidCastException)
  4. 如果某个类的属性不是基本数据类型或 String 类型,而是另一个引用类型,那么这个引用类型必须是可序列化的,否则拥有该类型的 Field(字段) 的类也不能序列化

8.8 BIO、NIO、AIO

8.8.1 各自的概念

  • BIO同步并阻塞,服务实现模式为一个连接对应一个线程,即客户端发送一个连接,服务端要有一个线程来处理。如果连接多了,线程数量不够,就只能等待,即会发生阻塞。
  • NIO同步非阻塞,服务实现模式是一个线程可以处理多个连接,即客户端发送的连接都会注册到多路复用器上,然后进行轮询连接,有 I/O 请求就处理
  • AIO异步非阻塞,引入了异步通道,采用的是 proactor 模式,特点是:有效的请求才启动线程,先有操作系统完成在通知服务端

8.8.2 各自的应用场景

  • BIO:适用连接数目比较小且固定的架构,对服务器要求比较高,并发局限于应用中
  • NIO:适用连接数目多且连接比较短的架构,如:聊天服务器,弹幕系统等,编程比较复杂
  • AIO:适用连接数目多且连接长的架构,如相册服务器

8.8.3 BIO 和 NIO 的区别

  • BIO (Blocking I/O):同步阻塞 I/O 模式,数据的读取写入必须阻塞在一个线程内等待其完成。这里假设一个烧开水的场景,有一排水壶在烧开水,BIO 的工作模式就是, 叫一个线程停留在一个水壶那,直到这个水壶烧开,才去处理下一个水壶。但是实际上线程在等待水壶烧开的时间段什么都没有做。
  • NIO (New I/O):同时支持阻塞与非阻塞模式,但这里我们以其同步非阻塞 I/O 模式来说明,那么什么叫做同步非阻塞?如果还拿烧开水来说,NIO 的做法是叫一个线程不断的轮询每个水壶的状态,看看是否有水壶的状态发生了改变,从而进行下一步的操作。
  • AIO ( Asynchronous I/O):异步非阻塞 I/O 模型。异步非阻塞与同步非阻塞的区别在哪里?异步非阻塞无需一个线程去轮询所有 I/O 操作的状态改变,在相应的状态改变后,系统会通知对应的线程来处理。对应到烧开水中就是,为每个水壶上面装了一个开关,水烧开之后,水壶会自动通知我水烧开了。

END

本文作者:
文章标题:Java 基础知识概述
本文地址:https://www.pendulumye.com/java-foundation/135.html
版权说明:若无注明,本文皆PendulumYe原创,转载请保留文章出处。
最后修改:2022 年 09 月 22 日
千山万水总是情,给个一毛行不行💋