文件

文件就是保存数据的地方。

  • 流: 数据在数据源(文件)和程序(内存)之间经历的路径
  • 输入流:数据从数据源(文件)到程序(内存)的路径
  • 输出流: 数据从程序(内存)到数据源(文件)的路径

常用的文件操作

  • new File(String pathname): 根据路径构建一个 File 对象
  • new File(File parent, String child): 根据父目录文件+子路径构建
  • new File(String parent, String child): 根据父目录+子路径构建
import org.junit.Test;

import java.io.File;
import java.io.IOException;

// 演示创建文件
public class FileCreate {
    public static void main(String[] args) {

    }

    // 方式1
    @Test
    public void create01() {
        String filePath = "f:\\news1.txt";
        File file = new File(filePath);
        try {
            // 执行了这个方法才会在磁盘创建
            file.createNewFile();
            System.out.println("文件创建成功");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    // 方式2
    @Test
    public void create02() {
        File parentFile = new File("f:\\");
        String fileName = "news2.txt";
        File file = new File(parentFile, fileName);
        try {
            file.createNewFile();
            System.out.println("创建成功02");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    // 方法3
    public void create03() {
        String parentPath = "f:\\";
        String fileName = "news03.txt";
        File file = new File(parentPath, fileName);

        try {
            file.createNewFile();
            System.out.println("03 创建成功");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

获取文件的信息

import org.junit.Test;

import java.io.File;

public class FileInformation {
    public static void main(String[] args) {

    }

    // 获取文件的信息
    @Test
    public void info() {
        File file = new File("f:\\news1.txt");
        // 得到对应的信息
        System.out.println("文件名: "+ file.getName());
        System.out.println("绝对路径: "+ file.getAbsolutePath());
        System.out.println("父级目录: "+ file.getParent());
        // UTF-8 一个中文是3个字节
        System.out.println("文件大小(字节):" + file.length());
        System.out.println("文件是否存在: "+ file.exists());
        System.out.println("文件是否是文件: "+ file.isFile());
        System.out.println("文件是否是目录:"+ file.isDirectory());
    }
}

文件夹操作

import org.junit.Test;

import java.io.File;

public class Directory_ {
    public static void main(String[] args) {

    }

    // 判断 F:\\news1.txt 是否存在, 如果存在就删除
    @Test
    public void m1() {
        String filePath = "F:\\news1.txt";
        File file = new File(filePath);
        if (file.exists()){
            if (file.delete()) {
                System.out.println("删除成功");
            } else {
                System.out.println("删除失败");
            }
        } else {
            System.out.println("该文件不存在");
        }
    }

    // 在 Java 编程中, 目录也当成文件
    // 判断
    @Test
    public void m2() {
        String filePath = "F:\\filedemo";
        File file = new File(filePath);
        if (file.exists()){
            if (file.delete()) {
                System.out.println("删除成功");
            } else {
                System.out.println("删除失败");
            }
        } else {
            System.out.println("该目录不存在");
        }
    }

    // 判断文件夹是否存在, 如果不存在就创建
    // 创建多级目录要用 mkdirs
    // 创建一级目录用 mkdir 就行
    @Test
    public void m3() {
        String directoryPath = "F:\\demo2\\a\\b\\c";
        File file = new File(directoryPath);
        if (file.exists()) {
            System.out.println(directoryPath + "已存在");
        } else {
            if (file.mkdirs()) {
                System.out.println("文件夹创建成功");
            } else {
                System.out.println("文件夹创建失败");
            }
        }
    }
}

IO

IO 流原理及流的分类

  • 按操作数据单位分为: 字节流(8bit,二进制文件), 字符流(按字符, 文本文件)
  • 按数据流的流向分为: 输入流, 输出流
  • 按流的角色分为: 节点流, 处理流/包装流
字节流 字符流
输入流 InputStream Reader
输出流 OutputStream Writer

InputStream

  • FileInputStream: 文件输入流
  • BufferedInputStream: 缓冲字节输入流
  • ObjectInputStream: 对象字节输入流
import org.junit.Test;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class FileInputStream_ {
    public static void main(String[] args) {

    }

    // 演示读取文件
    // 单个字节的读取, 效率比较低
    @Test
    public void readFile01() {
        String filePath = "F:\\hello.txt";
        int readData = 0;
        FileInputStream fileInputStream = null;
        try {
            // 创建这个对象用于读取文件
            fileInputStream =  new FileInputStream(filePath);
            // 从该输入流中读取一个字节的数据, 如果没有输入可用, 此方法将被阻止
            while ((readData = fileInputStream.read()) != -1) {
                System.out.print((char) readData);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            // 关闭文件流, 释放资源
            try {
                fileInputStream.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

    // 使用 read(byte[]) 进行优化
    @Test
    public void readFile02() {
        String filePath = "F:\\hello.txt";
        int readLen = 0;
        // 字节数组
        byte[] buf = new byte[8];
        FileInputStream fileInputStream = null;
        try {
            // 创建这个对象用于读取文件
            fileInputStream =  new FileInputStream(filePath);
            // 如果读取完毕返回 -1
            // 如果读取正常, 返回实际读取的字节数
            while ((readLen = fileInputStream.read(buf)) != -1) {
                System.out.print(new String(buf,0, readLen)); // 转成
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            // 关闭文件流, 释放资源
            try {
                fileInputStream.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

OutPutStream

  • FileOutputStream
import org.junit.Test;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class FileOutputStream01 {

    public static void main(String[] args) {
    }

    @Test
    public void writerFile() {
        // 创建 FileOutputStream 对象
        String filePath  = "F:\\a.txt";
        FileOutputStream fileOutputStream = null;

        try {
            // 得到 FileOutputStream 对象
            // 这种方式会覆盖原来的内容
            // 如果希望是追加的话, new FileOutputStream(filePath, true)
            fileOutputStream = new FileOutputStream(filePath);
            // 写入一个字节
            fileOutputStream.write('a');
            // 写入一个字符串
            fileOutputStream.write("Hello,world".getBytes());
            // 另一种方法, start(offset) 和 length
            fileOutputStream.write("000start,end000".getBytes(), 3, 9);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            try {
                fileOutputStream.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

文件拷贝

主要就是边读边拷.

import org.junit.Test;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class FileCopy {

    public static void main(String[] args) {

    }

    // 完成文件拷贝
    //
    @Test
    public void copyFile() {
        // 将F:\\a.txt 拷贝到 E:\\
        // 如果文件大, 应该是读取部分数据, 就写入到指定的文件.
        String filePath = "f:\\bun.mp4";
        String outputFilePath = "f:\\bun2.mp4";
        FileInputStream fileInputStream = null;
        FileOutputStream fileOutputStream= null;
        int readLen = 0;
        try {
            fileInputStream = new FileInputStream(filePath);
            fileOutputStream = new FileOutputStream(outputFilePath, true);

            // 定义一个字节数组
            byte[] buf = new byte[1024];

            while((readLen = fileInputStream.read(buf)) != -1) {
                fileOutputStream.write(buf,0 ,readLen);
            }
            System.out.println("拷贝成功");

        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            try {
                if (fileInputStream != null){
                    fileInputStream.close();
                }
                if (fileOutputStream != null){
                    fileOutputStream.close();
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }

        }
    }
}

FileReader

// 单个字符读取文件
public class FileReader_ {
    public static void main(String[] args) throws IOException {

        String filePath = "F:\\a.txt";
        int data;
        try (FileReader fileReader = new FileReader(filePath)) {
            // 循环读取
            while ((data = fileReader.read()) != -1) {
                System.out.print((char) data);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Test
    public void readFile02() {
        String filePath = "F:\\a.txt";
        char[] buf =new char[4];
        int readLen;
        try (FileReader fileReader = new FileReader(filePath)) {
            // 循环读取
            while ((readLen = fileReader.read(buf)) != -1) {
                System.out.print(new String(buf,0,readLen));
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

FileWriter

fileWrite 已经要关闭(close)或者刷新(flush)

import org.junit.Test;

import java.io.FileWriter;
import java.io.IOException;

public class FileWriter_ {
    public static void main(String[] args) {

    }

    @Test
    public void writeFile01() throws IOException {
        String filePath = "F:\\a.txt";

        // 这个写法有点像 python 的
        // with open
        try (FileWriter fileWriter = new FileWriter(filePath)) {
            // 写入单个字符 write(int)
            fileWriter.write("L");
            // 写入指定数组 write(char[])
            char[] buf = {'U','O' };
            fileWriter.write(buf);
            // 写入指定数组的指定部分 write(char[], off, len)
            fileWriter.write(" file",0,5);
            // write(string) 写入整个字符串
            fileWriter.write(" test");
            // write(string, off,len)
            fileWriter.write(" textaaaaaaaaaaaaaaa",0,5);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

节点流和处理流

  1. 节点流可以从一个特定的数据源读取数据,如 FileReader, FileWriter
  2. 处理流(也叫包装流)是“连接”再已经存在的流(节点流或处理流)之上, 为程序提供更加强大的独写功能, 也更加灵活, 如 BufferedReader, BufferedWriter

常见的节点流可以分为:

  1. 字节输入流
  2. 字节输出流
  3. 字符输入流
  4. 字符输出流

可以根据访问的数据源分类

  1. 访问文件
  2. 访问数组
  3. 访问管道
  4. 访问字符串

节点流和处理流的区别和联系

  1. 节点流是底层流, 直接与数据源相关
  2. 处理流(包装流)包装节点流, 既可以消除不同节点流的实现差异, 也可以提供更方便的方法来完成输入输出.
  3. 处理流(包装流)对节点流进行包装, 使用了修饰器设计模式, 不会直接与数据源相连.

处理流的功能

  1. 性能的提高: 主要增加缓冲的方式来提高输入输出的效率.
  2. 操作的便捷: 处理流可能提供了一系列便捷的方法来一次输入输出大批量的数据, 使用更加灵活方便.

处理流

BufferedReaderBufferedWriter 属于字符流, 是按照字符来读取数据的.
关闭处理流时, 只需要关闭外层流即可.

public class BufferedReader_ {

    @Test
    public void readFile01() throws IOException {
        BufferedReader bufferedReader = new BufferedReader(new FileReader("F:\\a.txt"));
        // 读取
        String line;
        // 1. BufferedReader.readLine() 是按行读取文件
        // 2. 当返回 null 时, 表示文件读取完毕.
        while((line = bufferedReader.readLine()) != null){
            System.out.println(line);
        }
        // 关闭流, 只需要关闭 BufferedReader, 底层源码会自己去关闭被包装的流.
        bufferedReader.close();
    }
}

public class BufferedWriter_ {

    @Test
    public void writeFile01() throws IOException {
        // 如果要追加模式应当在 FileWriter 里面构造的时候 FileWriter("F:\\b.txt", true)
        // 应该很容易理解吧.
        BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("F:\\b.txt"));
        bufferedWriter.write("你好");
        bufferedWriter.write("测试会不会换行");
        // 看来不会换行
        bufferedWriter.newLine();
        // 这个是换行符, 是根据系统相关的换行符.
        bufferedWriter.write("新的一行");
        bufferedWriter.close();
    }
}

文件拷贝

public class BufferedCopy_ {

    @Test
    public void copyFile01() throws IOException {
        BufferedReader bufferedReader = new BufferedReader(new FileReader("F:\\a.txt"));
        String destFilePath = "F:\\demo\\b.txt";
        BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(destFilePath));
        String line;
        while ((line = bufferedReader.readLine()) != null) {
            bufferedWriter.write(line);
            // 记得插入换行符
            bufferedWriter.newLine();
        }
        bufferedWriter.close();
        bufferedReader.close();
    }
}

不要用 Reader, Writer 去拷贝二进制文件, 图片, 视频等, 试过了真的会坏.

二进制文件用字符流, 看下面

@Test
public void copyFile02() throws IOException {
    BufferedInputStream bufferedInputStream = null;
    BufferedOutputStream bufferedOutputStream = null;

    String srcPath = "F:\\pyy.PNG";
    String destPath = "F:\\demo\\pyy.PNG";
    byte[] buff = new byte[1024];
    try {
        bufferedInputStream = new BufferedInputStream(new FileInputStream(srcPath));
        bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(destPath));
        int readLen = 0;
        // 当 readLen  为 -1 时就表示读取完毕
        while ((readLen = bufferedInputStream.read(buff)) != -1){
            bufferedOutputStream.write(buff, 0, readLen);
        }
    } catch (IOException e){
        e.printStackTrace();
    } finally {
        if (bufferedInputStream != null){
            bufferedInputStream.close();
        }
        if (bufferedOutputStream != null) {
            bufferedOutputStream.close();
        }
    }
}

对象流

有时候需要把数据保存到文件中, 把对象保存到文件中, 因此需要ObjectOutputStream, 需要保存数据值和数据类型, 就是要能够将对象进行序列化和反序列化.

为了让某个对象支持序列化机制, 就必须让这个类时可序列化的, 为了让某个类时可序列化的, 该类必须实现如下两个接口之一:

  • Serializable(一般用这个)
  • Externalizable(因为这个有方法需要实现)
public class ObjectOutputStream_ {

    @Test
    public void saveObject() throws IOException {
        // 序列化后保存的文件格式不是文本, 而是他的方法
        String filePath = "F:\\object.dat";
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(filePath));

        // Integer 实现了 Serializable 接口
        objectOutputStream.writeInt(100);
        // 放字符串不是用 writeString
        objectOutputStream.writeUTF("你好");
        objectOutputStream.writeObject(new Person("allison"));
        objectOutputStream.close();
    }

    @Test
    public void readObject() throws IOException, ClassNotFoundException {
        String filePath = "F:\\object.dat";
        ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(filePath));

        // 读取的顺序(反序列化的顺序), 需要和保存数据的顺序一致
        System.out.println(inputStream.readInt());
        System.out.println(inputStream.readUTF());
        // 就算这个类的定义一模一样, 但是如果不在同一个包下也会有问题, 比如我在别的包下定义的 Person 类,
        // 哪怕代码一模一样, 也无法读取, 因为所在的包不一样.
        Object p = inputStream.readObject();
        System.out.println("类型: "+p.getClass());
        System.out.println(p);

    }
}

class Person implements Serializable {
    private String name;

    public Person(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

//    @Override
//    public String toString() {
//        return "Person{" +
//                "name='" + name + '\'' +
//                '}';
//    }
}

注意:

  1. 独写顺序要一致
  2. 要求序列化或者反序列化对象, 需要实现 Serializable
  3. 序列化的类中建议添加 SerialVersionUID, 为了提高版本的兼容性
  4. 序列化对象时, 默认将里面所有属性都进行序列化, 但除了 static 或 transient 修饰的成员.
  5. 序列化对象时, 要求里面属性的类型也需要实现序列化接口
  6. 序列化具备可继承性, 也就是如果某类已经实现了序列化, 则它的所有子类也已经默认实现了序列化.

标准输入输出流

public class InputAndOutput {

    @Test
    public void aaa(){
        // System.in
        // 编译类型 InputStream
        // 运行类型 BufferedInputStream
        System.out.println(System.in.getClass());
        // Scanner scanner = new Scanner(System.in);
        // scanner.nextLine();

        // 1. System.out
        // 编译类型 PrintStream
        // 运行类型 PrintStream
        // 表示输出到显示屏上
        System.out.println(System.out.getClass());
    }
}

转换流

InputStreamReader: Reader 的子类, 可以将 InputStream(字节流)包装成Reader(字符流)

OutputStreamWriter: Writer 的子类, 可以将 OutputStream(字节流)包装成 Writer(字符流)

常用于不同编码方式的读取

public class CodeQuestion {

    @Test
    public void readFile01() throws IOException {
        // 读取 F:\\a.txt
        // 1. 创建一个字符输入流 BufferedReader [处理流]
        // 2. 使用 BufferedReader 读取 a.txt
        // 3. 在默认情况下, 读取文件时按照 UTF-8 进行读取的, 当文件不是用 UTF-8 保存的时候, 可能会出现乱码
        //
        String filePath = "F:\\a.txt";
        BufferedReader bufferedReader = new BufferedReader(new FileReader(filePath));

        String s = bufferedReader.readLine();
        while ((s = bufferedReader.readLine()) != null){
        System.out.println("读取到的内容: "+s);}
        bufferedReader.close();
        // 如果不是 UTF-8 就可能会乱码
    }

    @Test
    public void readFile02() throws IOException {
        String filePath = "F:\\a.txt";
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(filePath), "gb18030"));
        String s = bufferedReader.readLine();
        while ((s = bufferedReader.readLine()) != null){
            System.out.println("读取到的内容: "+s);}
        bufferedReader.close();
        // 指定了编码就可以了, 就不会乱码了
    }

    @Test
    public void saveFile01() throws IOException {
        String filePath = "F:\\c.txt";
        BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(Files.newOutputStream(Paths.get(filePath)), "gbk"));

        bufferedWriter.write("你好!");
        bufferedWriter.newLine();
        bufferedWriter.write("Hello");
        bufferedWriter.close();
    }
}

打印流

打印流只有输出流没有输入流: PrintWriter, PrintStream

public class Print {

    @Test
    public void printStream() throws IOException {
        PrintStream out = System.out;
        // 默认情况下, PrintStream 输出数据的位置是 标准输出, 即显示器
        out.println("你好");
        // 基本等价于
        // out.write("你好".getBytes());
        // 好像不能直接用 newLine
        // out.newLine(); 因为 newLine 是 private 方法
        out.close();
        System.setOut(new PrintStream("F:\\d.txt"));
        System.out.println("输出到文件");

    }
    @Test
    public void printWriter() throws IOException {
        // PrintWriter(Writer)
        // PrintWriter(OutputStream)
        // PrintWriter(Charset, File)
        PrintWriter printWriter = new PrintWriter(new FileWriter("F:\\e.txt"));
        printWriter.println("你好 print writer");
        // 这时候才真的写数据, 可以看源码, 也可以debug到这里看 e.txt 有没有内容
        printWriter.close();
    }
}

Properties 类

public class Properties_ {

    @Test
    public void readProperties01() throws IOException {
        BufferedReader bufferedReader = new BufferedReader(new FileReader("src\\mysql.properties"));
        String line;
        while((line = bufferedReader.readLine())!=null) {
            System.out.println("line: " + line);
        }
        bufferedReader.close();
        // 但是这样效果不好,也不方便
    }

    @Test
    public void readProperties02() throws IOException {
        // 使用 Properties 类来读取 mysql.properties 文件
        // 1. 创建 Properties 对象
        String filePath = "src\\mysql.properties";
        Properties properties = new Properties();
        // 2. 加载指定配置文件
        properties.load(new FileReader(filePath));
        // 3. 把 k-v 显示在控制台
//        properties.list(System.out);
        // 4. 根据 key 获取对应的值
        System.out.println("IP: " + properties.getProperty("ip"));
    }

    @Test
    public void modifiedProperties() throws IOException {
        // 1. 创建 Properties 对象
        String filePath = "src\\mysql.properties";
        Properties properties = new Properties();
        // 2. 加载指定配置文件
        properties.load(new FileReader(filePath));
        // 3. 修改文件
        properties.setProperty("ip","192.168.0.1");
        properties.store(new FileWriter(filePath), "会在properties 最上面作为一个注释");
    }
}