Java基础学习笔记

基于B站的 黑马程序员Java零基础视频教程

  • 主要写出一些与C++不同的地方,以及重点的地方

1. Java 入门

  • IDEA关于类操作的快捷键 alt+回车 alt+insert

  • IDEA中层级结构分类

  • project(项目、工程)

  • module(模块)

  • package(包)

  • class(类)

project(项目、工程)

​ 淘宝、京东、黑马程序员网站都属于一个个项目,IDEA中就是一个个的Project。

module(模块)

​ 在一个项目中,可以存放多个模块,不同的模块可以存放项目中不同的业务功能代码。在黑马程序员的官方网站中,至少包含了以下模块:

  • 论坛模块
  • 报名、咨询模块

为了更好的管理代码,我们会把代码分别放在两个模块中存放。

package(包)

​ 一个模块中又有很多的业务,以黑马程序员官方网站的论坛模块为例,至少包含了以下不同的业务。

  • 发帖
  • 评论

为了把这些业务区分的更加清楚,就会用包来管理这些不同的业务。

class(类)

​ 就是真正写代码的地方。

小结
  • 层级关系

    ​ project - module - package - class

  • 包含数量

    ​ project中可以创建多个module ​ module中可以创建多个package ​ package中可以创建多个class

    ​ 这些结构的划分,是为了方便管理类文件的。

  • 什么叫 JDK 和 JRE

  • float long 类型要加后缀;

  • java的数据类型的区别

    • char是16位,2个字节
    • 隐式数据类型转换的问题
  • 字符串+

  • 字符+

  • 无符号右移 >>> 的概念,高位默认只是0,因为无符号。

  • IDEA 快捷键

    • 5.fori 快捷键写for循环

    • ctrl + D 复制当前行到下一行

    • arr.fori 数组遍历快捷键

    • 使用快捷键Ctrl + Alt + v 自动生成返回值

  • 小驼峰===变量名、方法

  • 大驼峰===类

  • IDEA 查看源码的方法

    • ctrl + n:查找所有的类
    • alt + 7 : 列出所有方法的大纲

2. 数组

  • 数组的 = 赋值是浅拷贝,共享一个内存空间。

  • 注意c++的vector的等于 = 是一个深拷贝。

  • new的对象才在堆里面开辟空间,否则基础数据类型都是在栈区开辟空间。

  • 二维数组

3. 方法

  • 方法的重载:允许方法名相同,参数不同。注意,与返回值无关。

    • 个数不同、类型不同、顺序不同。
  • 只要new了,就和堆有关。

  • 方法传递基本数据类型时,传递的是真实的数据,形参的改变,不影响实际参数的值。

  • 但是传递引用数据类型,传递的是地址值,会改变。尽管还是值传递。

  • 这里说明与c++不同的地方:

    • c++可以传引用;
    • 但java都是传值。
    • 但是,java不同数据类型的存储空间不同,导致即使是传值,传的也是地址,也意味着可更改,这就是最大的区别。

4. 面向对象

4.1 基础概念

  • JavaBean类

  • private 关键修饰词

  • this关键词,注意这不是this指针,和c++的区别。

  • 构造方法——即构造函数。如果写了构造方法,虚拟机不再提供无参构造方法。

  • IDEA有快捷键快速生产javaBean

  • protected作用于继承关系。定义为protected的字段和方法可以被子类访问,以及子类的子类;

  • 包作用域 package 是指一个类允许访问同一个package的没有publicprivate修饰的class,以及没有publicprotectedprivate修饰的字段和方法。只要在同一个包,就可以访问package权限的classfieldmethod

    •  1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      
      package abc;
      
      class Main {
          void foo() {
              // 可以访问package权限的类:
              Hello h = new Hello();
              // 可以调用package权限的方法:
              h.hi();
          }
      }
      
  • 小结

    • Java内建的访问权限包括publicprotectedprivatepackage权限;
    • Java在方法内部定义的变量是局部变量,局部变量的作用域从变量声明开始,到一个块结束;
    • final修饰符不是访问权限,它可以修饰classfieldmethod
    • 一个.java文件只能包含一个public类,但可以包含多个非public类。

4.2 创建对象的内存图

  • 还是以方法区为例子,虽然JDK8之后改了

  • 一个对象的内存图

  • 两个对象的内存图,需要注意的是,第二次创建对象的时候 .class 字节码文件加载进方法区不用再重复了,直接用就行。

  • 两个引用指向同一个对象

  • this 的本质:代表所在方法调用者的地址值。作用是区分局部变量和成员变量。

4.5 修饰符介绍

  • 1、 接口 public interface 的修饰符只有:public。

  • 2、 类的修饰符分为:可访问控制符和非访问控制符两种。

    • 可访问控制符是:公共类修饰符 public 或 default

      • 省略访问修饰符

        具有默认的访问特性,即具有包访问特性,只能被同一个包中的类使用。

      • public访问修饰符

        用于说明类和类的成员的访问权限。这种类叫公有类。在一个文件中只能有一个public类型的类。

    • 非访问控制符有:抽象类修饰符 abstract ;最终类修饰符 final

  • 3、属性的控制修饰符也分为:可访问控制符和非访问控制符两类。

    • 可访问控制符有 4 种:公共访问控制符: public ;私有访问控制符: private ;保护访问控制符: protected ;私有保护访问控制符: private protected

    • 非访问控制符有 4 种:静态域修饰符: static ;最终域修饰符: final ;易失 ( 共享 ) 域修饰符: volatile ;暂时性域修饰符: transient

  • 4、方法的控制修饰符也分为:可访问控制符和非访问控制符两类。

    • 可访问控制符有 4 种:公共访问控制符: public ;私有访问控制符: private ;保护访问控制符: protected ;私有保护访问控制符: private protected

    • 非访问控制符有 5 种:抽象方法控制符: abstract ;静态方法控制符: static ;最终方法控制符: final ;本地方法控制符: native ;同步方法控制符: synchronized

  • Java中的static class是一种特殊的类,它与普通类不同的是,不能被实例化,只能被静态引用。

    static class的主要用途如下:

    1. 用于实现工具类: static class可以包含静态方法和静态变量,方便在程序中复用。例如,一个计算器工具类可以使用static class来存储常用的数学函数和常量。
    2. 用于实现枚举类型: static class可以用于定义枚举类型,使得代码更加清晰易懂。例如,一个人的性别有男、女、双性人三种可能,可以将它们定义为一个static class。
    3. 用于实现常量类: static class可以用于定义常量类,其中包含一些固定的值或常量。例如,天气预报应用程序可以使用static class来存储不同地区的天气数据。
    4. 用于实现单例模式: static class可以用于实现单例模式,避免了重复创建对象的开销。例如,一个数据库连接池可以使用static class来存储连接对象,保证只有一个连接被创建并重复使用。
  • 这里重点说明 java 类属性和方法的的访问修饰符public、protected、default、private的作用。

  • default就是什么都不加。

  •  1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    class 类的名称{
    
       修饰符【public|protected| default |private |static|final】数据类型 成员变量;
    
       修饰符 public|protected| default |private| static|final】返回值的数据类型  方法名称(数据类型 参数1,数据类型 参数2…){
       //方法体
      return表达式
    	}
    
    }
    
  • 访问权限 当前类 同一package 子孙类 其他package
    public
    protected
    default
    private

5. 字符串

5.1 基本概念

  • 以下以 String 为例。

  • 字符串不可变,他们的值在创建之后不能改变。

  • 直接赋值和new出来的对象的区别

    • 直接赋值可以复用堆内存空间;
    • new 出来的对象不会复用堆内存,会浪费空间。
  • 字符串直接赋值的内存模型:

    • 主要用到的是StringTable串池的概念;
  • 使用new String,堆内存不会复用,每new一次,就会创建新的内存空间。

5.2 字符串比较

  • 不同数据类型 == 号比较的是不一样。引用类型比较的是地址值,而不是实际内容。
  • 字符串的比较有自身的API函数

5.3 遍历字符串

  • 无法使用 [] 进行遍历读取。

  • 1
    2
    3
    4
    
    String str = "aabfbbb12344";
    for (int i = 0; i < str.length(); i++) {
        System.out.println(str.charAt(i));
    }
    

5.4 StringBuilder

  • StringBulider 是可变的容器,在字符串拼接过程不会产生无用的字符串。
  • 主要作用是字符串拼接或字符串反转。
  • 虽然StringBulider是一个类,但这是Java已经写好的类,Java底层对其做了特殊处理,因此其println打印StringBulider不是打印其地址值,而是其内容。
  • 一般自定义的对象,打印的是地址值。

5.5 StringJoiner

  • StringJoiner 的作用是在 StringBuilder 之后提出的,目的是减少 StringBuilder 字符串拼接时候的代码复杂度。

5.6 字符串拼接底层原理

    1. 等号右边没有变量,全是字符串的相加 +
  • 和直接赋值的写法一样。即内存都是在串池里面。

    1. 等号右边有变量名称
  • 在JDK8前,一个加号,会在堆内存创建两个对象。最后返回的String也是堆内存的对象。

  • 在JDK8之后,做了一些优化:在拼接之前,会预估最后的字符串长度,创建数组,然后把字符串放进去,最后转为String对象。但最后的对象仍然是堆内存的对象。

    1. 常见面试题目

6. ArrayList 集合

  • 因此,需要注意,集合里面只能存类,基本数据类型需要包装才能放进集合里面。

6.1 成员方法

  • 基本构造,利用泛型确定类型

  • 增删改查

方法名 说明
public boolean add(要添加的元素) 将指定的元素追加到此集合的末尾
public boolean remove(要删除的元素) 删除指定元素,返回值表示是否删除成功
public E remove(int index) 删除指定索引处的元素,返回被删除的元素
public E set(int index, E element) 修改指定索引处的元素,返回被修改的元素
public E get(int index) 返回指定索引处的元素
public int size() 返回集合中的元素的个数

6.2 基本数据类型的包装类

7. 面向对象进阶

7.1 static 静态

  • 1.当 static 修饰成员变量或者成员方法时,该变量称为静态变量,该方法称为静态方法。该类的每个对象都共享同一个类的静态变量和静态方法。任何对象都可以更改该静态变量的值或者访问静态方法。但是不推荐这种方式去访问。因为静态变量或者静态方法直接通过类名访问即可,完全没有必要用对象去访问。

  • 2.无static修饰的成员变量或者成员方法,称为实例变量,实例方法,实例变量和实例方法必须创建类的对象,然后通过对象来访问。

  • 3.static修饰的成员属于类,会存储在静态区,是随着类的加载而加载的,且只加载一次,所以只有一份,节省内存。存储于一块固定的内存区域(静态区),所以,可以直接被类名调用。它优先于对象存在,所以,可以被所有对象共享。

  • 4.无static修饰的成员,是属于对象,对象有多少个,他们就会出现多少份。所以必须由对象调用。

  • 定义格式

    1
    
    修饰符 static 数据类型 变量名 = 初始值;    
    

    举例

    1
    2
    3
    4
    
    public class Student {
        public static String schoolName = "传智播客" // 属于类,只有一份。
        // .....
    }
    

    静态成员变量的访问:

    格式:类名.静态变量

    1
    2
    3
    4
    5
    
    public static void  main(String[] args){
        System.out.println(Student.schoolName); // 传智播客
        Student.schoolName = "黑马程序员";
        System.out.println(Student.schoolName); // 黑马程序员
    }
    
  • 重新认识main函数===静态成员函数

7.2 继承

  • 注意和c++的区别,没有公有继承、私有继承、保护继承的区分。统一都是extends。

  • 关键字==extends

  • Java只支持单继承,不支持多继承,但支持多层继承。

  • 不支持多继承是为了避免c++语言多继承的成员函数二义性问题。

  • 任何一个类,Java虚拟机都会自动继承于Object类(如果自身没有继承的话)

  • 父类的private成员和变量,子类无法访问。public和protect可以访问。

  • 构造方法是不能被子类继承的。无论是public修饰还是private修饰。

  • 成员变量都可以被继承,无论是public修饰还是private修饰。但是private修饰的成员变量无法直接修改,只能通过set和get方法修改。

  • 成员方法,如果能够添加到虚方法表,则可以被继承;否则不能被继承。

    • 非private
    • 非final
    • 非static
  • 继承中成员变量的访问特点——就近原则。注意super的区别。

  • 方法重写。

  • 关于构造方法的调用,默认会自动调用父类的无参构造。如果是要调用父类的有参构造,必须手动使用super。

7.3 多态

  • 应用场景

  • 如何函数的形参是一个类的名字,此时就可以传递这个类的所有的子类对象来实现多态。

    • 有父类引用指向子类对象——即把一个子类对象赋值个父类的类型。
    • 从这里就可以看到和c++的区别,c++的动态多态必须是指针
      • 必须是通过指针调用;
      • 该指针为向上转型upcast: 父类指针指向子类对象,以保证安全。
      • 调用的是虚函数。
    • 可以看出,相比c++,java的多态简单很多。
  • 多态的弊端

    • 不能调用子类的特有的方法,解决方法就是强制转换为子类类型;

7.4 包

  • import 减少了全类名的书写。

7.5 final 关键字

  • 可以修饰类、方法、变量。含义各个不同。

7.6 权限修饰符

7.7 静态代码块

  • 构造代码块的概念与运用。

  • 静态代码块,只执行一次,随着类的加载而加载。

7.8 抽象类

  • 抽象方法子类必须重写,有抽象方法的类必须是抽象类。

  • 定义格式

  • 抽象类的构造函数的作用是:当构建子类对象时,给属性赋值用的。

7.9 接口

  • Java接口是一种定义了一组方法但没有实现的抽象类型。它的作用是提供一种规范,以便不同的类可以按照这个规范进行开发和实现。接口提供了一种统一的方式来处理不同类之间的交互,使得代码更加灵活、可扩展和易于维护。通过接口,我们可以将一个类的功能抽象出来,让其他类去实现这个功能,从而达到解耦的目的。同时,接口也可以帮助我们在运行时检查对象是否实现了某个特定的方法或属性,从而增加了程序的健壮性和可靠性。

  • 注意和抽象类的区别。

  • 接口可以实现多个,和继承不一样。

  • JDK8 新增的接口可定义默认方法,即非抽象方法,子类非必要可以不重写。

  • JDK8 新增的接口可定义静态方法,通过接口名直接调用。

  • JDK9 接口可定义私有方法。

7.10 适配器模式

  • 解决接口与接口实现类之间的矛盾问题。
  • 在实现类和接口中间创建一个抽象适配器类。最终只需要重写一个函数就可以了。适配器每个抽象方法都采用空实现即可。

7.11 内部类

  • 内部类:在类的里面,在定义一个类。

  • 注意和c++的内部类进行区分:

    • 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
      
      #include <iostream>
      using namespace std;
      
      class A {
      public:
        A() : _a(10) {}
      
      private:
        class B {
        private:
          int m_b = 6;
      
        public:
          void show(const A &aa) {
            cout << "_a: " << aa._a << endl;
          } // aa._a此处显示指名外部类对象!!
        };
      
      private:
        int _a;
      
      public:
        B bb;
      };
      
      int main(int argc, char *argv[]) {
        A a;
        a.bb.show(a);
        return 0;
      }
      
      // 输出
      //_a: 10
      
    • 只是内部类比友元类多了一点权限:可以不加类名的访问外部类中的static、枚举成员。其他的都和友元类一样。

    •  1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      
      #include <iostream>
      using namespace std;
      
      class Outer {
      private:
        static int x;
        enum Color { RED, GREEN, BLUE };
      
      public:
        class Inner {
        public:
          void printX() { cout << x << endl; }
      
          void printColor() { cout << Color::RED << endl; }
        };
      };
      
      int Outer::x = 10;
      
      int main() {
        Outer::Inner inner;
        inner.printX();     // 10
        inner.printColor(); // 0
      }
      
  • 成员内部类
  • 成员内部类

  •  1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    
    public class Outer1 {
        private int a = 20;
        class Inner {
            private int a = 40;
    
            public void show() {
                int a = 30;
                System.out.println(a);
                System.out.println(this.a);
                System.out.println(Outer1.this.a);
            }
        }
    }
    
  • 静态内部类
  • 只有是静态的东西,都可以直接用类名点直接获取。

  • 局部内部类
  • 匿名内部类
  • 隐藏了名字的内部类,主要作用是可以简化代码。

8. API

8.1 Math

  • Math 数学库

8.2 System

  • System 库

8.3 Runtime

  • Runtime 库
  • 首先需要getRuntime函数获取对象,然后就可以调用该对象的成员函数了。

8.4 Object

  • 顶级父类 Object 只有无参构造方法。

  • 所有的类都继承于 Object。

  • toString 方法——返回对象的包名、类名、地址

  • 如果相应得到对象的属性值,只需要在继承类重写toString函数就可以了。

  • equals 方法,默认的是比较地址值是否相等。一般需要重写。

  • alt+insert ,IEDA会帮我们自动生成equals的重写方法。

  • clone 方法:默认浅克隆,如果需要深克隆需要重写方法或者使用第三方工具类。

  • 默认重写的是浅克隆

  • 书写细节: 1.重写Object中的clone方法 2.让javabean类实现Cloneable接口 3.创建原对象并调用clone就可以了 User u2 =(User)u1.clone();

  • 深克隆需要自己new内存,返回新的地址。

  •  1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    @Override
    protected Object clone() throws CloneNotSupportedException {
        //调用父类中的clone方法
        //相当于让Java帮我们克隆一个对象,并把克隆之后的对象返回出去。
    
        //先把被克隆对象中的数组获取出来
        int[] data = this.data;
        //创建新的数组
        int[] newData =new int[data.length];
        //拷贝数组中的数据
        for (int i = 0; i < data.length; i++) {
            newData[i] = data[i];
        }
        //调用父类中的方法克隆对象
        User u=(User)super.clone();
        //因为父类中的克隆方法是浅克隆,替换克隆出来对象中的数组地址值
        u.data =newData;
        return u;
    }
    
  • 也可以采用第三方库方便深克隆——gson库

  • 1
    2
    3
    4
    5
    6
    7
    8
    
    //以后一般会用第三方工具进行克隆
    //1.第三方写的代码导入到项目中
    //2.编写代码
    Gson gson =new Gson();
    //把对象变成一个字符串
    String s=gson.toJson(u1);
    //再把字符串变回对象就可以了
    User user = gson.fromJson(s, User.class);
    

8.5 Objects

  • Objects是一个工具类,提供了一些方法去完成一些功能。

  • 有这么一个情景,如果一个类是null,会在运行的时候直接报错,因为空类型是没有方法的,这时候判断相等就会比较麻烦,因为添加了条件判断语句。

  • 但是使用Objects工具类,可以减少代码的书写量。

  • 三个重要的成员方法。

8.6 BigInteger

  • 表示一个大整数。

  • 除了intvalue之外,还有longValue、doublevalue。

  • BigInteger的底层存储为int数组,加一个符合类型int。

8.7 BigDecimal

  • 底层存储使用ascii码表的字符串数组

8.8 正则表达式

  • 在java里面,正则表达式也是使用String体现的。

  • 因为传入的是字符串,因此需要注意转移字符 \ 的使用,对应 \d 这种正则表达式,输入的字符串应该为 \\d

  • 下面的 \x0B 表示垂直tab。

  • ? 0或1

  • * 0或n

  • + 1或n

  • () 表示分组

  • (a|b) 表示a或b,为一组

  • IDEA 里面有一个any rule插件,提供大部分正则表达式

8.9 利用正则爬取数据

  • 本地字符串爬取。

  • Pattern:表示正则表达式 Matcher:文本匹配器,作用按照正则表达式的规则去读取字符串,从头开始读取。 在大串中去找符合匹配规则的子串。

    代码示例:

     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
    69
    70
    71
    
    package com.itheima.a08regexdemo;
    
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    public class RegexDemo6 {
        public static void main(String[] args) {
            /* 有如下文本,请按照要求爬取数据。
                    Java自从95年问世以来,经历了很多版本,目前企业中用的最多的是Java8和Java11,
                    因为这两个是长期支持版本,下一个长期支持版本是Java17,相信在未来不久Java17也会逐渐登上历史舞台
                    要求:找出里面所有的JavaXX
             */
    
            String str = "Java自从95年问世以来,经历了很多版本,目前企业中用的最多的是Java8和Java11," +
                    "因为这两个是长期支持版本,下一个长期支持版本是Java17,相信在未来不久Java17也会逐渐登上历史舞台";
    
    
            //1.获取正则表达式的对象
            Pattern p = Pattern.compile("Java\\d{0,2}");
            //2.获取文本匹配器的对象
            //拿着m去读取str,找符合p规则的子串
            Matcher m = p.matcher(str);
    
            //3.利用循环获取
            while (m.find()) {
                // 得到匹配的字符串
                String s = m.group();
                System.out.println(s);
            }
    
    
        }
    
        private static void method1(String str) {
            //Pattern:表示正则表达式
            //Matcher: 文本匹配器,作用按照正则表达式的规则去读取字符串,从头开始读取。
            //          在大串中去找符合匹配规则的子串。
    
            //获取正则表达式的对象
            Pattern p = Pattern.compile("Java\\d{0,2}");
            //获取文本匹配器的对象
            //m:文本匹配器的对象
            //str:大串
            //p:规则
            //m要在str中找符合p规则的小串
            Matcher m = p.matcher(str);
    
            //拿着文本匹配器从头开始读取,寻找是否有满足规则的子串
            //如果没有,方法返回false
            //如果有,返回true。在底层记录子串的起始索引和结束索引+1
            // 0,4
            boolean b = m.find();
    
            //方法底层会根据find方法记录的索引进行字符串的截取
            // substring(起始索引,结束索引);包头不包尾
            // (0,4)但是不包含4索引
            // 会把截取的小串进行返回。
            String s1 = m.group();
            System.out.println(s1);
    
    
            //第二次在调用find的时候,会继续读取后面的内容
            //读取到第二个满足要求的子串,方法会继续返回true
            //并把第二个子串的起始索引和结束索引+1,进行记录
            b = m.find();
    
            //第二次调用group方法的时候,会根据find方法记录的索引再次截取子串
            String s2 = m.group();
            System.out.println(s2);
        }
    }
    

8.10 按要求爬取

  • 重点是掌握 ?=?:?! 的作用。

    需求:

    ​ 有如下文本,按要求爬取数据。

    ​ Java自从95年问世以来,经历了很多版本,目前企业中用的最多的是Java8和Java11,因为这两个是长期支持版本,下一个长期支持版本是Java17,相信在未来不久Java17也会逐渐登上历史舞台。

    需求1:

    ​ 爬取版本号为8,11.17的Java文本,但是只要Java,不显示版本号。

    需求2:

    ​ 爬取版本号为8,11,17的Java文本。正确爬取结果为:Java8 Java11 Java17 Java17

    需求3:

    ​ 爬取除了版本号为8,11,17的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
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    
    public class RegexDemo9 {
        public static void main(String[] args) {
            /*
                有如下文本,按要求爬取数据。
                    Java自从95年问世以来,经历了很多版本,目前企业中用的最多的是Java8和Java11,
                    因为这两个是长期支持版本,下一个长期支持版本是Java17,相信在未来不久Java17也会逐渐登上历史舞台
    
    
                需求1:爬取版本号为8,11.17的Java文本,但是只要Java,不显示版本号。结果应该为4个Java
                需求2:爬取版本号为8,11,17的Java文本。正确爬取结果为:Java8 Java11 Java17 Java17
                需求3:爬取除了版本号为8,11.17的Java文本,正确结果为:Java
            */
            String s = "Java自从95年问世以来,经历了很多版本,目前企业中用的最多的是Java8和Java11," +
                "因为这两个是长期支持版本,下一个长期支持版本是Java17,相信在未来不久Java17也会逐渐登上历史舞台";
    
            //1.定义正则表达式
            //?理解为前面的数据Java
            //=表示在Java后面要跟随的数据
            //但是在获取的时候,只获取前半部分
            //需求1:
            String regex1 = "((?i)Java)(?=8|11|17)";
            //需求2:
            //:表示添加后面的数据
            String regex2 = "((?i)Java)(8|11|17)";
            String regex3 = "((?i)Java)(?:8|11|17)";
            //需求3:
            //!表示不要后面的数据匹配的那部分        
            String regex4 = "((?i)Java)(?!8|11|17)";
    
            Pattern p = Pattern.compile(regex4);
            Matcher m = p.matcher(s);
            while (m.find()) {
                System.out.println(m.group());
            }
        }
    }
    

8.11 贪婪爬取和非贪婪爬取

  • Java 默认是贪婪爬取,满足条件的都爬取。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    只写+和表示贪婪匹配,如果在+和后面加问号表示非贪婪爬取
    +? 非贪婪匹配
    *? 非贪婪匹配
    贪婪爬取:在爬取数据的时候尽可能的多获取数据
    非贪婪爬取:在爬取数据的时候尽可能的少获取数据
    
    举例:
    如果获取数据:ab+
    贪婪爬取获取结果:abbbbbbbbbbbb
    非贪婪爬取获取结果:ab
    

    代码示例:

     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
    
    public class RegexDemo10 {
        public static void main(String[] args) {
            /*
                只写+和*表示贪婪匹配
    
                +? 非贪婪匹配
                *? 非贪婪匹配
    
                贪婪爬取:在爬取数据的时候尽可能的多获取数据
                非贪婪爬取:在爬取数据的时候尽可能的少获取数据
    
                ab+:
                贪婪爬取:abbbbbbbbbbbb
                非贪婪爬取:ab
            */
            String s = "Java自从95年问世以来,abbbbbbbbbbbbaaaaaaaaaaaaaaaaaa" +
                    "经历了很多版木,目前企业中用的最多的是]ava8和]ava11,因为这两个是长期支持版木。" +
                    "下一个长期支持版本是Java17,相信在未来不久Java17也会逐渐登上历史舞台";
    
            String regex = "ab+";
            Pattern p = Pattern.compile(regex);
            Matcher m = p.matcher(s);
    
            while (m.find()) {
                System.out.println(m.group());
            }
    
    
        }
    }
    

8.12 正则表达式在字符串方法中的使用

  • 一般常用的三种方法

  • replaceAll 方法示例

  • split 方法示例

8.13 捕获分组和非捕获分组

  • 分组就是小括号

  • 以括号为区分。主要组号的区分,以左括号为基准,哪一个先出现哪一个就是第一组,不论是里面外面。

  • 捕获分组
  • 使用 \\x 或者 $x 进行捕获。

  • 注意有一个技巧 \\组号X ——表示把X组的内容再拿出来用一次。

  • 正则内部使用的例子——\\组号

  • 正则外部使用的例子——$组号

  • 非捕获分组
  • 不占用组号,仅仅是把数据括起来。

    • ?=
    • ?:
    • ?!

8.14 JDK7 以前的时间相关类

    1. Date 时间类
    1. SimpleDateFormat 类
  • 格式化

  • 解析parse

  • Calendar 类

8.15 JDK8 新增的时间类

  • ZoneId 时区

Instant 时间戳

  • ZoneDateTime 带时区的时间

  • DateTimeFormatter 时间格式化和解析

  • LocalDate LocalTime LocalDateTime 日历类

  • Duration Period ChronoUnit 时间间隔类

8.16 包装类

  • 在Java里面,万物皆对象。
  • 包装类即基本数据类型对应的引用类型。这是因为集合是不能使用基本数据类型的。

9. 常见算法

9.1 快速排序

  • 这里采用快慢指针找到基准数的位置。一般的方法是双指针。但是相对来说快慢指针的判断添加没那么复杂,可读性好。

  •  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
    
    public class HelloWorld {
    
        public static void main(String[] args) {
            int[] arr = {4, 1, 2, 7, 9, 3, 4, 5, 10, 8};
            quickSort(0, arr.length-1, arr);
            for (int i = 0; i < arr.length; i++) {
                System.out.println(arr[i]);
            }
        }
    
        public static int paritition(int[] arr, int left, int right) {
            int pivot = arr[left];
            int slow = left + 1;
            int fast = slow;
            while (fast <= right) {
                if (arr[fast] < pivot) {
                    int tmp = arr[fast];
                    arr[fast] = arr[slow];
                    arr[slow] = tmp;
                    ++slow;
                }
                ++fast;
            }
            int tmp1 = arr[slow - 1];
            arr[slow - 1] = arr[left];
            arr[left] = tmp1;
            return slow - 1;
        }
    
        public static void quickSort(int left, int right, int[] arr) {
            if (left >= right) return;
            int partIdx = paritition(arr, left, right);
            quickSort(left, partIdx - 1, arr);
            quickSort(partIdx + 1, right, arr);
        }
    }
    
  • 使用双指针,复杂一点,需要判断条件

  •  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
    
    public class HelloWorld {
    
        public static void main(String[] args) {
            int[] arr = {4, 1, 2, 7, 9, 3, 5, 10, 8, 6};
            quickSort(0, arr.length - 1, arr);
            for (int i = 0; i < arr.length; i++) {
                System.out.println(arr[i]);
            }
        }
    
        public static int paritition(int[] arr, int left, int right) {
            int pivot = arr[left];
            int l = left + 1;
            int r = right;
            while (l < r) {
                while (l < r && arr[r] > pivot) {
                    --r;
                }
                while (l < r && arr[l] < pivot) {
                    ++l;
                }
                if (l < r) {
                    int tmp = arr[l];
                    arr[l] = arr[r];
                    arr[r] = tmp;
                    ++l;
                    --r;
                }
            }
    
            // 双指针最后需要判断r,如果确实小于,返回;否则返回前一个
            if (arr[r] < pivot) {
                int tmp = arr[r];
                arr[r] = pivot;
                arr[left] = tmp;
                return r;
            } else {
                r--;
                int tmp = arr[r];
                arr[r] = pivot;
                arr[left] = tmp;
                return r;
            }
        }
    
        public static void quickSort(int left, int right, int[] arr) {
            if (left >= right) return;
            int partIdx = paritition(arr, left, right);
            quickSort(left, partIdx - 1, arr);
            quickSort(partIdx + 1, right, arr);
        }
    }
    
  • 其他常见的排序算法。

9.2 Arrays 类

  • 操作数组的工具类。

  • sort 排序,默认给基本数据类型进行升序排序,底层为快速排序。

  • 1
    2
    3
    4
    5
    6
    7
    8
    
    public static void main(String[] args) {
        int[] arr = {4, 1, 2, 7, 9, 3, 4, 5, 10, 8, 6};
        // 最基本的,没有其他定义规则,只能是基本数据类型,同时是升序
        Arrays.sort(arr);
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }
    }
    
  • 按照规则进行排序,需要使用包装类,且使用匿名内部类。

  •  1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    
    public static void main(String[] args) {
        Integer[] arr = {4, 1, 2, 7, 9, 3, 4, 5, 10, 8, 6};
    
        Arrays.sort(arr, new Comparator<Integer>() {
            @Override
            // o1 待插入;o2 已排序;
            // 正数、0表示放后面;
            // 负数表示放前面
            public int compare(Integer o1, Integer o2) {
                return o1 - o2;
            }
        });
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }
    }
    

9.3 lambda 表达式

  • 作用是简化匿名内部类的代码书写。
  • 注意第一个遍历是待插入的值,第二个是已有的按规则排好序的值。
  • 只能简化函数式接口的匿名内部类的代码书写。注意必须是接口,抽象类是不行的。

10. 集合进阶

10.1 集合体系结构

  • List系列集合:添加的元素是有序的、可重复的、有索引。

  • Set系列集合:添加的元素是无序的、不重复、无索引。

10.2 Collection

  • 迭代器不依赖索引。

    1. 迭代器遍历
  • 但是你实在要删除,可以使用迭代器自身的remove()函数进行删除当前元素,注意应该是会自动修改迭代器的指向的。添加的话暂时不可以。

  • 对比c++的迭代器,一样需要注意在遍历的时候添加、删除元素迭代器失效的问题,c++是可以添加删除的,但是java只能删除,暂时没有添加的操作。下面以vevtor为例子。

    • 遍历的同时添加元素,使用insert函数。insert函数会返回一个迭代器,它指向被插入的元素。这样,在遍历时使用insert函数后,可以更新迭代器的值,以避免迭代器失效的问题。

    •  1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      
      int main() {
        vector<int> v = {1, 2, 3, 4, 5};
        for (vector<int>::iterator iter = v.begin(); iter != v.end();) {
          if (*iter % 2 == 0) {
            iter = v.insert(iter + 1, *iter);
            cout << *iter << endl;
          }
          iter++;
        }
        for (vector<int>::iterator iter = v.begin(); iter != v.end(); iter++) {
          cout << *iter << " ";
        }
        return 0;
      }
      
    • 遍历的同时删除元素,使用erase函数。v.erase(iter)会返回一个新的迭代器,它指向被删除元素的下一个位置。这样,在遍历时使用erase函数后,可以更新迭代器的值,以避免迭代器失效的问题。

    •  1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      
      int main() {
        vector<int> v = {1, 2, 3, 4, 5};
        for (vector<int>::iterator iter = v.begin(); iter != v.end();) {
          if (*iter % 2 == 1) {
            iter = v.erase(iter);
          } else {
            iter++;
          }
        }
        for (vector<int>::iterator iter = v.begin(); iter != v.end(); iter++) {
          cout << *iter << " ";
        }
        return 0;
      }
      
    1. 增强for遍历
    1. lambda 表达式遍历
  • 就是forEach===底层就是采用的普通for

10.3 List

  • List是Collection的一种

  • 在Java中,如果出行了方法的重载,会优先调用实参和形参一致的方法。

  • List的遍历方法==5种。

  • 列表迭代器可以允许在遍历的时候插入元素,这就是最大的区别。以下四个方法是列表迭代器最常用的4个方法。

  • 示例代码

  •  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
    
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("tyc");
        list.add("ttt");
        list.add("ccc");
    
        // 1.迭代器遍历
        Iterator<String> it = list.iterator();
        while (it.hasNext()) {
            String str = it.next();
            System.out.println(str);
        }
    
        // 2. 增强for
        System.out.println("=======================");
        for (String s : list) {
            System.out.println(s);
        }
    
        // 3. lambda 表达式
        System.out.println("=======================");
        list.forEach((String s) -> {
            System.out.println(s);
        });
    
        // 4. 普通for循环
        System.out.println("=======================");
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
    
        // 5. 列表迭代器
        // 相比迭代器,可以在遍历的时候添加元素
        System.out.println("=======================");
        ListIterator<String> stringListIterator = list.listIterator();
        while (stringListIterator.hasNext()) {
            String str1 = stringListIterator.next();
            System.out.println(str1);
            if (str1.equals("tyc")) {
                // 里面修改之后原本的迭代器指向不变,下次不会读取到添加的元素,而是原本的下一个元素
                stringListIterator.add("newadd");
            }
        }
        System.out.println("=======================");
        System.out.println(list);
    
    }
    

10.4 ArrayList 集合底层原理

  • 基本的方法和Collection和List的方法一样。

10.5 LinkedList 集合底层原理

10.6 迭代器的底层原理

  • 迭代器是集合的内部类对象。

  • 如何避免并发修改异常,在使用迭代器或者增强for遍历集合的过程中,不用使用集合的方法去添加或删除元素,而应该使用迭代器自身的方法。

  • 注意和c++的迭代器实现区别很大。

10.7 泛型深入

(1) 概述

  • Java的泛型是伪泛型,即自动帮你强转。编译时会自动转为Object类,使用的时候又会强转为泛型。

(2) 定义

    1. 泛型类
    1. 泛型方法
    1. 泛型接口
  • 重点是泛型接口怎么用的问题。

  • ①实现类给出具体类型,那么就只能固定是这种类型了。

  • ②创建对象再给出具体类型

(3) 泛型的继承和通配符

  • 泛型不具备继承性,但是数据具备继承性。
  • 泛型的通配符 ? ,可以限制泛型的种类。

10.8 平衡二叉树

  • 是一种绝对平衡的二叉查找树。

  • AVL树,左子树和右子树的深度(高度)之差的绝对值不超过1。

  • LL LR RL RR 四种旋转操作。

  • 平衡二叉树(AVL树)

  • 左左:“最低失衡根结点”的左子树的左子树失衡。——一次右旋

  • 左右:“最低失衡根结点”的左子树的右子树失衡。——局部左旋,整体右旋

  • 右左:“最低失衡根结点”的右子树的左子树失衡。——局部右旋,整体左旋

  • 右右:“最低失衡根结点”的右子树的右子树失衡。——一次左旋

10.9 红黑树

  • 自平衡的二叉查找树,不是高度平衡的。

  • 第五条规则是重点,以下是17节点的后代叶节点。

  • 添加节点颜色默认是红色的,因为效率高。

  • 添加节点的规则

  • 叔叔节点是父亲节点的同层次的另外一个节点。

  • 添加节点,可能需要的操作不止一次。

  • 旋转的次数少很多。

10.10 Set

  • 继承于Collection。

10.11 HashSet 集合底层原理

10.12 LinkedHashSet 集合底层原理

  • 和HashSet一样,只是多了链表,记录存储顺序。

  • 注意这里的有序,是指存储和读取的顺序一致,而不是按大小排序。

10.13 TreeSet 集合底层原理

  • 自定义排序规则,需要实现接口Comparable,并重写抽象方法。
  • 下面是直接使用比较器进行排序。
  • 可以进一步使用lambda表达式简化代码。

10.14 Map

  • put方法,既是添加,也是覆盖;如果已经存在,则会覆盖,并返回原来的值;没有的话返回null。

  • (1) 遍历方式1:通过键获取

  • keySet() 函数和 get() 函数的使用。

  • (2) 遍历方式2:通过键值对

  • entrySet() 方法的使用。

  • (3) 遍历方法3:通过lambda表示式遍历

  • forEach方法的使用。

10.15 HashMap集合底层原理

  • 直接使用Map的成员方法就可以了。

10.16 LinkedHashMap 集合底层原理

  • 和 HashMap相比只是多了双向链表确定存储的顺序,以保证有序读取。

10.17 TreeMap 集合底层原理

11. Queue 队列

  • 队列(Queue)是一种经常使用的集合。Queue实际上是实现了一个先进先出(FIFO:First In First Out)的有序表。它和List的区别在于,List可以在任意位置添加和删除元素,而Queue只有两个操作:

    • 把元素添加到队列末尾;
    • 从队列头部取出元素。
  • 在Java的标准库中,队列接口Queue定义了以下几个方法:

    • int size():获取队列长度;
    • boolean add(E)/boolean offer(E):添加元素到队尾;
    • E remove()/E poll():获取队首元素并从队列中删除;
    • E element()/E peek():获取队首元素但并不从队列中删除。
  • 总结

  • throw Exception 返回false/null
    添加元素到队尾 add(E e) boolean offer(E e) ==>false
    取队首元素并删除 E remove() E poll() ==>null
    取队首元素但不删除 E element() E peek() ==>null

12. Stack 栈

  • Stack只有入栈和出栈的操作:
    • 把元素压栈:push(E)
    • 把栈顶的元素“弹出”:pop()
    • 取栈顶元素但不弹出:peek()
  • Java中不推荐使用遗留的Stack类,因为这是继承于Vector,但已经被弃用了,而且逻辑关系应该是has-a,而不是is-a。
  • 在Java中,我们用Deque可以实现Stack的功能:
    • 把元素压栈:push(E)/addFirst(E)
    • 把栈顶的元素“弹出”:pop()/removeFirst()
    • 取栈顶元素但不弹出:peek()/peekFirst()

13. PriorityQueue 优先队列

  • PriorityQueue实现了一个优先队列:从队首获取元素时,总是获取优先级最高的元素。

    PriorityQueue默认按元素比较的顺序排序(必须实现Comparable接口),也可以通过Comparator自定义排序算法(元素就不必实现Comparable接口)。

  • PriorityQueueQueue的区别在于,它的出队顺序与元素的优先级有关,对PriorityQueue调用remove()poll()方法,返回的总是优先级最高的元素。

    要使用PriorityQueue,我们就必须给每个元素定义“优先级”。