F

126 阅读31分钟

第一章.设计模式

 设计模式(Design pattern),是一套被反复使用、经过分类编目的、代码设计经验的总结,使用设计模式是为了可重用代码、保证代码可靠性、程序的重用性。
 ​
 1995 年,GoF(Gang of Four,四人组)合作出版了《设计模式:可复用面向对象软件的基础》一书,共收录了 23 种设计模式。
 ​
 总体来说设计模式分为三大类:
 ​
 创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。-->创建对象
 ​
 结构型模式,共七种:[适配器模式]、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。-->对功能进行增强
 ​
 行为型模式,共十一种:策略模式、模板方法模式、[观察者模式]、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、[中介者模式]、解释器模式。

单例模式

 1.目的:为了在一个类中只产生一个对象,给外界使用
 2.单:一个
 3.例:实例(对象)

1.饿汉式:

 1.简单理解:  我很饥饿,迫不及待的想要这个对象,所以此类的对象就应该很早的创建出来
 /**
  * 饿汉式
  * 1.简单理解:  我很饥饿,迫不及待的想要这个对象,所以此类的对象就应该很早的创建出来
  */
 public class Singleton {
     /*
       为了防止外面随意使用构造去new对象
       可以将构造私有化
      */
     private Singleton(){
 ​
     }
 ​
     /*
       由于构造私有,外界new不了对象
       所以对象只能在本类中产生
 ​
       饿汉式,迫不及待的想让对象产生
       所以此对象变成static的,就可以随着类的加载而加载
 ​
       而且此对象不能让外界直接访问到的,所以再加一个private权限
      */
     private static Singleton singleton = new Singleton();
 ​
     /*
       定义一个方法,将本类内部创建出来的对象,返回给外界
      */
     public static Singleton getSingleton(){
         return singleton;
     }
 }
 public class Test01 {
     public static void main(String[] args) {
         for (int i = 0; i < 5; i++) {
             Singleton singleton = Singleton.getSingleton();
             System.out.println(singleton);
         }
 ​
     }
 }

2.懒汉式:

 1.懒汉式:什么时候想要对象,对象再产生,不着急new对象
 /**
  * 懒汉式
  * 什么时候想要对象,对象再产生,不着急new对象
  */
 public class Singleton {
     /*
       为了防止外面随意使用构造去new对象
       可以将构造私有化
      */
     private Singleton() {
 ​
     }
 ​
     /*
       由于构造私有,外界new不了对象
       所以对象只能在本类中产生
 ​
       懒汉式,不用迫不及待的让对象产生
       所以不用上来就new
 ​
       而且此对象不能让外界直接访问到的,所以再加一个private权限
      */
     private static Singleton singleton = null;
 ​
     /*
       定义一个方法,将本类内部创建出来的对象,返回给外界
       什么时候调用此方法,什么时候再给外界产生对象,而且保证只能返回同一个对象
      */
     public static Singleton getSingleton() {
         if (singleton == null) {
             synchronized (Singleton.class) {
                 if (singleton == null) {
                     singleton = new Singleton();
                 }
             }
         }
         return singleton;
     }
 }
 ​
 public class Test01 {
     public static void main(String[] args) {
         for (int i = 0; i < 5; i++) {
             Singleton singleton = Singleton.getSingleton();
             System.out.println(singleton);
         }
 ​
     }
 }
 ​

3.小结

 1.构造私有
 2.对象在内部产生,并且私有,静态
 3.定义方法,让外界调用方法,将本类对象返回

第二章.File类

1.File类

 1.要知道的英文单词:
   a.File:文件
   b.Directory:文件夹(目录)
   c.path:路径
   d.in:输入
   e.out:输出
       
 2.以.jpg结尾的一定是图片吗?
   不一定,要看类型
       
 3.路径分隔符
   路径名称分隔符:在一个路径中文件夹之间的分隔符
     windows:\
     linux:/
         
   路径分隔符:路径和其他路径之间的分隔符
     ;
 ​
 4. E:\Idea\io
    io的父路径是谁?E:\Idea
     
 5.什么是文本文档
   用记事本打开人能看懂的就是文本文档
   .txt   .html  .css .java->都是
        
   .doc  .ppt->不是
 File:
   1.概述:文件和目录路径名的抽象表示形式
   2.通俗解释:
     我们创建File对象的时候,需要传递指定文件或者文件夹的路径,那么File就可以代表此文件或者文件夹
     然后也可以根据路径找到此文件或者文件夹,然后就可以通过File类中的方法去操作指定路径下的指定文件或者文件夹

2.File的静态成员

 static String pathSeparator  ->  与系统有关的路径分隔符    ;
 static String separator-> 与系统有关的默认名称分隔符        \
 public class Test01File {
     public static void main(String[] args) {
         //static String pathSeparator  ->  与系统有关的路径分隔符   ;
         String pathSeparator = File.pathSeparator;
         System.out.println(pathSeparator);
         //static String separator-> 与系统有关的默认名称分隔符   \
         String separator = File.separator;
         System.out.println(separator);
     }
 }

经验值:如何正确写一个路径

 public class Test02File {
  public static void main(String[] args) {
 ​
      /*
        写代码的时候需要跨平台
        达到一次编写,到处运行的效果
 ​
        如果写路径时,分隔符写死,到了别的平台上运行
        有可能有问题,所以代码要写活
       */
      String path = "E:\Idea\io";
      System.out.println(path);
 ​
      String path1 = "E:"+ File.separator+"Idea"+File.separator+"io";
      System.out.println(path1);
  }
 }

3.File的构造方法

 File(String parent, String child) -> 根据所填写的路径创建File对象
      parent:父路径
      child:子路径
 File(File parent, String child) -> 根据所填写的路径创建File对象
      parent:父路径->File对象
      child:子路径
 File(String pathname) -> 根据所填写的路径创建File对象
      字符串的路径
 public class Test03File {
     public static void main(String[] args) {
       /*  File(String parent, String child) -> 根据所填写的路径创建File对象
         parent:父路径
         child:子路径*/
 ​
         File file1 = new File("E:\Idea\io","1.jpg");
         System.out.println(file1);
        /* File(File parent, String child) -> 根据所填写的路径创建File对象
         parent:父路径->File对象
         child:子路径*/
 ​
        File file2 = new File(new File("E:\Idea\io"),"1.jpg");
         System.out.println(file2);
         //File(String pathname) -> 根据所填写的路径创建File对象 字符串的路径
         File file3 = new File("E:\Idea\io");
         System.out.println(file3);
     }
 }
 ​

注意:创建File的时候,指定的路径可以不存在,但是没意义

4.File的获取方法

 - public String getAbsolutePath() :返回此File的绝对路径名字符串。->带盘符的路径
 - public String getPath() :将此File转换为路径名字符串。-> 获取的是File的封装路径
                            new File的时候,传递的是啥路径,getPath方法就获取的是啥路径
 - public String getName()  :返回由此File表示的文件或目录的名称。  
 - public long length()  :返回由此File表示的文件的长度。->文件的字节数 
 - public File getParentFile()返回由此File表示的文件或目录的父目录,如果没有父目录,返回null。
 public class Test01_Get {
     public static void main(String[] args) {
         File file = new File("io\1.txt");
         //- public String getAbsolutePath () :返回此File的绝对路径名字符串。->带盘符的路径
         String absolutePath = file.getAbsolutePath();
         System.out.println("absolutePath = " + absolutePath);
 /*        - public String getPath () :将此File转换为路径名字符串。->获取的是File的封装路径
                                      new File的时候, 传递的是啥路径, getPath方法就获取的是啥路径*/
         String path = file.getPath();
         System.out.println("path = " + path);
         //- public String getName ()  :返回由此File表示的文件或目录的名称。
         String name = file.getName();
         System.out.println("name = " + name);
 ​
         //- public long length ()  :返回由此File表示的文件的长度。->文件的字节数
         File file1 = new File("E:\Idea\io\a.txt");
         System.out.println("file1.length() = " + file1.length());
         //- public File getParentFile () 返回由此File表示的文件或目录的父目录,如果没有父目录,返回null。
         System.out.println("file1.getParentFile() = " + file1.getParentFile());
     }
 }
 ​

5.相对路径和绝对路径

 1.绝对路径:带盘符的路径
   跨盘符可以写绝对路径
     
 2.相对路径:在idea中写的相对路径
   a.找一个参照路径-> 在idea中,参照路径是当前project的绝对路径
   b.哪个路径为参照路径,哪个路径就可以省略不写,剩下的就是在idea中的相对路径写法
   
 3.相对路径举例说明:
   3.1.在day19模块下创建了一个1.txt,那么1.txt的相对路径怎么写?
       a.1.txt的绝对路径:E:\Idea\idea2019\workspace\220212_javase\day19\1.txt
       b.1.txt的参照路径:E:\Idea\idea2019\workspace\220212_javase
       c.1.txt的相对路径:day19\1.txt
           
 4.总结:
   a.在idea中相对路径一般都是从模块名开始写
   b.要是不带模块名,直接写文件名或者文件夹名,默认位置在当前project

6.File的创建方法

 boolean createNewFile() -> 创建新文件
         如果指定的文件之前有,创建失败,返回false
         如果指定的文件之前没有,创建成功,返回true
 boolean mkdirs() -> 既可以创建单级文件夹,还可以创建多级文件夹
         如果指定的文件夹之前有,创建失败,返回false
         如果指定的文件夹之前没有,创建成功,返回true
 public class Test03_Create {
     public static void main(String[] args) throws IOException {
         /*
         boolean createNewFile() -> 创建新文件
         如果指定的文件之前有,创建失败,返回false
         如果指定的文件之前没有,创建成功,返回true
         */
         File file = new File("E:\Idea\io\1.txt");
         System.out.println("file.createNewFile() = " + file.createNewFile());
 ​
         /*
         boolean mkdirs() -> 既可以创建单级文件夹,还可以创建多级文件夹
         如果指定的文件夹之前有,创建失败,返回false
         如果指定的文件夹之前没有,创建成功,返回true
          */
         File file1 = new File("E:\Idea\io\haha\heihei\xixi\giaogiao");
         System.out.println("file1.mkdirs() = " + file1.mkdirs());
     }
 }
 ​

7.File类的删除方法

 boolean delete() -> 既可以删除文件,也可以删除文件夹 -> 不走回收站
         文件:如果有,删除成功,返回true,否则返回false
         文件夹:如果删除文件夹,必须保证是空文件夹           
 public class Test04_Delete {
     public static void main(String[] args) {
         File file = new File("E:\Idea\io\haha");
         System.out.println("file.delete() = " + file.delete());
     }
 }

8.File类的判断方法

 boolean isDirectory()  -> 判断是否为文件夹
 boolean isFile()  -> 判断是否为文件
 boolean exists()  -> 判断文件或者文件夹是否存在
     
 注意:判断的是类型
 public class Test05_Is {
     public static void main(String[] args) {
         File file = new File("E:\Idea\io");
         //boolean isDirectory()  -> 判断是否为文件夹
         System.out.println("file.isDirectory() = " + file.isDirectory());
         //boolean isFile()  -> 判断是否为文件
         System.out.println("file.isFile() = " + file.isFile());
         //boolean exists()  -> 判断文件或者文件夹是否存在
         System.out.println("file.exists() = " + file.exists());
     }
 }

9.File的遍历方法

 String[] list()-> 获取指定目录下的子文件夹或者文件
 File[] listFiles()-> 获取执行目录下的子文件夹或者文件的File对象
     
 注意:listFiles底层原理其实就是调用list方法,将文件或者文件夹获取出来封装成一个一个的File对象,放到File数组中
 public class Test06_Foreach {
     public static void main(String[] args) {
         File file = new File("E:\Idea\io");
         //String[] list()-> 获取指定目录下的子文件夹或者文件
         /*String[] list = file.list();
         for (String s : list) {
             System.out.println(s);
         }*/
         //File[] listFiles()-> 获取执行目录下的子文件夹或者文件的File对象
         File[] files = file.listFiles();
         for (File file1 : files) {
             System.out.println(file1);
         }
     }
 }
 ​

练习:遍历指定文件夹下所有的.jpg文件

 步骤:
   1.创建File对象,指定要遍历的文件夹路径
   2.调用listFiles(),遍历指定文件夹下的内容
   3.在遍历的过程中判断
   4.判断如果是文件,获取文件的文件名,判断是否以.jpg结尾,如果是,直接输出
   5.如果是文件夹,再遍历这个子文件夹
 public class Test07_Foreach {
     public static void main(String[] args) {
         //1.创建File对象,指定要遍历的文件夹路径
         File file = new File("E:\Idea\io");
         method(file);
     }
 ​
     private static void method(File file) {
         //2.调用listFiles(),遍历指定文件夹下的内容
         File[] files = file.listFiles();
         //3.在遍历的过程中判断
         for (File file1 : files) {
             //4.判断如果是文件,获取文件的文件名,判断是否以.jpg结尾,如果是,直接输出
             if (file1.isFile()) {
                 String name = file1.getName();
                 if (name.endsWith(".jpg")) {
                     System.out.println(name);
                 }
             } else {
                 //5.如果是文件夹,再遍历这个子文件夹
                 method(file1);
             }
 ​
         }
     }
 }
 ​

image-20220309153811798

第三章.字节流

1.IO流介绍以及输入输出以及流向的介绍

 1.概述:
   I:In-> 输入
   O:Out-> 输出
 2.什么是IO流技术
   将数据从一个设备上传输到另外一个设备上的技术
 3.为什么要学IO流
   之前我们保存数据可以放到集合中,数组中,但是集合和数组是临时存储,程序运行完毕,程序会从内存中释放出来,此时数组和集合中的数据就没了
   我们就想,将数据永久保存起来,所以我们可以将数据放到硬盘上,只要硬盘不废,数据就还在,想用时候,直接从硬盘上读回来就直接可以用了
 ​
   IO流技术就可以将内存中的数据保存到硬盘上,用的时候还可以将数据从硬盘上读回来直接用

2.IO流的流向

 1.输出流:从内存出发,将数据写到硬盘的文件中
 2.输入流:将数据从硬盘的文件中读到内存中

image-20220309162342526

3.IO流分类

 1.字节流:一切皆字节,万能流
         复制的话要用字节流
     
   字节输出流:OutputStream
   字节输入流:InputStream
     
     
 2.字符流:操作文本文档的->能用记事本打开,人能看懂的
   .css .html  .txt  .java
     
   字符输出流:Writer
   字符输入流:Reader
       
  
 3.单词考验
   FileOutputStream->字节输出流
   FileInputStream->字节输入流
   FileWriter-> 字符输出流
   FileReader-> 字符输入流
   BufferedOutputStream->缓冲字节输出流
   BufferedInputStream->缓冲字节输入流
   BufferedWriter->缓冲字符输出流
   BufferedReader->缓冲字符输入流
   ObjectOutputStream->序列化流
   ObjectInputStream->反序列化流
   OutputStreamWriter->转换流->写数据
   InputStreamReader->转换流->读数据
   printStream->打印流->输出流

4.OutputStream中子类[FileOutputStream]的介绍以及方法的简单介绍

 1.字节输出流:OutputStream->抽象类
 2.OutputStream是抽象类,不能new对象,所以我们需要学习子类
   FileOutputStream
 3.构造:
   FileOutputStream(File file)
   FileOutputStream(String name) 
   以上两个方法new对象的时候参数要指明文件路径
       
 4.注意:输出流在写数据的时候,如果指定的文件没有,会自动创建
 ​
 5.方法:
    void write(int b)  -> 一次写一个字节
    void write(byte[] b)  -> 一次写一个字节数组
    void write(byte[] b, int off, int len) -> 一次写一个字节数组一部分
               b:要写的数组
               off:从数组的哪个索引开始写
               len:写多少个
    void close() -> 关闭流对象
 public class Test01_FileOutputStream {
     public static void main(String[] args) throws Exception {
         FileOutputStream fos = new FileOutputStream("day19\io\out.txt");
         //void write(int b)  -> 一次写一个字节
         fos.write(97);
         //关流
         fos.close();
     }
 }
 public class Test02_FileOutputStream {
     public static void main(String[] args) throws Exception {
         FileOutputStream fos = new FileOutputStream("day19\io\out.txt");
         //void write(byte[] b)  -> 一次写一个字节数组
         byte[] bytes = {97,98,99,100};
         fos.write(bytes);
         //关流
         fos.close();
     }
 }
 ​
 public class Test03_FileOutputStream {
     public static void main(String[] args) throws Exception {
         FileOutputStream fos = new FileOutputStream("day19\io\out.txt");
         /*
              void write(byte[] b, int off, int len) -> 一次写一个字节数组一部分
               b:要写的数组
               off:从数组的哪个索引开始写
               len:写多少个
          */
         byte[] bytes = {97,98,99,100,101,102};
         fos.write(bytes,0,3);
         //关流
         fos.close();
     }
 }
 public class Test04_FileOutputStream {
     public static void main(String[] args) throws Exception {
         FileOutputStream fos = new FileOutputStream("day19\io\out.txt");
 ​
        // byte[] bytes = {-28, -67, -96};
         fos.write("97".getBytes());
         fos.write("你好".getBytes());
         fos.write("中国".getBytes());
         //关流
         fos.close();
     }
 }
 ​

image-20220309165519265

 //字节输出流续写追加
 ​
 FileOutputStream(String name, boolean append) ->实现追加功能
                  append:false-> 不追加,会覆盖
                  append:true-> 不覆盖了,直接追加
                      
 //换行-> 换行符
   \r\n  -> 占两个字节
      
 public class Test04_FileOutputStream {
     public static void main(String[] args) throws Exception {
         FileOutputStream fos = new FileOutputStream("day19\io\out.txt",true);
 ​
        // byte[] bytes = {-28, -67, -96};
         fos.write("97".getBytes());
         fos.write("你好".getBytes());
         fos.write("中国".getBytes());
         //关流
         fos.close();
     }
 }
 ​
 public class Test04_FileOutputStream {
     public static void main(String[] args) throws Exception {
         FileOutputStream fos = new FileOutputStream("day19\io\out.txt",true);
 ​
        // byte[] bytes = {-28, -67, -96};
         /*fos.write("97".getBytes());
         fos.write("\r\n".getBytes());
         fos.write("你好".getBytes());
         fos.write("\r\n".getBytes());
         fos.write("中国".getBytes());*/
 ​
         fos.write("你好\r\n".getBytes());
         //关流
         fos.close();
     }
 }
 ​

第四章.哈希表结构存储过程

image-20220309103115059

 1.HashMap底层数据数据结构:哈希表
 2.jdk7:哈希表 = 数组+链表
   jdk8:哈希表 = 数组+链表+红黑树
 3.
   先算哈希值,此哈希值在HashMap底层经过了特殊的计算得出
   如果哈希值不一样,直接存
   如果哈希值一样,再去比较内容,如果内容不一样,也存
   如果哈希值一样,内容也一样,直接去重复(后面的value将前面的value覆盖)
   
   哈希值一样,内容不一样->哈希冲突(哈希碰撞)
 4.要知道的点:
   a.在不指定长度时,哈希表中的数组默认长度为16,HashMap创建出来,一开始没有创建长度为16的数组
   b.什么时候创建的长度为16的数组呢?在第一次put的时候,底层会创建长度为16的数组
   c.哈希表中有一个数据加[加载因子]->默认为0.75(加载因子)->代表当元素存储到百分之75的时候要扩容了->2倍
   d.如果对个元素出现了哈希值一样,内容不一样时,就会在同一个索引上以链表的形式存储,当链表长度达到8并且当前数组长度>64时,链表就会改成使用红黑树存储
   e.加入红黑树目的:查询快
 外面笔试时可能会问到的变量
 default_initial_capacity:HashMap默认容量  16
 default_load_factor:HashMap默认加载因子   0.75f
 threshold:扩容的临界值   等于   容量*0.75 = 12  第一次扩容
 treeify_threshold:链表长度默认值,转为红黑树:8
 min_treeify_capacity:链表被树化时最小的数组容量:64

第五章.哈希表源码分析

1.HashMap无参数构造方法的分析

 //HashMap中的静态成员变量
 static final float DEFAULT_LOAD_FACTOR = 0.75f;
 public HashMap() {
     this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
 }

解析:使用无参数构造方法创建HashMap对象,将加载因子设置为默认的加载因子,loadFactor=0.75F。

2.HashMap有参数构造方法分析

 HashMap(int initialCapacity, float loadFactor) ->创建Map集合的时候指定底层数组长度以及加载因子
     
 public HashMap(int initialCapacity, float loadFactor) {
     if (initialCapacity < 0)
         throw new IllegalArgumentException("Illegal initial capacity: " +
     initialCapacity);
     if (initialCapacity > MAXIMUM_CAPACITY)
         initialCapacity = MAXIMUM_CAPACITY;
     if (loadFactor <= 0 || Float.isNaN(loadFactor))
         throw new IllegalArgumentException("Illegal load factor: " +
     loadFactor);
     this.loadFactor = loadFactor;
     this.threshold = tableSizeFor(initialCapacity);//10
 }

解析:带有参数构造方法,传递哈希表的初始化容量和加载因子

  • 如果initialCapacity(初始化容量)小于0,直接抛出异常。

  • 如果initialCapacity大于最大容器,initialCapacity直接等于最大容器

    • MAXIMUM_CAPACITY = 1 << 30 是最大容量 (1073741824)
  • 如果loadFactor(加载因子)小于等于0,直接抛出异常

  • tableSizeFor(initialCapacity)方法计算哈希表的初始化容量。

    • 注意:哈希表是进行计算得出的容量,而初始化容量不直接等于我们传递的参数。

3.tableSizeFor方法分析

 static final int tableSizeFor(int cap) {
     int n = cap - 1;
     n |= n >>> 1;
     n |= n >>> 2;
     n |= n >>> 4;
     n |= n >>> 8;
     n |= n >>> 16;
     return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
 }
 ​
 8  4  2  1规则->无论指定了多少容量,最终经过tableSizeFor这个方法计算之后,都会遵循8421规则去初始化列表容量

解析:该方法对我们传递的初始化容量进行位移运算,位移的结果是 8 4 2 1 码

  • 例如传递2,结果还是2,传递的是4,结果还是4。
  • 例如传递3,结果是4,传递5,结果是8,传递20,结果是32。

4.Node 内部类分析

哈希表是采用数组+链表的实现方法,HashMap中的内部类Node非常重要,证明HashSet是一个单向链表

  static class Node<K,V> implements Map.Entry<K,V> {
      final int hash;
      final K key;
      V value;
      Node<K,V> next;
  Node(int hash, K key, V value, Node<K,V> next) {
      this.hash = hash;
      this.key = key;
      this.value = value;
      this.next = next;
 }

解析:内部类Node中具有4个成员变量

  • hash,对象的哈希值
  • key,作为键的对象
  • value,作为值得对象(讲解Set集合,不牵扯值得问题)
  • next,下一个节点对象

5.存储元素的put方法源码

 public V put(K key, V value) {
     return putVal(hash(key), key, value, false, true);
 }

解析:put方法中调研putVal方法,putVal方法中调用hash方法。

  • hash(key)方法:传递要存储的元素,获取对象的哈希值
  • putVal方法,传递对象哈希值和要存储的对象key

6.putVal方法源码

 Node<K,V>[] tab; Node<K,V> p; int n, i;
     if ((tab = table) == null || (n = tab.length) == 0)
         n = (tab = resize()).length;

解析:方法中进行Node对象数组的判断,如果数组是null或者长度等于0,那么就会调研resize()方法进行数组的扩容。

7.resize方法的扩容计算

 if (oldCap > 0) {
      if (oldCap >= MAXIMUM_CAPACITY) {
          threshold = Integer.MAX_VALUE;
          return oldTab;
      }
      else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
               oldCap >= DEFAULT_INITIAL_CAPACITY)
          newThr = oldThr << 1; // double threshold
 }

解析:计算结果,新的数组容量=原始数组容量<<1,也就是乘以2。

8.确定元素存储的索引

 if ((p = tab[i = (n - 1) & hash]) == null)
      tab[i] = newNode(hash, key, value, null);

解析:i = (数组长度 - 1) & 对象的哈希值,会得到一个索引,然后在此索引下tab[i],创建链表对象。

不同哈希值的对象,也是有可能存储在同一个数组索引下。

 其中resize()扩容的方法,默认是16
  tab[i] = newNode(hash, key, value, null);->将元素放在数组中  i就是索引
 
  i = (n - 1) & hash
      0000 0000 0000 0000 0000 0000 0000 1111->15
                                                     &   0&0=0 0&1=0 1&1=1
      0000 0000 0000 0001 0111 1000 0110 0011->96355
 --------------------------------------------------------
      0000 0000 0000 0000 0000 0000 0000 0011->3
      0000 0000 0000 0000 0000 0000 0000 1111->15
                                                     &   0&0=0 0&1=0 1&1=1
      0000 0000 0001 0001 1111 1111 0001 0010->1179410
 --------------------------------------------------------
      0000 0000 0000 0000 0000 0000 0000 0010->2

9.遇到重复哈希值的对象

  Node<K,V> e; K k;
  if (p.hash == hash &&
     ((k = p.key) == key || (key != null && key.equals(k))))
          e = p;

解析:如果对象的哈希值相同,对象的equals方法返回true,判断为一个对象,进行覆盖操作。

 else {
      for (int binCount = 0; ; ++binCount) {
         if ((e = p.next) == null) {
             p.next = newNode(hash, key, value, null);
         if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
             treeifyBin(tab, hash);
         break;
  }

解析:如果对象哈希值相同,但是对象的equals方法返回false,将对此链表进行遍历,当链表没有下一个节点的时候,创建下一个节点存储对象。

第六章.字节流

1.InputStream子类[FileInputStream]的介绍以及方法的使用

 1.概述:字节输入流->抽象类
 2.子类:FileInputStream->读数据
 3.构造:
   FileInputStream(File file) 
   FileInputStream(String name) 
 4.方法:
   int read()->一次读一个字节
   int read(byte[] b)->一次读一个字节数组
   int read(byte[] b, int off, int len)->一次读一个字节数组一部分
            b:要读取的字节数组
            off:从数组的哪个索引开始读
            len:读多少个
   void close() -> 关流 

2.一次读取一个字节

 public class Test01_FileInputStream {
     public static void main(String[] args) throws Exception {
         FileInputStream fis = new FileInputStream("day20\io\in.txt");
         method01(fis);
 ​
     }
 ​
     /*
       int read()->一次读一个字节,返回的是读取的字节
      */
     public static void method01(FileInputStream fis) throws IOException {
         /*int read1 = fis.read();
         System.out.println((char) read1);
 ​
         int read2 = fis.read();
         System.out.println((char) read2);
 ​
         int read3 = fis.read();
         System.out.println((char) read3);
 ​
         int read4 = fis.read();
         System.out.println((char) read4);
 ​
         int read5= fis.read();
         System.out.println(read5);
 ​
         int read6 = fis.read();
         System.out.println(read6);*/
 ​
         //循环读取
         //定义一个变量,接收读取的内容
         int len;
         while((len = fis.read())!=-1){
             System.out.println((char)len);
         }
 ​
         fis.close();
     }
 }

1.循环读取的时候,不要判断的时候读一次,输出的时候再读一次

2.如果一个流对象提前关闭了,此流对象就不能再使用了

image-20220311092852978

3.IO流对象,如果将文件中的内容读完了,就不能再读了

3.读取-1的问题

 1.每个文件最后(末尾),都有一个结束标记,此结束标记看不见,摸不着

image-20220311093511748

4.一次读取一个字节数组以及过程

     /*
       int read(byte[] b)->一次读一个字节数组,返回的是读取的个数
      */
     public static void method02(FileInputStream fis) throws Exception {
        /*
          创建byte数组
 ​
          数组作用:看做是一个临时存储区域,我读取的数据会先放到数组中,然后我们从数组中拿数据
          
          数组一般长度会定成1024或者其倍数
         */
        /* byte[] bytes = new byte[2];
         int read01 = fis.read(bytes);
         //System.out.println(read01);//2 代表读了2个
         System.out.println(new String(bytes,0,read01));
 ​
         int read02 = fis.read(bytes);
         //System.out.println(read02);//2 代表读了2个
         System.out.println(new String(bytes,0,read01));
 ​
         int read03 = fis.read(bytes);
         //System.out.println(read03);//1 代表读了1个
         System.out.println(new String(bytes,0,read03));
 ​
 ​
         int read04 = fis.read(bytes);
         System.out.println(read04);*/
 ​
 ​
 ​
          byte[] bytes = new byte[5];
         //定义一个变量,表示读取的个数
         int len;
         while((len = fis.read(bytes))!=-1){
             System.out.println(new String(bytes,0,len));
         }
         fis.close();
 ​
     }

image-20220311100009024

5.字节流实现图片复制分析

image-20220311102436870

6.字节流实现图片复制代码实现

 public class Test02_CopyFile {
     public static void main(String[] args)throws Exception {
         //1.创建FileInputStream用于读取文件
         FileInputStream fis = new FileInputStream("E:\Idea\io\5.jpg");
         //2.创建FileOutputStream用于将读取到内存中的文件写到另外的位置
         FileOutputStream fos = new FileOutputStream("E:\Idea\io\file\李成敏.jpg");
         //3.定义一个数组
         byte[] bytes = new byte[1024];
         //4.定义一个变量,接收读取的字节个数
         int len;
         while((len = fis.read(bytes))!=-1){
             //5.将读取的字节写到指定位置
             fos.write(bytes,0,len);
         }
 ​
         //关流->先开后关
         fos.close();
         fis.close();
     }
 }
 ​

第七章.字符流

1.字节流读取中文的问题

 public class Test02_FileInputStream {
     public static void main(String[] args)throws Exception {
         FileInputStream fis = new FileInputStream("day20\io\in.txt");
         byte[] bytes = new byte[2];
         int len;
         while((len = fis.read(bytes))!=-1){
             System.out.println(new String(bytes,0,len));
         }
         fis.close();
     }
 }

image-20220311105528097

如果文件中存中文,读取的时候会有编码问题:

GBK:一个中文占2个字节

UTF-8: 一个中文占3个字节

注意:字节流确实是万能流,但是不要边读边看(输出)

字符流在操作文本文档时,[编码一致的情况下],边读边看,没有问题

2.FileReader的介绍以及使用

 1.字符输入流:Reader-> 抽象类
 2.子类:FileReader -> 读字符
 3.构造:
   FileReader(File file) 
   FileReader(String fileName) 
 4.方法:
   int read()->一次读取一个字符
   int read(char[] cbuf) -> 一次读取一个字符数组,返回的是读取个数 
   int read(char[] cbuf, int off, int len)  -> 一次读取一个字符数组一部分
           cbuf:要读取的字符数组
           off:从数组哪个索引开始读
           len:读多少个
    void close() -> 关流
 public class Test01_FileReader {
     public static void main(String[] args) throws Exception {
         /*
           int read()->一次读取一个字符,返回的是读取的内容
          */
 ​
         FileReader fr = new FileReader("day20\io\read.txt");
 /*        int read01 = fr.read();
         System.out.println((char) read01);
 ​
         int read02 = fr.read();
         System.out.println((char) read02);
 ​
         int read03 = fr.read();
         System.out.println((char) read03);
 ​
         int read04 = fr.read();
         System.out.println((char) read04);*/
 ​
         //int read05 = fr.read();
         //System.out.println(read05);//-1
 ​
 ​
         //循环读取
         int len;
         while((len = fr.read())!=-1){
             System.out.println((char) len);
         }
         //关流
         fr.close();
     }
 }
 ​
 public class Test02_FileReader {
     public static void main(String[] args)throws Exception {
         //int read(char[] cbuf) -> 一次读取一个字符数组,返回的是读取个数
 ​
         FileReader fr = new FileReader("day20\io\read.txt");
         //定义数组
        /* char[] chars = new char[2];
         int read01 = fr.read(chars);
         System.out.println(new String(chars));
 ​
 ​
         int read02 = fr.read(chars);
         System.out.println(new String(chars));
 */
 ​
         char[] chars = new char[4];
        //循环读取
         int len;
         while((len = fr.read(chars))!=-1){
             System.out.println(new String(chars,0,len));
         }
         //关流
         fr.close();
     }
 }
 ​

3.FileWriter的介绍以及使用

 1.概述:字符输出流:Writer -> 抽象类
 2.子类:FileWriter
 3.构造:
   FileWriter(File file) 
   FileWriter(String fileName) 
   FileWriter(String fileName, boolean append) 
              append:true-> 追加,续写
                     false-> 会创建新文件覆盖老文件
 ​
 4.方法:
   void write(int c) -> 一次写一个字符 
   void write(char[] cbuf)  -> 一次写一个字符数组
   void write(char[] cbuf, int off, int len)  -> 一次写一个字符数组一部分
   void write(String str)  -> 一次写一个字符串
   void write(String str, int off, int len)->一次写一个字符串的一部分  
   void flush()  -> 刷新缓冲区    
 5.注意:
   字符流,底层自带一个缓冲区
   我们要是写数据的话,写的数据会先放到缓冲区中,我们需要将缓冲区中的数据刷到硬盘的文件中
 public class Test01_FileWriter {
     public static void main(String[] args)throws Exception {
         FileWriter fw = new FileWriter("day20\io\write.txt",true);
         //void write(char[] cbuf)  -> 一次写一个字符数组
         char[] chars = {'中','国','你','好'};
         fw.write(chars);
         //刷新缓冲区
         //fw.flush();
 ​
         //关流-> 自带刷新功能,先刷新,后关流
         fw.close();
     }
 }
 ​
 public class Test02_FileWriter {
     public static void main(String[] args)throws Exception {
         FileWriter fw = new FileWriter("day20\io\write.txt");
 ​
         fw.write("床前明月光");
         fw.write("\r\n");
         fw.write("疑是地上霜");
         fw.write("\r\n");
         fw.write("举头望明月");
         fw.write("\r\n");
         fw.write("低头思故乡");
         fw.write("\r\n");
 ​
         fw.write("两眼泪汪汪\r\n");
         fw.write("来碗饺子汤");
 ​
         //刷新或者关流
         fw.close();
     }
 }
 ​

4.FileWriter的刷新功能和关闭功能

 flush:刷新,刷新完毕之后,流对象还能使用
 close:先刷新,后关闭,关闭之后,流对象不能再次使用
 public class Test03_FileWriter {
     public static void main(String[] args)throws Exception {
         FileWriter fw = new FileWriter("day20\io\write1.txt");
         fw.write("离离原上草");
         fw.write("一岁一枯荣");
         fw.write("野火烧不尽");
         fw.write("春风吹又生");
         //刷新
         fw.flush();
         fw.write("喜洋洋");
         fw.flush();
         //关流
         fw.close();
         fw.write("懒洋洋");//fw对象用不了了
     }
 }
 ​

5.IO异常处理的方式

 public class Test04_FileWriter {
     public static void main(String[] args) {
         FileWriter fw = null;
         try {
             fw = new FileWriter("day20\io\write2.txt");
             fw.write("翠儿");
         } catch (Exception e) {
             e.printStackTrace();
         } finally {
             if (fw!=null){//如果fw没有创建成功,依然是null,如果是null,就没必要close了
                 try {
                     fw.close();
                 } catch (IOException e) {
                     e.printStackTrace();
                 }   
             }
           
         }
     }
 }
 ​

6.JDK7之后io异常处理方式->(扩展)

 1.格式:
   try(io对象){
       可能出现异常的代码
   }catch(异常对象 对象名){
       
   }
 ​
 2.注意:用以上格式处理异常,会自动管理IO流对象
 public class Test05_FileWriter {
     public static void main(String[] args) {
        try(FileWriter fw = new FileWriter("day20\io\write2.txt")){
            fw.write("柳岩");
        }catch (Exception e){
            e.printStackTrace();
        }
     }
 }

第八章.字节缓冲流

 1.和普通的字节流有什么区别
   a.有缓冲区,默认大小8192
   b.缓冲流在读写时,都会将数据先放到缓冲区中,所以缓冲区的读写动作,不是直接调用的native方法,而是在内存中进行读写,效率会高
   c.我们使用缓冲流可以尽量减少直接对系统进行读写的操作,减少了系统资源的占用
     
     
 2.字节缓冲输出流:BufferedOutputStream
   a.构造:
     BufferedOutputStream(OutputStream out) 
                          OutputStream:抽象类
                          需要传递OutputStream的子类对象
   b.用法:
     和FileOutputStream一样
         
 3.字节缓冲输入流:BufferedInputStream
   a.构造:
     BufferedInputStream(InputStream in)
                          InputStream:抽象类
                          需要传递InputStream的子类对象
   b.用法:
     和FileInputStream一样
 public class Demo01_BufferedInAndOut {
     public static void main(String[] args)throws Exception {
         //method01();
 ​
         //method02_buffered();
     }
 ​
     private static void method02_buffered()throws Exception {
         long start = System.currentTimeMillis();
         //1.创建FileInputStream用于读取文件
         FileInputStream fis = new FileInputStream("E:\Idea\io\集合.avi");
         BufferedInputStream bis = new BufferedInputStream(fis);
 ​
 ​
         //2.创建FileOutputStream用于将读取到内存中的文件写到另外的位置
         FileOutputStream fos = new FileOutputStream("E:\Idea\io\新集合.avi");
         BufferedOutputStream bos = new BufferedOutputStream(fos);
 ​
 ​
         //3.定义一个数组
         byte[] bytes = new byte[1024];
         //4.定义一个变量,接收读取的字节个数
         int len;
         while((len = bis.read(bytes))!=-1){
             //5.将读取的字节写到指定位置
             bos.write(bytes,0,len);
         }
 ​
         long end = System.currentTimeMillis();
 ​
         System.out.println(end-start);
 ​
         //关流->先开后关
         bos.close();
         bis.close();
     }
 ​
     private static void method01()throws Exception {
         long start = System.currentTimeMillis();
         //1.创建FileInputStream用于读取文件
         FileInputStream fis = new FileInputStream("E:\Idea\io\集合.avi");
         //2.创建FileOutputStream用于将读取到内存中的文件写到另外的位置
         FileOutputStream fos = new FileOutputStream("E:\Idea\io\新集合.avi");
         //3.定义一个数组
         byte[] bytes = new byte[1024];
         //4.定义一个变量,接收读取的字节个数
         int len;
         while((len = fis.read(bytes))!=-1){
             //5.将读取的字节写到指定位置
             fos.write(bytes,0,len);
         }
 ​
         long end = System.currentTimeMillis();
 ​
         System.out.println(end-start);
 ​
         //关流->先开后关
         fos.close();
         fis.close();
     }
 }
 ​

第九章.字符缓冲流

1.字符缓冲输出流_BufferedWriter

 1.构造:
   BufferedWriter(Writer w)
 2.方法:
   和FileWriter一样
       
 3.特有方法:
   newLine()->换行
 public class Demo02_BufferedWriter {
     public static void main(String[] args)throws Exception {
         BufferedWriter bw =
                 new BufferedWriter(new FileWriter("day20\io\bufferedwriter.txt",true));
         bw.write("东临碣石");
         bw.newLine();
         bw.write("以观沧海");
         bw.newLine();
         bw.close();
     }
 }

2.字符缓冲输入流_BufferedReader

 1.构造:
   BufferedReader(Reader r)
 2.方法:
   和FileReader一样
 3.特有方法:
   String readLine()-> 一次读一行
 public class Demo03_BufferedReader {
     public static void main(String[] args) throws Exception {
         BufferedReader br = new BufferedReader(new FileReader("day20\io\bufferedreader.txt"));
        /* String s1 = br.readLine();
         System.out.println(s1);
 ​
         String s2 = br.readLine();
         System.out.println(s2);
 ​
         String s3 = br.readLine();
         System.out.println(s3);
 ​
         String s4 = br.readLine();
         System.out.println(s4);
 ​
         String s5 = br.readLine();
         System.out.println(s5);*/
 ​
 ​
         String line;
         while ((line = br.readLine()) != null) {
             System.out.println(line);
         }
         br.close();
     }
 }
 ​

3.字符缓冲流练习

 将in.txt中的内容排好序,写到另外一个新文件中
 c.侍中、侍郎郭攸之、费祎、董允等,此皆良实,志虑忠纯,是以先帝简拔以遗陛下。愚以为宫中之事,事无大小,悉以咨之,然后施行,必得裨补阙漏,有所广益。
 h.愿陛下托臣以讨贼兴复之效,不效,则治臣之罪,以告先帝之灵。若无兴德之言,则责攸之、祎、允等之慢,以彰其咎;陛下亦宜自谋,以咨诹善道,察纳雅言,深追先帝遗诏,臣不胜受恩感激。
 d.将军向宠,性行淑均,晓畅军事,试用之于昔日,先帝称之曰能,是以众议举宠为督。愚以为营中之事,悉以咨之,必能使行阵和睦,优劣得所。
 b.宫中府中,俱为一体,陟罚臧否,不宜异同。若有作奸犯科及为忠善者,宜付有司论其刑赏,以昭陛下平明之理,不宜偏私,使内外异法也。
 a.先帝创业未半而中道崩殂,今天下三分,益州疲弊,此诚危急存亡之秋也。然侍卫之臣不懈于内,忠志之士忘身于外者,盖追先帝之殊遇,欲报之于陛下也。诚宜开张圣听,以光先帝遗德,恢弘志士之气,不宜妄自菲薄,引喻失义,以塞忠谏之路也。
 i.今当远离,临表涕零,不知所言。
 f.臣本布衣,躬耕于南阳,苟全性命于乱世,不求闻达于诸侯。先帝不以臣卑鄙,猥自枉屈,三顾臣于草庐之中,咨臣以当世之事,由是感激,遂许先帝以驱驰。后值倾覆,受任于败军之际,奉命于危难之间,尔来二十有一年矣。
 g.先帝知臣谨慎,故临崩寄臣以大事也。受命以来,夙夜忧叹,恐付托不效,以伤先帝之明,故五月渡泸,深入不毛。今南方已定,兵甲已足,当奖率三军,北定中原,庶竭驽钝,攘除奸凶,兴复汉室,还于旧都。此臣所以报先帝而忠陛下之职分也。至于斟酌损益,进尽忠言,则攸之、祎、允之任也。
 e.亲贤臣,远小人,此先汉所以兴隆也;亲小人,远贤臣,此后汉所以倾颓也。先帝在时,每与臣论此事,未尝不叹息痛恨于桓、灵也。侍中、尚书、长史、参军,此悉贞良死节之臣,愿陛下亲之信之,则汉室之隆,可计日而待也。
 实现步骤:
   1.创建BufferedReader用于读取in.txt中的内容
   2.创建ArrayList集合,用于存储读取出来的内容
   3.调用Collections.sort方法,进行排序
   4.创建BufferedWriter,用于将集合中排好序的内容写到out.txt中
   5.遍历ArrayList集合,边遍历边写
   6.关流
 public class Demo04_Buffered {
     public static void main(String[] args)throws Exception {
         //1.创建BufferedReader用于读取in.txt中的内容
         BufferedReader br = new BufferedReader(new FileReader("day20\io\in.txt"));
         //2.创建ArrayList集合,用于存储读取出来的内容
         ArrayList<String> list = new ArrayList<>();
 ​
         String line;
         while((line = br.readLine())!=null){
             list.add(line);
         }
 ​
         //3.调用Collections.sort方法,进行排序
         Collections.sort(list);
         //4.创建BufferedWriter,用于将集合中排好序的内容写到out.txt中
         BufferedWriter bw = new BufferedWriter(new FileWriter("day20\io\out.txt"));
         //5.遍历ArrayList集合,边遍历边写
         for (String s : list) {
             bw.write(s);
             bw.newLine();
         }
         //6.关流
         bw.close();
         br.close();
     }
 }
 ​

第十章.转换流

1.字符编码

计算机中储存的信息都是用二进制数表示的,而我们在屏幕上看到的数字、英文、标点符号、汉字等字符是二进制数转换之后的结果。[按照某种规则,将字符存储到计算机中,称为编码] 。反之,将存储在计算机中的二进制数按照某种规则解析显示出来,称为解码 。比如说,按照A规则存储,同样按照A规则解析,那么就能显示正确的文本f符号。反之,按照A规则存储,再按照B规则解析,就会导致乱码现象。

  • 字符编码Character Encoding : 就是一套自然语言的字符与二进制数之间的对应规则。

2.字符集

  • 字符集 Charset:也叫编码表。是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等。

计算机要准确的存储和识别各种字符集符号,需要进行字符编码,一套字符集必然至少有一套字符编码。常见字符集有ASCII字符集、GBK字符集、Unicode字符集等。

1622638117998

可见,当指定了编码,它所对应的字符集自然就指定了,所以编码才是我们最终要关心的。

  • ASCII字符集

    • ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)是基于拉丁字母的一套电脑编码系统,用于显示现代英语,主要包括控制字符(回车键、退格、换行键等)和可显示字符(英文大小写字符、阿拉伯数字和西文符号)。
    • 基本的ASCII字符集,使用7位(bits)表示一个字符,共128字符。ASCII的扩展字符集使用8位(bits)表示一个字符,共256字符,方便支持欧洲常用字符。
  • ISO-8859-1字符集

    • 拉丁码表,别名Latin-1,用于显示欧洲使用的语言,包括荷兰、丹麦、德语、意大利语、西班牙语等。
    • ISO-8859-1使用单字节编码,兼容ASCII编码。
  • GBxxx字符集

    • GB就是国标的意思,是为了显示中文而设计的一套字符集。
    • GB2312:简体中文码表。一个小于127的字符的意义与原来相同。但两个大于127的字符连在一起时,就表示一个汉字,这样大约可以组合了包含7000多个简体汉字,此外数学符号、罗马希腊的字母、日文的假名们都编进去了,连在ASCII里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的"全角"字符,而原来在127号以下的那些就叫"半角"字符了。
    • GBK:最常用的中文码表。是在GB2312标准基础上的扩展规范,使用了双字节编码方案,共收录了21003个汉字,完全兼容GB2312标准,同时支持繁体汉字以及日韩汉字等。
    • GB18030:最新的中文码表。收录汉字70244个,采用多字节编码,每个字可以由1个、2个或4个字节组成。支持中国国内少数民族的文字,同时支持繁体汉字以及日韩汉字等。
  • Unicode字符集

    • Unicode编码系统为表达任意语言的任意字符而设计,是业界的一种标准,也称为统一码、标准万国码。

    • 它最多使用4个字节的数字来表达每个字母、符号,或者文字。有三种编码方案,UTF-8、UTF-16和UTF-32。最为常用的UTF-8编码。

    • UTF-8编码,可以用来表示Unicode标准中任何字符,它是电子邮件、网页及其他存储或传送文字的应用中,优先采用的编码。互联网工程工作小组(IETF)要求所有互联网协议都必须支持UTF-8编码。所以,我们开发Web应用,也要使用UTF-8编码。它使用一至四个字节为每个字符编码,编码规则:

      1. 128个US-ASCII字符,只需一个字节编码。
      2. 拉丁文等字符,需要二个字节编码。
      3. 大部分常用字(含中文),使用三个字节编码。
      4. 其他极少使用的Unicode辅助字符,使用四字节编码。
 1.如果想要不出现乱码问题,编码和解码遵循的编码规则要一样
 2.在GBK中,一个中文占2个字节
 3.在UTF-8中,一个中文占3个字节

3.转换流_InputStreamReader

 1.作用:在读取的时候,可以指定按照什么编码去读
 2.构造:
   InputStreamReader(InputStream in, String charsetName) 
                     in:抽象类,传递子类
                     charsetName:指定的编码表  不区分大小写
 3.使用:
   和FileReader一样
 public class Demo01_InputStreamReader {
     public static void main(String[] args)throws Exception {
         InputStreamReader isr = new InputStreamReader(new FileInputStream("E:\Idea\io\test.txt"),"GBK");
         int read = isr.read();
         System.out.println((char)read);
         isr.close();
     }
 }

4.转换流_OutputStreamWriter

 1.作用:按照指定的编码规则去写数据,指定什么规则,就按照什么规则去保存数据
 2.构造:
   OutputStreamWriter(OutputStream out,String charsetName)
                      out:抽象类,传递子类对象
                      charsetName:指定的编码表,不区分大小写
 3.使用:
   和FileWriter一样
 public class Demo02_OutputStreamWriter {
     public static void main(String[] args)throws Exception {
         OutputStreamWriter osw =
                 new OutputStreamWriter(new FileOutputStream("E:\Idea\io\out.txt"),"GBK");
         osw.write("你");
         osw.close();
     }
 }
 ​