1. java.io.File

  1. 分隔符
    1. 文件名分隔符: File.pathSeparator
    2. 路径分隔符: File.separator;window系统下\\为分隔符
  2. 路径
    1. 绝对路径:以盘符开始的完整路径
    2. 相对路径:相对于当前项目的根目录的简化路径
  3. 构造方法
    1. File(File parent, String child)
    2. File(String parent, String parent)
    3. File(String pathname)
  4. 获取方法
    1. public String getAbsolutePath() 返回文件或文件夹的绝对路径
    2. public String getPath() 返回的是输入给构造方法字符串,相当于 toString() 方法
    3. public String getName() 返回的是最后一个路径分隔符后的内容(文件名或文件夹名)
    4. public long length() 返回文件大小;如果File对象是一个文件夹,或文件不存在,length方法会返回0
  5. 判断方法
    1. public boolean exists() 判断构造方法中传入的路径是否存在
    2. pubic boolean isDirectory() 只有该路径存在且以文件夹结尾才返回 true,否则 false
    3. public boolean isFile() 只有该路径存在且以文件结尾才返回 true,否则 false
  6. 创建删除方法
    1. public boolean createNewFile()
      1. 传入构造方法中的路径不存在,调用该方法会抛出异常
      2. 文件存在,则不创建文件,返回 false
      3. 文件不存在,则创建文件,返回 true
    2. public boolean mkdir() 成功创建返回true,未创建返回 false
      1. 创建单级文件夹,创建多级文件夹不会报错,但无法创建
      2. 文件夹路径已存在,则不能创建
    3. public boolean mkdirs() 文件夹不存在,则可成功创建返回true;否则,未创建返回 false
    4. public boolean delete() 删除文件和文件夹(构造方法中最后一个路径分隔符后的内容)
      1. 文件夹中有内容(文件、子文件夹),文件夹将不会被删除,返回false
      2. 直接在硬盘上删除,不会走回收站,删除需谨慎
  7. 遍历方法
    下面两个方法用来遍历文件夹,如果给出的路径不是文件夹,或者路径不存在,会抛出空指针异常
    1. public String[] list() 返回String数组,来表示所有子文件夹和子文件(只是下面一级,不会深入子文件夹)
    2. public File[] listFiles() 返回File数组,来表示所有子文件夹和子文件
    3. listFiles有两个重载方法,返回满足过滤器筛选条件的文件和文件夹
      1. public File[] listFile(FileFilter filter)
      2. public File[] listFile(FilenameFilter filter)

两个接口都是函数式接口,仅有一个抽象方法 boolean accept(File pathname)boolean accept(File dir, String name);需要自己重写抽象方法,实现接口;可以用匿名内部类来实现

2. 遍历文件

  1. 递归使用前提
    1. 方法的主体不变,但是每次调用的方法不同
    2. 要有条件限定,保证递归能够停下来,否则将会出现StackOverflow异常(被调用的方法时会被压入栈,执行完毕之后才会从栈中释放,所以会导致栈内存溢出)
    3. 构造方法不能用递归!会导致栈中有无数个对象
  2. 递归访问所有子文件/文件夹```java public static void getAllFiles(File file) { if(! file.exists()) { // 判断路径是否存在

     System.out.println("the input path doesn't exist!");
    

    } else if (file.isDirectory()){ // 判断是否为文件夹

     File[] fileList = file.listFiles();
    
     if (fileList != null) {  // 判断是否是空文件夹
         for (File f : fileList) {
             String name = f.getAbsolutePath();
             if(name.toLowerCase().endsWith(".java")){
                 System.out.println(name);
             }
             getAllFiles(f);
         }
     }
    

    } } ```

  3. 使用FileFilter过滤```java public static void getAllFiles(File file) { if(! file.exists()) { // 判断路径是否存在

     System.out.println("the input path doesn't exist!");
    

    } else if (file.isDirectory()){ // 判断是否为文件夹而不是文件

     // 使用FileFilter(匿名内部类)
     File[] javaFileList = file.listFiles(new FileFilter(){
         @Override
         public boolean accept(File pathname) {
             return pathname.isFile() && pathname.getName().endsWith(".java");
         }  // 筛选出.java文件
     });
    
     // 使用FilenameFilter(匿名内部类)
     File[] classFileList = file.listFiles(new FilenameFilter(){
         @Override
         public boolean accept(File dir, String name) {
             return new File(dir, name).isFile() && name.endsWith(".class");
         }  // 筛选出.class文件
     });
    
     // 使用FileFilter(匿名内部类)
     File[] dirList = file.listFiles(new FileFilter(){  // 筛选出子文件夹
         @Override
         public boolean accept(File pathname) {
             return pathname.isDirectory();
         }
     });
    
     // 使用FilenameFilter(Lambda表达式)
     File[] dirList = file.listFiles((dir, name) -> new File(dir, name).isDirectory())
    
     if (javaFileList != null) {  // 判断是否存在.java文件
         for (File f : javaFileList) {
             System.out.println(f.getAbsolutePath());
         }
     }
     if (classFileList != null) {  // 判断是否存在.class文件
         for (File f : classFileList) {
             System.out.println(f.getAbsolutePath());
         }
     }
    
     if (dirList != null) {  // 判断是否存在子文件夹
         for (File f : dirList) {
             getAllFiles(f);
         }
     }
    

    } } ```

3. 字节流与字符流

  1. IO流概念
    1. I:Input,输入,从磁盘中读取到内存
    2. O:Output,输出,从内存中写入到硬盘进行保存
    3. “流”分为字节流和字符流,1字符占2字节,1字节占8位
      1. 字节输入流:InputStream
      2. 字节输出流:OutputStream
      3. 字符输入流:Reader
      4. 字符输出流:Writer
  2. 字节数出流 java.io.FileOutputStream
    1. 构造方法:创建一个FileOutputStream对象(需要处理 FileNotFoundException,直接throw IOException),根据构造方法中传递的文件名创建文件,并将FileOutputStream对象指向创建好的文件
      1. FileOutputStream(String name) 覆盖写
      2. FileOutputStream(File file)覆盖写
      3. FileOutputStream(String name, boolean append) 追加写
      4. FileOutputStream(File file, boolean append) 追加写
    2. 写数据的流程:java program --> JVM --> Operation System --> OS writes data to the file。将二进制数据的字节写入文件,用记事本打开文件之后,会自动查询ASCII码表或GBK码表,将字节数据转化为字符数据来展示。
    3. 写入方法
      1. public void write(int b) 输入的字节是正数(0~127)查询ASCII码表,如果为负数查询GBK汉字码表
      2. public void write(byte[] b) 可以先用 String 对象的public byte[] getBytes()将字符串转换为字节数组,再写入文件
      3. public void write(byte[] b, int off, int len) 写入字符数组的一个片段
    4. public void close() 释放资源
    5. public void flush()
  3. 字节输入流 java.io.FileInputStream
    1. 构造方法:创建一个FileInputStream对象(throw IOException),根据构造方法中传递的文件名创建文件,并将FileInputStream对象指向创建好的文件
    2. 读取数据的流程:java program --> JVM --> Operation System --> OS reads data from the file
    3. public int read() 每次读取一个字节并返回字节对应的整形数据,读取到文件末尾返回`-1````java FileInputStream fis = new FileInputStream(file);

int i = 0; // 需要一个变量来接受读取字节数据(int类型) while((i = fis.read())!= -1){ // 读取到-1时,停止读取 System.out.print((char)i); }

fis.close();


   4. `public int read(byte[] b)` 读取一定数量的字节数据,存放到字节数组中```java
FileInputStream fis = new FileInputStream(file);

int len = (int) file.length();  // 获取文件的字节数
byte[] bytes = new byte[len];  // 构造存放数据的字节数组
int num = fis.read(bytes);  // 将file中的前NUM个字节读入,并且返回读入的字节数量

for (byte b : bytes) {  // 打印读入的字符
    System.out.print((char)b);
}

System.out.println(new String(bytes));  // 将字节数组转化为字符串输出
  1. 如果字节数组的维度大于文件中的字节数,多余的位置全部为0;此时返回有效读取的字节数目,而不是字节数组的维度
  2. 如果没有读入有效的字节数据,返回-1
  3. 字节数组起到缓存作用,可以每次读取多个字节
  1. public void close()

    1. 使用字节输入输出流复制文件```java public static void main(String[] args) throws IOException { File fromFile = new File(“C:\Users\LAU Sen\Desktop\白色恐怖时期的台大校长傅斯年.pdf”); File toFile = new File(“C:\Users\LAU Sen\Desktop\(copy)白色恐怖时期的台大校长傅斯年.pdf”);

    FileInputStream fis = new FileInputStream(fromFile); // 文件输入流,读取文件 FileOutputStream fos = new FileOutputStream(toFile); // 文件输出流,写入文件

    int len = (int) fromFile.length(); // 文件占用字节数 byte[] bytes = new byte[len]; // 保存字节数据

    int num = fis.read(bytes); // 读取字节数据 fos.write(bytes); // 写入字节数据

    fis.close(); // 释放资源 fos.close(); // 释放资源 } ```

  1. 字符输入流 java.io.FileReader
    1. 使用字节流读取中文文件时,可能会出现无法显示文本的情况(GBK编码占用2个字节,UTF-8占用3个字节),因为无法正确地将字节转化为字符;所以直接读取字符就显得有意义了
    2. 其中的方法与字节输入流大同小异
      1. 构造方法
        1. FileReader(String fileName)
        2. FileReader(File file)
      2. 读取方法
        1. int read() 返回的int是字节数据
        2. int read(cahr[] cbuf)返回的int是读取的有效长度
      3. void close()
  2. 字符输出流 java.io.FileWriter
    1. 构造方法
      1. FileWriter(String fileName)
      2. FileWriter(File file)
      3. FileWriter(String fileName, boolean append)
      4. FileWriter(File file, boolean append)
    2. 写入方法
      1. void write(int c) 写入一个字节数据对应的字符
      2. void write(char[] cbuf) 写入多个字符java char[] cbuf = new char[(int) file.length()]; fr1.read(cbuf);
        上书写法是不合理的,因为file.length()获得的是字节数目,而不是字符数量;字符数组是装不满的
      3. void write(char[] cbuf, int off, int len)写入字符数组中的一部分
      4. void write(String str) 写入一个字符串
      5. void write(String str, int off, int len)写入字符串中的一部分
    3. void flush() 刷新流的缓冲
    4. void close()
    5. 字符输出流与字节数出流有所不同,FileWriter中的writer方法,将数据写入内存缓冲区。在此处,字符转化成字节。如果不调用flush方法,内存缓冲区中的字节数据将不会被刷新到文件中。当然,close方法也可以在关闭文件之前将缓冲区中的字节数据刷新到文件中。
  3. 异常处理java try{ 可能出现异常的代码块 } catch(异常类 变量名) { 异常处理的逻辑 } finally { 一定会执行的代码 释放资源 }

4. java.util.Properties

  1. 背景
    1. java.util.Map 有实现类 java.util.Hashtable,已经甚少使用
    2. java.util.Hashtable 的子类 java.util.Properties作为唯一与IO流相关的属性集合,依然活跃
    3. Properties依然是双列集合,key和value都是字符串
  2. 方法

    1. 特有方法
      1. Object setProperty(String key, String value) 相当于Map的put方法
      2. String getProperty(String key) 相当于Map 的get方法,通过key获取value
      3. Set<String> stringPropertyNames() 返回属性列表中的所有键的集合;相当于Map的keySet方法
    2. load方法将硬盘中的文件(键值对)读取到集合中使用
    3. void load(Reader, reader)处理字符输入流,可以处理中文数据
    4. void load(InputStream in) 处理字节输入流,不能读取含中文的数据```java public static void main(String[] args) throws IOException { File file = new File(“D:\xxxx.txt”);

      FileReader fr = new FileReader(file); FileInputStream fos = new FileInputStream(file);

      prop.load(fr); // 载入数据

      Set set = prop.stringPropertyNames(); // 获取key的集合 for (String s : set) { System.out.println(“key=”+ s + “; value=” + prop.getProperty(s)); // 根据key来获取value } } ```

    5. store方法将集合中的临时数据,持久化写到硬盘中存储

    6. void store(Writer writer, String comments)处理字符输出流,可以写中文
    7. void store(OutputStream out, String comments) 处理字节输出流,不可以写中文;默认使用unicode,因此comments也不能写中文```java public static void main(String[] args) throws IOException { File file = new File(“D:\xxxx.txt”);

      FileWriter fw = new FileWriter(file); Properties prop = new Properties(); prop.setProperty(“张三”, “123”); // 向Properties对象中添加键值对 prop.setProperty(“李四”, “456”); prop.setProperty(“王五”, “789”);

      prop.store(fw, “save data”); } ```

  3. 其他

    1. 键值对存储的文件中,键与值默认的连接符号可以用=空格:空格
    2. 键值对存储的文件中,可以用#进行注释,备注是的键值对将不会被读取
    3. 键值对存储的文件中,键值对默认都是字符串,不必再加引号

5. 缓冲流

  1. 缓冲流,也叫高效流。与四个基本的流一一对应:
    1. 字节输入缓冲流:BufferedInputStream(InputStream in, int size)
    2. 字节输出缓冲流:BufferedOutputStream(OutputStream out, int size)
    3. 字符输入缓冲流:BufferedReader(Reader in, int size)
      1. 还有String readLine()读取一行数据,返回一个字符串
      2. 其余方法与FileReader相同
    4. 字符输出缓冲流:BufferedWriter(Writer out, int size)
      1. void newLine()写入一个行分隔符
      2. 其余方法与FileWriter相同
  2. 输出缓冲流可以增加缓冲区,提高写入效率;可以传递参数,指定缓冲流内部缓冲区的大小```java public static void main(String[] args) throws IOException { FileOutputStream fos = new FileOutputStream(“\xxx.txt”, true); BufferedOutputStream bos = new BufferedOutputStream(fos);

    bos.write(“这是一个中文字符串\n”.getBytes()); // 调用BufferedOutputStream对象的write方法;传入字节数组 bos.flush(); bos.close();

    FileWriter fw = new FileWriter(“\yyy.txt”, true); BufferedWriter bw = new BufferedWriter(fw);

    bw.write(“This is a English string”.toCharArray()); // 调用BufferedWriter对象的write方法; 传入字符数组 bw.newLine(); bw.flush(); bw.close(); // 只需要关闭缓冲流,而不需要关闭原有的字节流和字符流 } ```

  3. 输入缓冲流可以增加缓冲区,提高读取效率;也可以指定缓冲流内部缓冲区的大小
    使用Arrays.copyOfRange(cbuf, 0, len)来获取字符数组中的有效字符数据,字节数组也可以用该方法。```java public static void main(String[] args) throws IOException {

    FileInputStream fis = new FileInputStream(“\xxx.txt”); BufferedInputStream bis = new BufferedInputStream(fis, 10);

    int i = 0; while((i=bis.read())!=-1) { // 不适合处理中文字符

     System.out.print((char)i);
    

    }

    FileReader fr = new FileReader(“\yyy.txt”); BufferedReader br = new BufferedReader(fr, 10);

    char[] cbuf = new char[3]; int len = 0; while((len=br.read(cbuf))!=-1) { // 可以处理中文

     for (char c : Arrays.copyOfRange(cbuf, 0, len)) {
         System.out.print((char)c);
     }
    

    } } ```

  4. 其他

    1. 只需要关闭缓冲流,而不需要关闭原有的字节流和字符流
    2. 处理中文文本数据,首选Writer、Reader;而非InputStream、OutputStream
    3. 使用字符/字节数组缓冲,或者用BufferedXxx系列的对象实现缓冲,都能提高读取效率
    4. 复制文件```java public static void main(String[] args) throws IOException { String fromPath = “D:\Video\Documentary\赵普 下一站看风景.mp4”; String toPath = “D:\Video\Documentary\(copy)赵普 下一站看风景.mp4”;

      FileInputStream fis = new FileInputStream(fromPath); FileOutputStream fos = new FileOutputStream(toPath); BufferedInputStream bis = new BufferedInputStream(fis); BufferedOutputStream bos = new BufferedOutputStream(fos);

      long timeBgn = System.currentTimeMillis();

      int i = 0; while((i=bis.read())!=-1) { bos.write(i); } bis.close(); bos.close();

      // 使用字节数组缓冲 int len = 0; byte[] bytes = new byte[1024]; while((len=bis.read())!=-1) { bos.write(bytes, 0, len); } bis.close(); bos.close(); long timeEnd = System.currentTimeMillis();

      System.out.println(“拷贝数据用时:” + (timeEnd-timeBgn)); } ```

  5. 对文本根据序号排序```java public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new FileReader(“in.txt”)); BufferedWriter bw = new BufferedWriter(new FileWriter(“out.txt”));

    Properties prop = new Properties();

    String line; while((line = br.readLine())!= null) {

     String[] arr = line.split("\\. ");
     prop.setProperty(arr[0], arr[1]);
    

    }

    for (String key : prop.stringPropertyNames()) {

     String value = prop.getProperty(key);
     bw.write(key + ". " + value);
     bw.newLine();
    

    }

    br.close(); bw.close(); } ```

    1. 不关闭bufferedWriter不会写入数据
    2. 还是要熟悉字符串的操作方法

6. 转换流

  1. 字符集
    1. ASCII:英文字母、阿拉伯数字、标点符号
    2. GBK:GB=国标,对中文双字节编码;支持繁体中文、日韩汉字;
    3. Unicode:UTF-8, UTF-16, UTF-32;最多使用四个字节来表达字符,最常用的UTF-8用三个字节编码中文
  2. FileReader能够读取IDE默认的UTF-8编码,但是系统中的文本文件会使用中文系统默认的GBK编码格式,此时会产生乱码。
  3. 转换流
    1. InputStreamReader使用指定的字符集(Charset)将字节解码为字符;而FileReader只会查询IDE默认的UTF-8码表
    2. OutputStreamWriter 使用指定的字符集,将字符编码为字节存入文件;而FilerWriter只会查询IDE默认的UTF-8码表
  4. 举例```java public static void writeUtf8() throws IOException { OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(“utf_8.txt”),
                                                 "utf-8");  // 指定Charset
    
    osw.write(“知我者谓我心忧”); osw.flush(); osw.close(); }

public static void readUtf8() throws IOException { InputStreamReader isr = new InputStreamReader(new FileInputStream(“utf_8.txt”), “utf-8”); // 指定Charset char[] chars = new char[3]; int len = 0; while((len = isr.read(chars))!= -1) { System.out.print(Arrays.copyOfRange(chars, 0, len)); } isr.flush(); isr.close(); }

public static void writeGBK() throws IOException { OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(“gbk.txt”), “gbk”); // 指定Charset osw.write(“不知我者谓我何求”); osw.flush(); osw.close(); }

public static void readGBK() throws IOException { InputStreamReader isr = new InputStreamReader(new FileInputStream(“gbk.txt”), “gbk”); // 指定Charset char[] chars = new char[3]; int len = 0; while((len = isr.read(chars))!= -1) { System.out.print(Arrays.copyOfRange(chars, 0, len)); } isr.flush(); isr.close(); }


5. 注意事项
   1. 记得释放资源
   2. 转换流的构造方法中需要传入相应的**字节流类**,而不是File对象,或地址
   3. 可以通过转换流来转换编码格式(A编码格式读入,B编码格式写入)

<a name="b2e062df"></a>
## 7. 序列化流与反序列化流

1. 概念
   1. 对象的序列化/写对象:以流的方式,将对象写入文件,保存为字节文件。
   2. 对象的反序列化/读对象:以流的方式,将对象从文件中读取出来。
2. 对象的序列化流 `ObjectOutputStream`
   1. 创建`ObjectOutputStream`对象,在构造方法中传入字节输出流`OutputStream`对象
   2. 使用`writeObject方法`,把对象写入文件
   3. 释放资源
3. 对象的反序列化流 `ObjectInputStream`
   1. 创建`ObjectInputStream`对象,在构造方法中传入字节输出流`InputStream`对象
   2. 使用`readObject`方法,把对象读入内存
   3. 释放资源
```java
public static void main(String[] args) throws IOException, ClassNotFoundException {

    // 写入
    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.txt"));

    ArrayList<Student> students = new ArrayList<>();
    students.add(new Student("刘森", 22));
    students.add(new Student("刘方", 23));

    oos.writeObject(students);

    oos.flush();  // 刷新缓存
    oos.close();  // 释放资源

    // 读取
    ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.txt"));

    ArrayList<Student> arr = (ArrayList<Student>) ois.readObject();  // 强制类型转换 Object --> ArrayList<Student>
    for (Object o : arr) {
        System.out.println((Student) o);
    }

    ois.close();  // 释放资源
}
  1. 其他
    1. 如果对象的类没有实现Serializable接口,则无法序列化与反序列化,抛出NotSerializableException异常;只需要implements Serializable
    2. 反序列化过程中,如果JVM找不到对象所对应的类,将抛出ClassNotFoundException异常
    3. 反序列化过程中,如果class文件被修改,类与原来有所不同,会抛出InvalidClassException异常
      1. 类包含位置数据类型
      2. 类没有可访问的无参构造方法
      3. 类的serialVersionUID与保存的文件中的serialVersionUID不匹配(常因为修改了类,导致重新编译了Class文件中生成了新的序列号)
    4. 可以强制private static final long serialVersionUID = 12345L,强制使得即使类被改变,序列版本号也不会被修改
    5. 想要保存多个对象,可以将对象保存到ArrayList中,通过ObjectOutputStream保存ArrayList对象
    6. transient关键字
      1. static修饰过的变量,优先于对象加载到内存中,不能被序列化
      2. 如果不想成员变量被序列化,就可以用transient关键字来修饰