什么是枚举

  1. 一枚一枚可以列举出来的,才建议使用枚举类型
  2. 枚举编译之后也是生成class文件
  3. 枚举也是一种引用类型数据,但不继承Object
  4. 枚举中的每一个值都可以看做是常量
  5. 枚举语法
    1. enum 枚举类型名{

枚举值1, 枚举值2(全部大写)
}

  1. public static void main(String[] args) {
  2. System.out.println(divide(10,0) == Result.SUCCESS ? "计算成功" : "计算失败");
  3. }
  4. public static Result divide(int a,int b){
  5. try{
  6. int c = a / b;
  7. return Result.SUCCESS;
  8. }
  9. catch (Exception e){
  10. return Result.FAIL;
  11. }
  12. }
  13. enum Result{
  14. SUCCESS , FAIL
  15. }

枚举类的注意事项

  1. 枚据类无法通过new进行实例化对象的创建,否则编译报错
  2. 当枚举类没有任何构造器的时候,JVM的编译器会提供一个private的无参构造器供其使用,当枚举类含有任何的构造器时,JVM的编译则不会提供
  3. 枚举类的构造器只能使用Private进行修饰,如果没有private关键字,也不是“缺省”,编译器会自动补全
  4. 枚举类的构造器中无法使用super(实参)
  5. 枚举类中枚举对象如果使用无参构造器进行对象的初始化,枚举对象后面的()可以省略不写
  6. 枚举类中的枚举对象必须编写在枚举类的第一行,否则编译报错
  7. 枚举类中的枚举对象隐式被public static final关键字进行修饰,如果显示修饰编译报错
  8. 所有的枚举类都隐式继承Enum类
  9. 所有的枚举类无法继承其他类,因为唯一的单继承机会给了Enum类
  10. 所有的枚举类可以实现多个接口
  11. 所有的枚举类不可以被其他类进行继承

    1. enum Week{
    2. MONDAY(1,"星期一"),
    3. TUESDAY(2,"星期二"),
    4. WEDNESDAY(3,"星期三"),
    5. THURSDAY(4,"星期四"),
    6. FRIDAY(5,"星期五"),
    7. SATURDAY(6,"星期六"),
    8. SUNDAY(7,"星期日");
    9. private int number;
    10. private String decription;
    11. private Week(int number, String decription) {
    12. this.number = number;
    13. this.decription = decription;
    14. }
    15. public static Week getByNumber(int number){
    16. switch(number){
    17. case 1:
    18. return MONDAY;
    19. case 2:
    20. return TUESDAY;
    21. case 3:
    22. return WEDNESDAY;
    23. case 4:
    24. return THURSDAY;
    25. case 5:
    26. return FRIDAY;
    27. case 6:
    28. return SATURDAY;
    29. case 7:
    30. return SUNDAY;

    异常

    什么是异常?

  • 异常在Java中以类的形式存在,可以实例化异常类
  • 当程序出现异常时,JVM底层会实例化异常对象,并把异常信息打印在控制台上
  • 异常的原始父类是Throwable
  • Exception类的直接子类(RuntimeException类及其子类除外),都叫做编译时异常(受检异常,受控异常)
    • 编译时异常表示必须在编写程序的时候预先对这种异常进行处理,如果不处理编译器报错
  • RuntimeException以及他的子类,都属于运行时异常(非受检异常,非受控异常)
    • 运行时异常表示在编写程序阶段,程序员可以预先处理,也可以不管

异常都是发生在运行阶段,编译阶段异常是不会发生的,

  • 只有程序运行时才可以new对象
  • 异常的发生就是new异常对象

    编译时异常因什么得名

  • 因为编译时异常必须在编译阶段预先处理,如果不处理编译器报错,因此得名

    编译时异常和运行时异常有什么区别

  1. 编译时异常一般发生的概率比较高
    1. 举个例子

你看到外面下雨了,出门之前会想到,如果不打伞就可能大概率生病,(生病也是一种异常)
“拿一把伞”就是对“生病异常”发生之前的一种处理方式
对于一些发生概率较高的异常,需要在运行之前对其进行预处理
虽然你哪了雨伞,但是你得出去才需要打伞 (异常只有运行时会发生)

  1. 运行时异常一般发生的概率比较低
    1. 举个例子

小明走在大街上,可能会被天上的飞机轮子砸到,(被砸到也是一种异常)
但是这种异常发生概率较低
在出门之前你没必要提前对这种发生概率较低的异常进行预处理
如果你预处理这种异常,你将活得很累

异常类父类UML图
QQ截图20220503164051.png

处理异常的两种方式

  1. 第一种方式——异常声明处理:
    1. 在方法声明的位置上使用throws关键字,谁调用这个方法,我就抛给谁,抛给调用者来处理
  2. 第二种方式——异常捕获处理:
    1. 使用try..catch语句对异常进行捕捉

一般main 方法不建议使用throws,因为这个异常如果真的发生了,一定会抛给JVM,JVM只有终止

关键字 throw 和 throws 区别

  1. throw关键字:
    1. 含义:抛出指定的异常对象
    2. 格式:throw new 异常类名(实参);
  2. throws关键字:
    1. 含义:处理异常的方式之一,抛出异常,将异常抛给调用者来处理
    2. 格式:修饰符 返回类型 方法名() throws 异常类名1,异常类名2 {}

      异常代码执行

      ```java public static void main(String[] args) { System.out.println(“main begin”); try { m1(); System.out.println(“文件输入成功”); } catch (FileNotFoundException e) { e.printStackTrace(); System.out.println(“文件输入失败”); } System.out.println(“main over”); }

public static void m1() throws FileNotFoundException { System.out.println(“m1 begin”); m2(); System.out.println(“m1 over”); } public static void m2() throws FileNotFoundException { System.out.println(“m2 begin”); m3(); System.out.println(“m2 over”); } public static void m3() throws FileNotFoundException { System.out.println(“m3 begin”); new FileInputStream(“F:/新建文件夹 (2)新建文本文档.txt”); System.out.println(“m3 over”); }

  1. 代码执行结果main begin m1 begin m2 begin m3 begin 文件输入失败 main over
  2. <a name="UZgyO"></a>
  3. ## 异常代码执行
  4. 1. 执行方法时,代码出现异常,只要代码没有捕捉,异常后续的代码不会执行
  5. 1. try语句块的某一行代码出现异常,该行后面的代码不会执行
  6. <a name="bhX2l"></a>
  7. ## 处理编译时异常,应该上报还是捕捉,怎么选?
  8. - 如果希望调用者来处理,选择throws上报
  9. <a name="Bb3Rp"></a>
  10. ## 异常对象有两个重要的方法
  11. 1. 获取异常简单的描述信息:
  12. 1. String msg = exception.getMessage();
  13. 2. 打印异常追踪的堆栈信息:
  14. 1. exception.printStackTrace();
  15. <a name="fmDhH"></a>
  16. ## try..catch 中的finally子句
  17. 1. finally子句中的代码是最后执行的,并且是一定会执行的(只有结束jvm才不会执行finally语句)
  18. 1. finally必须必须和try一起出现,不能单独编写
  19. 1. 通常在finally语句块中完成资源的释放,因为finally中的代码比较有保障,即使try语句的代码出现异常,finally也会正常执行
  20. 1. try中有return,会先将值暂存,无论finally语句中对该值做什么处理,最终返回的都是try语句中的暂存值
  21. 1. **当tryfinally语句中均有return语句,会忽略try中的return**
  22. ```java
  23. public static void main(String[] args) {
  24. FileInputStream fis = null;
  25. try {
  26. fis = new FileInputStream("E:/sasa");
  27. String s = null;
  28. s.toString();
  29. System.out.println("hello word");
  30. } catch(FileNotFoundException e){
  31. e.printStackTrace();
  32. } catch (IOException e) {
  33. e.printStackTrace();
  34. }finally {
  35. if (fis != null){
  36. try {
  37. fis.close();
  38. } catch (IOException e) {
  39. e.printStackTrace();
  40. }
  41. }
  42. }
  43. }

try…catch… 可以联合使用

try {
    System.out.println("try...");
    return;
//  System.out.println("sadasd");
}finally {
    System.out.println("finally...");
}
//以上代码执行顺序:
//   先执行try
//  在执行 finally
//最后执行return(return语句只要执行方法必然结束)

自定义异常

  1. 写一个类继承Exception或者RuntimeException
  2. 提供两个构造方法,一个无参数的,一个带有String参数的
    public class MyException extends Exception{
     public MyException(){}
     public MyException(String s){}
    }
    public class Test throws MyException{
     throw new MyException("sasas");
    }
    

    集合

  • 集合实际上就是一个容器,可以容纳其他类型的数据
  • 集合不能直接存储基本数据类型的数据,另外集合也不能直接存储java对象,集合当中存储的都是Java对象的内存地址(或者说集合存储的都是引用)
  • 每一个不同的集合,底层都会对应不同的数据结构,往不同的集合中存储元素,等于将数据放到了不同的数据结构中(你使用了不同的集合就是使用了不同的数据结构)
  • 所有的集合类和集合接口都在java.util包下

    在Java中集合分为两大类

  1. 一类是单个方式存储元素:
    1. 单个方式存储元素,这一类集合超级父接口:java.util.Collection

Collection.png

  1. 一类是以键值对的方式存储元素:
    1. 以键值对的方式存储元素,这一类集合中超级父类接口:java.util.Map

Map.png

集合两大类总结

  1. ArrayList:底层是数组
  2. LinkedList:底层是双向链表
  3. Vector:底层是数组,线程安全的,效率较低,使用较少
  4. HashSet:底层是HashMap,放到HashSet集合中的元素等同于放到HashMap集合key部分
  5. TreeSet:底层是TreeMap,放到TreeSet集合中的元素等同于放到TreeMap集合key部分
  6. HashMap:底层是哈希表
  7. HashTable:底层是哈希表,只不过线程安全的,效率较低,使用较少
  8. Properties:是线程安全的,并且key和value只能存储字符串String
  9. TreeMap:底层是二叉树,TreeMap集合中的key可以自动按照大小顺序排序

    集合存储元素的特点

  10. List集合存储元的特点

    1. 有序可重复
    2. 有序:存进去的顺序和取出的顺序相同,每一个元素都有下标
    3. 可重复:存进去1,可以再存储一个1
  11. Set(Map)集合元素的特点
    1. 无序不可重复
    2. 无序:存进去的顺序和取出的顺序不一定相同,另外Set集合中元素没有下标
    3. 不可重复:存进去1,就不能再存1了
  12. SortedSet(SortedMap)集合存储元素特点·
    1. 首先是无序不可重复的,但是SortedSet集合中的元素是可排序的
    2. 无序:存进去的顺序和取出的顺序不一定相同,另外Set集合中元素没有下标
    3. 不可重复:存进去1,不能在存储1了
    4. 可排序:可以按照大小顺序进行排列

Map集合的key就是一个Set集合,在Set集合中放数据,实际上放到了Map集合的key部分

Collection集合

关于java.util.Collection中的常用方法

  1. Collection中能存放什么元素?
    1. 没有使用“泛型”之前,Collection中可以存储Object的所有子类型
    2. 使用了“泛型”之后,Collection中只能存储某个具体类型
  2. Collection中常用的方法

    1. boolean add(E e) 向集合中添加元素
    2. int size() 获取集合中元素的个数
    3. void clear() 清空集合
    4. boolean contains(Object e) 判断当前集合中是否包含元素
    5. boolean remove(Object e) 删除集合中的某个元素
    6. boolean isEmpty() 判断集合是否为空
    7. Object[] toArray() 调用这个方法可以把集合转换成数组
      public static void main(String[] args) {
      //多态
      Collection c = new ArrayList();
      //往集合中添加元素
      c.add(200);//自动装箱,(Java5的新特性),实际上是放进去一个对象的内存地址,Integer x = new Integer(200)
      c.add(3.14);//存进去什么类型,取出的时候还是什么类型,
                //只不过这里println会调用toString()方法
      c.add(new Object());
      c.add(new Army());
      c.add(true);//自动装箱
      //获取集合中的元素个数
      System.out.println("集合中元素个数是:" + c.size());
      c.clear();
      System.out.println("集合中元素个数是:" + c.size());
      c.add("绿巨人");
      System.out.println(c.contains("绿巨人"));//true
      System.out.println(c.contains("绿巨"));//false
      //删除集合中某个元素
      System.out.println(c.remove("绿巨"));//false
      System.out.println(c.add(123));//true
      System.out.println(c.isEmpty());//false
      c.clear();
      System.out.println(c.isEmpty());//true
      c.add(200);
      c.add(3.14);
      c.add(new Object());
      c.add(new Army());
      c.add(true);
      Object[] objs = c.toArray();
      for (int i = 0; i < objs.length; i++) {
        System.out.println(objs[i]);
      }
      
  3. 迭代、遍历集合 的方法

    1. 第一步:获取集合对象的迭代器对象Iterator
    2. 第二步:通过以上获取的迭代器对象开始迭代/遍历集合
    3. Iterator对象中的方法:
      1. boolean hasNext() 如果仍有元素可以迭代。则返回true
      2. Object next()返回迭代的下一个元素

遍历集合时,一次循环只能调用一次iterator()方法
迭代器对象只能进行唯一的一轮迭代器,如果多轮迭代,需要每轮重新获取迭代器对象,否则发生没有元素异常NoSuchElementException
调用迭代器以后,不可以再对集合进行操作,否则会出现异常 java.util.ConcurrentModificationException
在迭代集合元素过程中,不能调用集合对象的remove方法,否则java.util.ConcurrentModificationException

Iterator it = c.iterator();
while(it.hasNext()){
    Object obj = it.next();
    //调用迭代器的remove方法,删除的一定是迭代器指向的额当前元素
    it.remove();  
    System.out.println(obj);
}

深入集合Collection的Contains方法

  • 源码:o.equals(elementData[i]) String类重写了equals()方法

    public static void main(String[] args) {
      Collection c = new ArrayList();
      String s1 = new String("abc");
      c.add(s1);
      String s2 = new String("def");
      String x = new String("abc");
      System.out.println(c.contains(x));  //true
    }
    

    集合使用contains()方法比较内容时,类要重写equals()方法

    深入集合Collection的remove方法

    public static void main(String[] args) {
      Collection c = new ArrayList();
      User u1 = new User("lisi");
      c.add(u1);
      User u2 = new User("lisi");
      c.remove(u2);
      System.out.println(c.size());
    }
    class User{
    private String name;
    public User(){}
    public User(String name){
      this.name = name;
    }
    //重写equals()方法
    public boolean equals(Object o){
      if (null == o || !(o instanceof User)) return false;
      if (this == o) return true;
      User u = (User) o;
      return u.name == this.name;
    }
    
  • remove在调用时,底层也会调用equals()方法,需要类重写equals方法

    List接口特有的方法

  • void add(int index, E element);

  • E get(int index)
  • int indexof(Object o)
  • int lastIndexOf(Object o)
  • E remove(int index)
  • E set(int index, E element)
    public static void main(String[] args) {
      List myList = new ArrayList();
      myList.add(1);
      myList.add(2);
      myList.add(1);
      myList.add(3);
      myList.add(4);
      myList.add(1,'a');
     /* Iterator it =myList.iterator();
      while(it.hasNext()){
          System.out.println(it.next());
      }*/
      //List集合特有的遍历方法
      for (int i = 0; i < myList.size(); i++) {
          System.out.println(myList.get(i));
      }
      //获取指定对象第一次出现的索引
      System.out.println(myList.indexOf('a'));//1
      //获取指定对象最后一次出现的索引
      System.out.println(myList.lastIndexOf(1));//3
      //删除指定元素
      myList.remove(1);
      //修改指定位置元素
      myList.set(2,'b');
    }
    

    ArrayList集合

  1. 默认初始化容量10(底层先创建了一个长度为0的数组,当添加第一个元素的时候,初始化容量为10)
  2. 集合底层是一个Object[] 数组
  3. 构造方法:
    1. new ArrayList();
    2. new ArrayList(20);
  4. ArrayList集合的扩容是原容量的1.5倍
  5. ArrayLIst是非线程安全的

    怎样将一个线程不安全的ArrayList集合转换成线程安全的

  6. java.util.Collection 是集合接口

  7. java.util.Collections 是集合工具类
  8. Collections.synchronizedList(myList)

    LinkedList集合

    单向链表数据结构

  9. 链表数据结构的基本单元Node

  10. 单向链表中任何一个节点Node中都有两个属性:
    1. 第一是存储数据
    2. 第二是存储下一节点的内存地址

005-链表(单向链表).png

链表的有点和缺点

  1. 链表优点:随机增删效率较高(因为链表上的元素在空间存储上内存地址不连续,所以增删元素不会有大量元素位移)
  2. 链表缺点:查询效率较低,每一次查找某个元素的时候需要从头节点开始往下遍历(不能通过数学表达式计算被查找元素的内存地址)

注意:ArrayLIst之所以检索效率比较高,不是单纯的因为下标的原因,是因为底层数组发挥的作用
LinkedList集合照样有下标,但是检索/查找某个元素的时候效率比较低,因为只能从头节点开始一个一个遍历

双向链表

006-双向链表.png

Vector

  1. 底层也是一个数组,是一个有序的集合
  2. 初始化容量也是10
  3. 扩容之后是原容量的2倍
  4. Vector集合可以存储null元素,在使用Vector集合元素之前,先针对元素进行非空校验,防止空指针异常
  5. Vector所有方法是线程同步的,都带有synchronized关键字,是线程安全的,效率比较低,使用较少

ArrayList集合和Vector集合的区别?

  1. 线程安全性:
    1. ArrayLIst集合:ArrayList集合是线程不安全的,只适用于单线程程序
    2. Vector集合:Vector集合是线程安全的,只是用于多线程程序,在单线程中使用效率较低
  2. 无参构造器底层的初始化容量:
    1. ArrayList集合:
      1. JDk6.0以前:初始化容量:10
      2. JDK7.0以后:初始化容量:0
    2. Vector集合:初始化容量:10
  3. 底层数组影响扩容的因素不同
    1. ArrayList集合:是否为第一次扩容
    2. Vector集合:增量参数是否大于0
  4. 底层数组的扩容规则不同

    1. ArrayList集合
      1. JDK6.0以前 扩容原理:(原来数组长度 * 3)/ 2 + 1
      2. JDK7.0以后
        1. 第一次添加元素时:扩容原理 10
        2. 后续添加元素时:原来数组长度 + (原来数组长度 >> 1)
    2. Vector集合
      1. 当增量参数 > 0 : 原来数组长度 + 增量参数
      2. 当增量参数 <= 0: 原来数组长度 + 原来数组长度

        泛型

  5. 集合使用泛型来减少向下转型的操作

  6. <>里填类名称,表示集合只能存储这样类型的数据 ```java class Test{ public E dosome(){} public static void main(String[] args){
     //使用泛型
     Test<String> test = new Test<>;
     //只能返回String类型的数据
     String s = test.dosome;
    
    } }
<a name="e3k2H"></a>
## foreach(增强for循环)

1. 对数组遍历:
   1. for(元素类型 变量名 :数组){

System.out.println(变量名);<br />}
```java
for (int i: arr) {
     System.out.println(i);
}
  1. 对集合遍历
    1. for(元素类型 变量名 :集合){

System.out.println(变量名);
}

for(String s: strList){
    System.out.println(s);
}

Map集合

  1. Map和Collection没有继承关系
  2. Map集合以key和value的方式存储数据:键值对

    1. key和value都是引用数据类型
    2. key和value都是存储对象的内存地址
    3. key起到主导的地位,value是key的一个附属品

      Map集合中常用的方法

  3. V put(K key, V value) 向Map集合中添加键值对

  4. V get(Object key) 通过key来获取value
  5. void clear() 清空Map集合
  6. boolean containsKey(Object key) 判断Map中是否含有某个value,底层调用equals方法
  7. boolean containsValue(Object value) 判断Map中是否包含某个value,底层调用equals方法
  8. boolean isEmpty() 判断Map集合中元素个数是否为0
  9. Set keySet() 获取Map集合所有的key(所有的键是一个set集合)
  10. V remove(Object key) 通过key删除键值对
  11. int size() 获取Map集合中键值对的个数
  12. Collection values() 获取Map集合中所有的value ,返回一个Collection
  13. Set> entrySet() 将Map集合转换成Set集合

遍历Map集合

  1. 第一种遍历方法

    //第一种遍历方法,先获取Map集合中所有的key,再用for循环
    Set<Integer> keys = map.keySet();
    Iterator<Integer> it = keys.iterator();
    while(it.hasNext()){
     Integer i = it.next();
     System.out.println(i + "=" + map.get(i));
    }
    
  2. 第二种遍历方法

    //第二种遍历方法,使用Map接口中的forEach()默认方法
    Map<Integer, String> map = new HashMap<>();
    //遍历Map集合
    map.forEach(new BiConsumer<Integer,String> (){
     //重写BiConsumer中的accept方法
     @Override
     public void accept(String key , String value){
         System.out.println(key + "=" + value);
     }
    });
    
  3. 第三种遍历方法

    //第三种遍历方法,先使用entrySet()方法,再用增强for循环
    //这种方式效率比较高,因为获取的key和value都是直接从node对象中获取的属性值
    //这种方式适合于大数据量
    Set<Map.Entry<Integer, String>> set = map.entrySet();
    for(Map.Entry<Integer, String> s : set){
             System.out.println(s.getKey() + "=" + s.getValue());
         }
    

    HashMap集合

  4. HashMap集合底层是一个哈希表/散列表的数据结构

  5. 哈希表是一个数组和单向链表的结合体,哈希表将以上两种数据结构融合在一起,充分发挥他们各自的优点
    1. 一维数组,这个数组中的每一个元素都是一个单向链表
    2. 如果单向链表长度超过8,单向列表会转换成红黑树
  6. HashMap集合底层源代码

    public class HashMap{
     //HashMap底层实际上就是一个数组
     Node<K,V>[] table;
     //静态内部类HashMap.Node
     static class Node<k,V>{
         final int hash;//哈希值(哈希值是key的hashCode()方法的执行结果)
         final K key;//存储到Map集合中的那个key
         V value;//存储到Map集合中的那个value
         Node<K,V> next;//下一个节点的内存地址
     }
    }
    
  7. HashMap集合的key部分特点:无序不可重复

  8. 放在HashMap集合key部分的元素以及放在HashSet集合中的元素,需要同时重写hashCode()+equals()方法
  9. HashMap集合的默认初始化容量是16,默认加载因子是0.75(当HashMap集合底层数组的容量达到75%的时候,数组开始扩容)

重点:HashMap集合初始化容量必须是2的幂,这也是官方推荐的,这是因为要达到散列均匀,提高存取效率

  1. 向Map集合中存、取,都是先调用key的hashCode方法,然后再调用equals方法(equals方法有可能调用有可能不调用)d71d0c43233e819afea548bf72c0793.jpg5ef9bfeea38251081d94cbf01b439b8.jpg
  2. HashMap集合的key和value都可以为null

HashMap集合底层数组的初始容量、初始加载因子和扩容规则

初始容量和初始加载因子是多少取决于创建对象时的构造器

  1. HashMap()
    1. 初始容量:创建对象时没有进行初始化操作,会在第一次添加元素时进行初始化操作,初始容量为16
    2. 初始加载因子:0.75
  2. HashMap(int initialCapacity)
    1. 初始容量:自定义
    2. 初始加载因子:0.75
  3. HashMap(int initialCapacity , float loadFactor)
    1. 初始容量:自定义
    2. 初始加载因子:自定义
  4. HashMap(Map<? extends K , ? extends V> m)
    1. 初始容量:参数集合的长度
    2. 加载因子:参数集合长度的加载因子
  5. 第一次添加元素时的扩容规则:16
  6. 后续添加元素时的扩容规则:原来底层数组的长度 << 1 (原来数组长度的2 倍)

    底层数组中链表对象和红黑树对象的转换规则

  7. 链表对象进行红黑树对象的转换

    1. 链表对象长度达到8时(链表对象达到8,但是数组长度没达到64,添加元素会将链表对象一部分元素转移到另一个数组中)
    2. 底层数组长度达到64时
  8. 红黑树对象进行链表对象的转换
    1. 红黑树结构中结点对象的数量降至6个

HashMap JDK7.0和JDK8.0的区别

  1. 底层数组的桶元素内容不同
    1. JDK7.0(包含)以前:null,链表对象
    2. JDK8.0(包含)以后:null,链表对象,红黑树对象
  2. 底层数组的数据类型不同
    1. JDK7.0(包含)以前:Entry[]
    2. JDK8.0(包含)以后:Node[]
  3. 无参构造器底层数组的初始容量不同:
    1. JDK7.0(包含)以前:16
    2. JDK8.0(包含)以后:创建对象时没有进行初始化操作,会在第一次添加元素时进行初始化操作,初始容量为16
  4. hash算法不同:
    1. JDK7.0(包含)以前:
      h ^= k.hashCode();
      h ^= (h >>> 20) ^ (h >>> 12);
      h ^ (h >>> 7) ^ (h >>> 4);
    2. JDK8.0(包含)以后:
      (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16)
  5. 底层数组的扩容规则:

    1. JDK7.0(包含)以前: 2 * 原来底层数组长度
    2. JDK8.0(包含)以后: 原来底层数组长度 << 1

      Properties集合

  6. Properties是一个Map集合,继承Hashtable,Properties的key和value都是String类型

  7. Properties被称为属性类对象
  8. Properties是线程安全的

    public static void main(String[] args) {
     Properties pp = new Properties();
     pp.setProperty("1","zhangsan");
     pp.setProperty("2","lisi");
     System.out.println(pp.getProperty("1"));
    }
    

    TreeMap集合

  9. TreeSet集合底层实际上是一个TreeMap

  10. TreeMap集合底层是一个二叉树
  11. 放到TreeSet集合中的元素等同于放到TreeMap集合key部分了
  12. TreeSet集合中的元素,无序不可重复,但是可以按照元素的大小顺序进行排序,称为:可排序集合

    放到TreeSet/TreeMap集合部分的key元素必须排序

  13. 因为TreeMap集合可以自动排序,所以必须实现java.lang.Comparable接口,不然会出现异常

    public class TreeSet01 {
     public static void main(String[] args) {
         User u1 = new User(2,"zhangsan");
         User u2 = new User(2,"lisi");
         User u3 = new User(4,"wangwu");
         TreeSet<User> ts = new TreeSet<>();
         ts.add(u1);
         ts.add(u2);
         ts.add(u3);
        for(User u : ts){
            System.out.println(u);
        }
     }
    }
    class User implements Comparable<User>{
     int age;
     String name;
     public User(int age, String name){
         this.age = age;
         this.name = name;
     }
     /*
         compareTo方法的返回值很重要:
         返回0表示相同,value会覆盖
         返回>0,会继续在右子树上找
         返回<0,会继续在左子树上找
     */
     public int compareTo(User u1) {
         if(this.age == u1.age) return this.name.compareTo(u1.name);
         return this.age - u1.age;
     }
     public String toString() {
         return "客户" + name + "年龄" + age ;
     }
    }
    
  14. 在构造TreeSet或者TreeMap集合的时候给他传一个比较器对象

    public static void main(String[] args) {
     //使用匿名内部类
     TreeSet<Animal> ts = new TreeSet<>(new Comparator<Animal>() {
         public int compare(Animal o1, Animal o2) {
    
             return o1.age - o2.age;
         }
     });
     ts.add(new Animal(2));
     ts.add(new Animal(1));
     ts.add(new Animal(0));
     for(Animal a : ts){
         System.out.println(a);
     }
    }
    }
    class Animal{
    int age;
    public Animal(int age){
     this.age = age;
    }
    }
    
  15. Comparable和Comparator怎么选择呢?

    1. 当比较规则不变的时候,或者说当比较规则只有1个的时候,建议实现Comparable接口
    2. 如果比较规则有多个,并且需要多个比较规则之间频繁切换,建议使用Comparator接口

      自平衡二叉树

  16. TreeSet/TreeMap是自平衡二叉树。遵循左小右大原则存放(存放是要依靠左小右大的原则,所以这个存放的时候要进行比较)

  17. 遍历二叉树的时候有三种方式:
    1. 前序遍历:根左右
    2. 中序遍历:左根右
    3. 后序遍历:左右根
  18. TreeSet/TreeMap集合采用的是:中序遍历方式。Itreator迭代器采用的是中序遍历方式:左根右

010-自平衡二叉树.png

集合工具类

  1. java.util.Collection 集合接口 java.util.Collections 集合工具类,方便集合进行操作
  2. Collections.synchronizedList() 将集合变成线程安全的
  3. Collections.sort()给集合进行排序
  4. Collections.reverse(List<?> list) 反转指定列表中元素的顺序
  5. Collections.shuffle(List<?> list) 使用默认随机源对指定列表进行置换

    //类实现java.lang.Comparable 接口
    class Animal implements Comparable<Animal>{
     int age;
     public Animal(int age){
         this.age = age;
     }
    }
    //直接用Collections工具类进行排序
    Collections.sort(myList1);
    //自定义比较器对象,实现java.util.Comparator接口 Collections.sort(List集合,比较器对象);
    Collections.sort(myList, new Comparator<Animal>() {
     @Override
     public int compare(Animal o1, Animal o2) {
         return o1.age - o2.age;
     }
    });
    

    File类

    遍历多级文件夹

    public static void main (String[] args){
     //创建文件对象
     File file = new File("D:\jdk-18");
     print(file);
    }
    public void print (File file){
     //健壮性判断
     if (file == null) {
         System.out.println("文件有误");
         return;
     }
     //将文件转换为File类的数组
     File[] files = file.listFiles();
     //使用增强for循环,遍历file数组
     for(File f : files){
         if(f.isFile()){
             System.out.println("文件:" + f);
         }
         if(f.isDirectory){
             System.out.println("目录:" + f);
             //递归调用print方法继续遍历下一层目录
             print(f);
         }
     }
    }
    

    IO流

    001-什么是IO.png

    IO流的分类

  6. 按照流的方向进行分类:

    1. 以内存为参照物
      1. 往内存中去,叫做输入(Input),或者叫做读(Read)
      2. 从内存中走出来,叫做输出(Output),或者叫做写(Write)
    2. 另一种方式是按照读取数据方式不同进行分类:

        有的流是按照字节的方式读取数据,一次读取1个字节byte,等同于一次读取8个二进制位。<br />            这种流是万能的,什么类型的文件都可以读取。包括:文本文件,图片,声音文件,视频文件等....<br />                假设文件file1.txt,采用字节流的话是这样读的:<br />                    a中国bc张三fe<br />                    第一次读:一个字节,正好读到'a'<br />                    第二次读:一个字节,正好读到'中'字符的一半。<br />                    第三次读:一个字节,正好读到'中'字符的另外一半。
      
        有的流是按照字符的方式读取数据的,一次读取一个字符,这种流是为了方便读取<br />            普通文本文件而存在的,这种流不能读取:图片、声音、视频等文件。只能读取纯<br />            文本文件,连word文件都无法读取。<br />                假设文件file1.txt,采用字符流的话是这样读的:<br />                    a中国bc张三fe<br />                    第一次读:'a'字符('a'字符在windows系统中占用1个字节。)<br />                    第二次读:'中'字符('中'字符在windows系统中占用2个字节。)
      

      Java IO流这块有四大家族

  7. 四大家族的首领(全部都是抽象类):

    1. java.io.InputStream 字节输入流
    2. java.io.OutputStream 字节输出流
    3. java.io,Reader 字符输入流
    4. java.io.Writer 字符输出流
  8. 所有的流都实现了
    1. java.io.Closeable 接口,都是可关闭的,都有close()方法
    2. 流毕竟是一个管道,这个是内存和硬盘之间的通道,用完之后一定要关闭,不然会浪费很多资源
  9. 所有的输出流都实现了:
    1. java.io.Flushable接口,都是可刷新的,都有flush()方法
    2. 输出流在最终输出之后,一定要记得flush(),表示将通道/管道当中剩余未输出的数据强行输出完
    3. 注意:如果没有flush()可能会导致丢失数据
  10. 在java中只要“类名”以Stream结尾的都是字节流,以“Reader/Writer”结尾的都是字符流

    java.io包下需要掌握的流有16个

  11. 文件专属:

     java.io.FileInputStream(掌握)<br />        java.io.FileOutputStream(掌握)<br />        java.io.FileReader<br />        java.io.FileWriter
    
  12. 转换流:(将字节流转换成字符流)

     java.io.InputStreamReader<br />        java.io.OutputStreamWriter
    
  13. 缓冲流专属:

     java.io.BufferedReader<br />        java.io.BufferedWriter<br />        java.io.BufferedInputStream<br />        java.io.BufferedOutputStream
    
  14. 数据流专属:

     java.io.DataInputStream   (必须知道写入文件时的顺序和规则才能读出文件内容)<br />        java.io.DataOutputStream
    
  15. 标准输出流:

     java.io.PrintWriter<br />        java.io.PrintStream(掌握)
    
  16. 对象专属流:

     java.io.ObjectInputStream(掌握)<br />        java.io.ObjectOutputStream(掌握)
    

    FileInputStream类常用的方法

  17. int read() 返回流中读到的阿斯克码值 int read(byte[] bytes) 返回流中读到的byte类型的数组长度

    public static void main(String[] args) {
     FileInputStream fis = null;
     int read;
     byte[] bytes = new byte[4];
     try {
         fis = new FileInputStream("F:/a.txt");
         while ((read = fis.read(bytes))!=-1){
             System.out.print(new String(bytes,0,read));
         }
     } catch (FileNotFoundException e) {
         e.printStackTrace();
     } catch (IOException e) {
         e.printStackTrace();
     }
    }
    
  18. int available() 返回流当中剩余的没有读到的字节数量

    public static void main(String[] args) {
     FileInputStream fis = null;
     try {
         fis = new FileInputStream("F:/a.txt");
         System.out.println(fis.available());
         byte[] bytes = new byte[fis.available()];
         System.out.println(new String(bytes));
     } catch (FileNotFoundException e) {
         e.printStackTrace();
     } catch (IOException e) {
         e.printStackTrace();
     }
     System.out.println("sadasdasd");
    }
    
  19. long skip(long n) 跳过几个字节不读

    public static void main(String[] args) {
     FileInputStream fis = null;
     try {
         fis = new FileInputStream("F:/a.txt");
         //从第四个字节开始读
         fis.skip(3);
         int read = fis.read();
         System.out.println(read);
     } catch (FileNotFoundException e) {
         e.printStackTrace();
     } catch (IOException e) {
         e.printStackTrace();
     }
     System.out.println("sadasdasd");
    }
    

    FileOutputStream的使用

  20. 当写入文件不存在时会自动创建

  21. new FileOutputStream(FIle file)谨慎使用,这种构造方法会先将原文件清空,然后重新写入

    public static void main(String[] args) {
     FileOutputStream fos = null;
     try {
         //这种构造方法会清空文件内容再写入
         //fos = new FileOutputStream("myfile");
         //这种构造方法会在文件末尾追加内容
         fos = new FileOutputStream("myfile",true);
         byte[] bytes ={'c','y','l','o','v','e','s','a','w'};
         fos.write(bytes);
         //将String类型的字符串转换成byte数组再写入
         fos.write("崔大脸".getBytes(StandardCharsets.UTF_8));
         //写完之后一定要刷新
         fos.flush();
     } catch (FileNotFoundException e) {
         e.printStackTrace();
     } catch (IOException e) {
         e.printStackTrace();
     }finally{
         if (fos != null) {
             try {
                 fos.close();
             }     catch (IOException e) {
                 e.printStackTrace();
             }
         }
     }
    }
    

    文件复制,拷贝目录

  22. 使用FileInputStream + FileOutputStream 完成文件的拷贝

  23. 拷贝的过程应该是一边读,一边写
  24. 使用字节流拷贝文件时,文件类型随意,万能的

    public static void main(String[] args) {
     FileInputStream fis = null;
     FileOutputStream fos = null;
     try {
         fis = new FileInputStream("F:a.txt");
         fos = new FileOutputStream("F:b.txt");
         //定义一个一次读多少的byte数组
         byte[] bytes = new byte[1024*1024];
         int readCount = 0;
         while((readCount = fis.read(bytes)) != -1){
             fos.write(bytes,0,readCount);
         }
         fos.flush();
     } catch (FileNotFoundException e) {
         e.printStackTrace();
     } catch (IOException e) {
         e.printStackTrace();
     }
     if (fos != null) {
         try {
             fos.close();
         } catch (IOException e) {
             e.printStackTrace();
         }
     }finally{
     if (fis != null) {
         try {
             fis.close();
         } catch (IOException e) {
             e.printStackTrace();
         }
     }
    }
    }
    
    public class CopyAll {
     public static void main(String[] args) {
         // 拷贝源
         File srcFile = new File("F:\\BaiduNetdiskDownload\\ja-netfilter-all");
         // 拷贝目标
         File destFile = new File("E:\\a\\");
         // 调用方法拷贝
         copyDir(srcFile, destFile);
     }
    
     /**
      * 拷贝目录
      * @param srcFile 拷贝源
      * @param destFile 拷贝目标
      */
     private static void copyDir(File srcFile, File destFile) {
         if(srcFile.isFile()) {
             // srcFile如果是一个文件的话,递归结束。
             // 是文件的时候需要拷贝。
             // ....一边读一边写。
             FileInputStream in = null;
             FileOutputStream out = null;
             try {
                 // 读这个文件
                 // D:\course\02-JavaSE\document\JavaSE进阶讲义\JavaSE进阶-01-面向对象.pdf
                 in = new FileInputStream(srcFile);
                 // 写到这个文件中
                 // C:\course\02-JavaSE\document\JavaSE进阶讲义\JavaSE进阶-01-面向对象.pdf
                 String path = (destFile.getAbsolutePath().endsWith("\\") ? destFile.getAbsolutePath() : destFile.getAbsolutePath() + "\\")  + srcFile.getAbsolutePath().substring(3);
                 out = new FileOutputStream(path);
                 // 一边读一边写
                 byte[] bytes = new byte[1024 * 1024]; // 一次复制1MB
                 int readCount = 0;
                 while((readCount = in.read(bytes)) != -1){
                     out.write(bytes, 0, readCount);
                 }
                 out.flush();
             } catch (FileNotFoundException e) {
                 e.printStackTrace();
             } catch (IOException e) {
                 e.printStackTrace();
             } finally {
                 if (out != null) {
                     try {
                         out.close();
                     } catch (IOException e) {
                         e.printStackTrace();
                     }
                 }
                 if (in != null) {
                     try {
                         in.close();
                     } catch (IOException e) {
                         e.printStackTrace();
                     }
                 }
             }
             return;
         }
         // 获取源下面的子目录
         File[] files = srcFile.listFiles();
         for(File file : files){
             // 获取所有文件的(包括目录和文件)绝对路径
             //System.out.println(file.getAbsolutePath());
             if(file.isDirectory()){
                 // 新建对应的目录
                 //System.out.println(file.getAbsolutePath());
                 //D:\course\02-JavaSE\document\JavaSE进阶讲义       源目录
                 //C:\course\02-JavaSE\document\JavaSE进阶讲义       目标目录
                 String srcDir = file.getAbsolutePath();
                 String destDir = (destFile.getAbsolutePath().endsWith("\\") ? destFile.getAbsolutePath() : destFile.getAbsolutePath() + "\\")  + srcDir.substring(3);
                 File newFile = new File(destDir);
                 if(!newFile.exists()){
                     newFile.mkdirs();
                 }
             }
             // 递归调用
             copyDir(file, destFile);
         }
     }}
    

    java.io.FileReader的使用

  • 文件字符输入流,读,只能输入普通文本

    public static void main(String[] args) {
      FileReader fr = null;
      try {
          fr = new FileReader("F:a.txt");
          char[] chars = new char[5];
          int readCount = 0;
    /*      fr.read(chars);
          for(char c : chars){
          System.out.println(c);
          }
    */      while ((readCount = fr.read(chars))!= -1){
          System.out.println(new String(chars,0,readCount));
          }
      } catch (IOException e) {
          e.printStackTrace();
      }finally{
          if (fr != null) {
              try {
                  fr.close();
              } catch (IOException e) {
                  e.printStackTrace();
              }
          }
      }
    }
    

    java.io.FileWriter的使用

  • 文件字符输出流,写,只能输出普通文本

  • FileWrite 可以直接输出String类型的数据 ```java public static void main(String[] args) { FileWriter fw = null; try {
      fw = new FileWriter("F:c.txt");
      char[] chars = {'c','y'};
      fw.write(chars);
      fw.write("崔裕是个大笨蛋");
      //输出必须Flush
      fw.flush();
    
    } catch (IOException e) {
      e.printStackTrace();
    
    }finally{
       if (fw != null) {
      try {
          fw.close();
      } catch (IOException e) {
          e.printStackTrace();
      }
    
    } }

}

<a name="VXCBS"></a>
## java.io.BufferedReader的使用

1. BufferedReader:
   1. 带有缓冲区的字符输入流
   1. 使用这个流的时候不需要自定义char数组,或者说不需要自定义byte数组。自带缓冲
2. 当一个流的构造方法中需要一个流的时候,这个被传进来的叫做:节点流
2. 外部负责包装的这个流,叫做:包装流,还有一个名字叫做:处理流
```java
public static void main(String[] args) {
    FileInputStream fi = null;
    try {
        fi = new FileInputStream("F:c.txt");
        //将字节流转化为字符流
        //这里fi是节点流,inputStreamReader是包装流
        InputStreamReader inputStreamReader = new InputStreamReade+(fi);
        //这里inputStreamReader 是节点流,bufferedReader 是包装流
        BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
        String s = null;
        while ((s = bufferedReader.readLine()) != null){
            System.out.println(s);
        }
        //包装流关闭,节点流也关闭
        buffereReader.close();
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

标准字节输出流 java.io.PrintStream

public class Logger {
    /*
    记录日志的方法。
     */
    public static void log(String msg) {
        try {
            // 指向一个日志文件
            PrintStream out = new PrintStream(new FileOutputStream("log.txt", true));
            // 改变输出方向
            System.setOut(out);
            // 日期当前时间
            Date nowTime = new Date();
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
            String strTime = sdf.format(nowTime);

            System.out.println(strTime + ": " + msg);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }
public static void main(String[] args) {
    //测试工具类是否好用
    Logger.log("调用了System类的gc()方法,建议启动垃圾回收");
    Logger.log("调用了UserService的doSome()方法");
    Logger.log("用户尝试进行登录,验证失败");
    Logger.log("我非常喜欢这个记录日志的工具哦!");
}

java.io.File

  1. File类和四大家族没有关系,所以File类不能完成文件的读和写
  2. File对象代表什么?
    1. 文件和目录路径名的抽象表示形式
    2. C:/Drivers 这是一个File对象
    3. C:/a.txt 这也是一个File对象
    4. 一个File对象有可能对应的是目录,也可能是文件
    5. File只是一个路径名的抽象表示形式
  3. 掌握FIle类的常用方法

    public static void main(String[] args) throws IOException {
     File f1 = new File("F:a.txt");
     //判断文件是否存在
     System.out.println(f1.exists());
     File f2 = new File("F:d.txt");
     if(!(f2.exists())){
         //以文件的形式新建
         f2.createNewFile();
     }
     File f3 = new File("F:a");
     if (!(f3.exists())){
         //创建单个目录
         f3.mkdir();
     }
     File f4 = new File("F:a/b");
     if (!(f4.exists())){
         //创建多重目录
         f4.mkdirs();
     }
    }
    
    public static void main(String[] args) {
     File f1 = new File("F:/a/b");
     //获取文件绝对路径
     System.out.println("文件名" + f1.getAbsolutePath());
     //获取父文件路径
     System.out.println("父路径名" + f1.getParent());
     //判断是否是一个目录
     System.out.println(f1.isDirectory());
     //判断是否是一个文件
     System.out.println(f1.isFile());
     //获取文件最后一次修改的时间,这个毫秒是从1970年到现在的总毫秒数
     long haoMiao = f1.lastModified();
     //将总毫秒转换成日期
     Date time = new Date(haoMiao);
     ////重新定义时间格式
     SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
     System.out.println(sdf.format(time));
     //获取文件大小
     System.out.println(f1.length());
    }
    
    public static void main(String[] args) {
     File file = new File("E:/qq");
     //返回一个File类型的数组
     File[] list = file.listFiles();
     for(File f : list){
         System.out.println(f);
     }
    }
    

    序列化和反序列化

    003-对象的序列化和反序列化.png

  4. 参加序列化和反序列化的对象,必须实现Serializable接口

  5. 注意:通过源代码发现,Serializable接口只是一个标志接口,接口当中什么代码也没有,只起到标识的作用

java虚拟机看到这个接口后,会为该类自定生成一个序列化版本号

  1. 凡是一个类实现了Serializable接口,建议给该类提供一个固定不变的序列化版本号。 这样,以后这个类即使代码修改了,但是版本号不变,java虚拟机会认为是同一个类。

    /*
    一次序列化多个对象呢?
     可以,可以将对象放到集合当中,序列化集合。
    提示:
     参与序列化的ArrayList集合以及集合中的元素User都需要实现 java.io.Serializable接口。
    */
    public class ObjectOutputStreamTest02 {
     public static void main(String[] args) throws Exception{
         List<User> userList = new ArrayList<>();
         userList.add(new User(1,"zhangsan"));
         userList.add(new User(2, "lisi"));
         userList.add(new User(3, "wangwu"));
         ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("users"));
         // 序列化一个集合,这个集合对象中放了很多其他对象。
         oos.writeObject(userList);
         oos.flush();
         oos.close();
     }
    }
    public static void main(String[] args) throws Exception{
     ObjectInputStream ois = new ObjectInputStream(new FileInputStream("users"));
     //Object obj = ois.readObject();
     //System.out.println(obj instanceof List);
     List<User> userList = (List<User>)ois.readObject();
     for(User user : userList){
         System.out.println(user);
     }
     ois.close();
    }
    

    Io和Properties联合使用

    /*
    IO+Properties的联合应用。
    非常好的一个设计理念:
     以后经常改变的数据,可以单独写到一个文件中,使用程序动态读取。
     将来只需要修改这个文件的内容,java代码不需要改动,不需要重新
     编译,服务器也不需要重启。就可以拿到动态的信息。
    
     类似于以上机制的这种文件被称为配置文件。
     并且当配置文件中的内容格式是:
         key1=value
         key2=value
     的时候,我们把这种配置文件叫做属性配置文件。
    
     java规范中有要求:属性配置文件建议以.properties结尾,但这不是必须的。
     这种以.properties结尾的文件在java中被称为:属性配置文件。
     其中Properties是专门存放属性配置文件内容的一个类。
    */
    public class IoPropertiesTest01 {
     public static void main(String[] args) throws Exception{
         /*
         Properties是一个Map集合,key和value都是String类型。
         想将userinfo文件中的数据加载到Properties对象当中。
          */
         // 新建一个输入流对象
         FileReader reader = new FileReader("chapter23/userinfo.properties");
    
         // 新建一个Map集合
         Properties pro = new Properties();
    
         // 调用Properties对象的load方法将文件中的数据加载到Map集合中。
         // 文件中的数据顺着管道加载到Map集合中,其中等号=左边做key,右边做value
         pro.load(reader); 
    
         // 通过key来获取value呢?
         String username = pro.getProperty("username");
         System.out.println(username);
    
         String password = pro.getProperty("password");
         System.out.println(password);
    
         String data = pro.getProperty("data");
         System.out.println(data);
    
         String usernamex = pro.getProperty("usernamex");
         System.out.println(usernamex);
     }
    }
    

    多线程

    进程和线程之间的关系

  2. 进程是一个应用程序(1个进程是一个软件),线程是一个进程中场景/执行单元,一个进程可以启动多个线程

  3. 对于java来说执行一个文件就相当于一个进程,JVM启动一个主线程调用main方法,同时再启动一个垃圾回收线程负责看护,回收垃圾
  4. 在Java语言中:

    1. 两个进程是独立的,不共享资源
    2. 线程A和线程B,堆内存和方法区内存共享,但是栈内存独立,一个线程一个栈

      实现线程有两种方式

  5. 编写一个类,直接继承java.lang.Thread,重写run方法

    public class ThreadTest01 {
     public static void main(String[] args) {
         //创建线程对象
         Thread thread = new Thread();
         //启动线程
         thread.start();
         for (int i = 0; i < 1000; i++) {
             System.out.println("主线程" + i);
         }
     }
    }
    class Thread extends java.lang.Thread {
     @Override
     public void run() {
         for (int i = 0; i < 1000; i++) {
             System.out.println("分支线程" + i);
         }
     }
    }
    
  6. 编写一个类,实现java.lang.Runnable接口,实现run方法

    public class ThreadTest02 {
     public static void main(String[] args) {
         //采用匿名内部类的方式
         Thread thread = new Thread(new Runnable(){
             @Override
             public void run() {
                 for (int i = 0; i < 1000; i++) {
                     System.out.println("分之线程" + i);
                 }
             }
         });
         thread.start();
         for (int i = 0; i < 1000; i++) {
             System.out.println("主线程" + i);
         }
     }
    }
    

    线程的生命周期

    屏幕截图(5).png

    怎么获取当前线程对象

  7. 怎么获取当前线程对象?

    1. Thread t = Thread.currentThread();
    2. 返回值t就是当前对象
  8. 获取线程对象的名字?
    1. String name = 线程对象.getName();
  9. 修改线程对象的名字
    1. 线程对象.setName(“线程名字”);
  10. 当线程没有设置名字的时候,默认的名字Thread-0

    关于线程的sleep方法

    static void sleep(long millis)

  11. 静态方法:Thread.sleep(1000);

  12. 参数是毫秒
  13. 作用:
    1. 当前线程进入休眠,进入“阻塞状态”,放弃占有CPU时间片,让给其他线程使用
    2. 这行代码出现在A线程中,A线程就进入休眠
    3. 这行代码出现在B线程中,B线程就进入休眠
  14. Thread.sleep()方法,可以做到间隔特定的时间,去执行特定的代码

    关于线程的一道面试题

    public class ThreadTest03 {
     public static void main(String[] args) {
         Thread t = new myThread();
         t.setName("t");
         t.start();
         try {
             //这段代码在执行的时候等同于Thread.sleep()
             //sleep方法的作用是让当前线程进入休眠
             //所以在这了是让主线程进入休眠
             t.sleep(1000*5);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
         System.out.println("主线程");
     }
    }
    class myThread extends Thread{
     @Override
     public void run() {
         for (int i = 0; i < 10; i++) {
             //run()当中的异常不能throws,只能try catch
             //因为run()方法当中在父类中没有抛出任何异常,子类不能比父类抛出更多的异常
             try {
                 //这里是让分支线程进入休眠
                 Thread.sleep(100);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
             System.out.println(Thread.currentThread().getName() + "线程" + i);
         }
     }
    }
    

    Java强行终止线程睡眠、终止线程执行

  15. 终止线程睡眠:线程对象.inteerup()

  16. 终止线程执行:在线程类里边加一个布尔标记

    public class ThreadTest04 {
     public static void main(String[] args) {
         myThread01 my = new myThread01();
         my.setName("t");
         my.start();
         try {
             Thread.sleep(1000*6);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
         //终止线程睡眠
         my.interrupt();
         //合理终止线程执行
         //my.run = false;
     }
    }
    class myThread01 extends Thread{
     //布尔标记
     boolean run = true;
     @Override
     public void run() {
         for (int i = 0; i < 10; i++) {
             if (run) {
                 try {
                     if(i !=0) Thread.sleep(1000*60*60);
                     System.out.println(Thread.currentThread().getName() + "-----" + i);
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
    
             } else {
                 return;
             }
         }
     }}
    

    synchronized

  17. 什么时候数据在多线程并发的环境下会出现安全问题

    1. 三个条件:多线程并发、有共享数据、共享数据有修改的行为
  18. 怎么解决线程安全问题呢?

     当多线程并发的环境下,有共享数据,并且这个数据还会被修改,此时就存在<br />        线程安全问题,怎么解决这个问题?<br />            线程排队执行。(不能并发)。<br />            用排队执行解决线程安全问题。<br />            这种机制被称为:线程同步机制。
    
         专业术语叫做:线程同步,实际上就是线程不能并发了,线程必须排队执行。<br />        <br />        怎么解决线程安全问题呀?<br />            使用“线程同步机制”。<br />        <br />        线程同步就是线程排队了,线程排队了就会牺牲一部分效率,没办法,数据安全<br />        第一位,只有数据安全了,我们才可以谈效率。数据不安全,没有效率的事儿。
    
  19. 异步编程模型:

         线程t1和线程t2,各自执行各自的,t1不管t2,t2不管t1,<br />            谁也不需要等谁,这种编程模型叫做:异步编程模型。<br />            其实就是:多线程并发(效率较高。)<br />            异步就是并发。
    
     同步编程模型:<br />            线程t1和线程t2,在线程t1执行的时候,必须等待t2线程执行<br />            结束,或者说在t2线程执行的时候,必须等待t1线程执行结束,<br />            两个线程之间发生了等待关系,这就是同步编程模型。<br />            效率较低。线程排队执行。
    
         同步就是排队。<br />**局部变量永远不会存在线程安全问题,因为局部变量在栈中,一个线程一个栈,所以不共享**<br />**实例变量在堆中,堆只有一个,可能出现线程安全问题**<br />**堆和方法区都是多线程共享的,所以可能存在线程安全问题**
    

总结:
synchronized有三种写法:
第一种:同步代码块(同步代码块同一时间只能被一条线程所执行,其他线程在同步代码块外进行“阻塞”,同步代码块中的线程执行完毕后,再进行资源抢夺)
灵活
synchronized(线程共享对象){
同步代码块;
}
第二种:在实例方法上使用synchronized
表示共享对象一定是this
并且同步代码块是整个方法体。
第三种:在静态方法上使用synchronized
表示找类锁。(类锁只能在静态方法上使用synchronized
类锁永远只有1把。
就算创建了100个对象,那类锁也只有一把。
对象锁:1个对象1把锁,100个对象100把锁。
类锁:100个对象,也可能只是1把类锁。

定时器

  1. 定时器的作用:间隔特定的时间,执行特定的程序
  2. 在Java的类库中已经写好了一个定时器:java.util.Timer,可以直接拿来用
    public class TimerTest {
     public static void main(String[] args) throws ParseException {
         //创建定时器对象
         Timer timer = new Timer();
         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
         Date firstTime = sdf.parse("2022-05-17 16:32:10");
         //指定定时任务
         //也可以使用匿名内部类
         timer.schedule(new LongTimerTask(), firstTime, 1000*5);
     }
    }
    class LongTimerTask extends TimerTask{
     @Override
     public void run() {
         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
         System.out.println(sdf.format(new Date())+ ":成功完成一次备份");
     }
    }
    

    实现线程的第三种方式:实现Callable接口

  • 这种方式实现的线程可以获取线程的返回值
  • java.util.concurrent.Callable
    public class ThreadTest05 {
      public static void main(String[] args) {
          //创建未来任务类对象
          //匿名内部类
          FutureTask<Integer> task = new FutureTask<>(new Callable<Integer>() {
              @Override
              public Integer call() throws Exception {
                  System.out.println("call begin");
                  Thread.sleep(1000);
                  System.out.println("call over");
                  return 1+2;
              }
          });
          //创建线程对象
          Thread t = new Thread(task);
           t.start();
          try {
              Integer i = task.get();
              System.out.println(i);
          } catch (InterruptedException e) {
              e.printStackTrace();
          } catch (ExecutionException e) {
              e.printStackTrace();
          }
      }
    }
    

    wait()和notify()方法

  1. wait和notify方法不是线程对象的方法,是普通Java对象都有的方法,
  2. wait方法和notify方法建立在线程同步的基础之上,因为多线程要同时操作一个仓库,有线程安全问题
  3. wait方法作用:o.wait()让正在o对象上活动的线程t进入等待状态,并且释放掉t线程之前占有的o对象的锁
  4. notify方法作用:o.notify()让正在o对象上等待的线程唤醒,只是通知:不会释放o对象上之前占有的锁, ```java public class ThreadTest16 { public static void main(String[] args) {

     // 创建1个仓库对象,共享的。
     List list = new ArrayList();
     // 创建两个线程对象
     // 生产者线程
     Thread t1 = new Thread(new Producer(list));
     // 消费者线程
     Thread t2 = new Thread(new Consumer(list));
    
     t1.setName("生产者线程");
     t2.setName("消费者线程");
    
     t1.start();
     t2.start();
    

    } }

// 生产线程 class Producer implements Runnable { // 仓库 private List list;

public Producer(List list) {
    this.list = list;
}
@Override
public void run() {
    // 一直生产(使用死循环来模拟一直生产)
    while(true){
        // 给仓库对象list加锁。
        synchronized (list){
            if(list.size() > 0){ // 大于0,说明仓库中已经有1个元素了。
                try {
                    // 当前线程进入等待状态,并且释放Producer之前占有的list集合的锁。
                    list.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            // 程序能够执行到这里说明仓库是空的,可以生产
            Object obj = new Object();
            list.add(obj);
            System.out.println(Thread.currentThread().getName() + "--->" + obj);
            // 唤醒消费者进行消费
            list.notifyAll();
        }
    }
}

}

// 消费线程 class Consumer implements Runnable { // 仓库 private List list;

public Consumer(List list) {
    this.list = list;
}

@Override
public void run() {
    // 一直消费
    while(true){
        synchronized (list) {
            if(list.size() == 0){
                try {
                    // 仓库已经空了。
                    // 消费者线程等待,释放掉list集合的锁
                    list.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            // 程序能够执行到此处说明仓库中有数据,进行消费。
            Object obj = list.remove(0);
            System.out.println(Thread.currentThread().getName() + "--->" + obj);
            // 唤醒生产者生产。
            list.notifyAll();
        }
    }
}

}

<a name="x7VQn"></a>
# Lambda表达式
<a name="r3qeF"></a>
## Lambda表达式使用前提

1. 函数式接口做方法参数传递
1. 函数式接口: 有且仅有一个抽象方法的接口
1. 可以使用注解: @FunctionalInterface 检测接口是否为函数式接口
<a name="N3FPJ"></a>
## Lambda表达式使用

1. 观察是否是函数式接口做方法参数传递    ;  如果是,考虑使用Lambda表达式
1. 调用方法传递实参时,写成匿名内部类的形式
1. 从new接口开始,到重写方法的方法名结束,选中,删除
1. 在重写方法的参数后面,方法体前面加上 ->
1. 再看看能不能根据省略规则再次删一删
<a name="kYeXq"></a>
## Lambda表达式省略规则

1. 重写方法的参数类型可以省略
1. 如果重写方法只有一个参数,那么类型也可以省略,所在的小括号也可以省略
1. 如果重写方法的方法体只有一句话,所在的方法体大括号可以省略
1. 如果重写方法的方法体只有一句话,且还是带return的,那么return,方法体的大括号,分号都可以省略
```java
public class Test02 {
    public static void main(String[] args) {
        ArrayList<Person> list = new ArrayList<>();
        list.add(new Person("柳岩",36));
        list.add(new Person("涛哥",18));
        list.add(new Person("金莲",26));
        Collections.sort(list, new Comparator<Person>() {
            @Override
            public int compare(Person o1, Person o2) {
                return o1.getAge()-o2.getAge();
            }
        });
        System.out.println("=======lambda表达式=======");
        Collections.sort(list, (Person o1, Person o2)-> {
                return o1.getAge()-o2.getAge();
        });
        System.out.println("======lambda表达式简化形式");
        Collections.sort(list, (o1,o2)-> o1.getAge()-o2.getAge());
        System.out.println(list);
    }
}

Stream流

Stream中的常用方法

  1. forEach方法 : void forEach(Consumer<? super T> action)

    /*
    forEach:逐一处理-->遍历
    注意:forEach方法在Stream流中是一个终结方法
    */
    public static void main(String[] args) {
     Stream<String> stream = Stream.of("张无忌", "张三丰", "张翠山", "张三", "李四", "王五", "赵六", "田七");
     stream.forEach(new Consumer<String>() {
         *//*
             accept方法中的s代表的就是Stream流中的每一个元素
             *//*
             @Override
             public void accept(String s) {
             System.out.println(s);
         }
     });
     System.out.println("====================");
     stream.forEach(s -> System.out.println(s));
    }
    
  2. 统计个数方法: long count()

    /*
    1.作用:统计元素个数
    2.注意:count方法,也是一个终结方法
    */
    public static void main(String[] args) {
         Stream<String> stream = Stream.of("张无忌", "张三丰", "张翠山", "张三", "李四", "王五", "赵六", "田七");
         long count = stream.count();
         System.out.println("count = " + count);
    }
    
  3. 过滤方法 : Stream filter(Predicate<? super T> predicate)