不理不知道,Java中的IO家族原来如此繁盛

106 阅读8分钟

文章首发于博客:布袋青年,原文链接直达:Java IO知识梳理


一、文件目录

1. 文件操作

  • isFile() :判断是否为文件,返回 boolean
  • exists() :判断文件是否存在,返回 boolean
  • createNewFile() :根据传入路径创建同名文件。
  • getPath() :获取文件路径。
  • getParentFile() :获取文件的父级目录。
  • getAbsolutePath() :获取文件的绝对路径。
    public void FileDemo() throws IOException {
        File sourceFile = new File("src\\main\\resources\\info.txt");
    
        // 获取文件路径
        String filePath = sourceFile.getPath();
            
        // 获取绝对路径
        String absolutePath = sourceFile.getAbsolutePath();
    
        // 文件是否存在
        boolean fileExist = sourceFile.exists();
            
        // 是否为文件
        boolean isFile = sourceFile.isFile();
    
        // 新建文件
        sourceFile.createNewFile();
    }
    

2. 目录操作

  • exists() :判断目录是否存在,返回boolean
  • mkdirs() :新建目录,返回boolean
  • isDirectory() :判断是否为目录,返回boolean
    public void DirDemo(){
        File sourceFile = new File("src\\main\\resources\\info.txt");
    
        // 获取父级路径
        File parentPath = sourceFile.getParentFile();
    
        // 目录是否存在
        boolean direExist = parentPath.exists();
            
        // 是否为目录
        boolean isDire = parentPath.isDirectory();
    
        // 新建目录
        parentPath.mkdirs();
    }
    

3. 工具操作

File 类中提供了一些静态变量定义了文件中常用的变量,如目录分隔符等。

我们都知道 WindowsLinux 的文件目录系统使用的分隔符是不同的,如 Windows 使用的是 \ ,而 Linux 中使用的是 / ,显然在工程中维护两套代码成本是很高的。因此 File 提供了静态变量可根据系统获取分隔符。

如下述代码在 WindowsLinux 运行将会分别输出 \/

public void demo() {
    String separator = File.separator;
    System.out.println("Separator: " + separator);
}

4. 文件递归

通过递归调用实现遍历目录下所有文件。

private void traverse(File file) {
    if (file.isDirectory()) {
        File[] files = file.listFiles();
        if (files != null) {
            for (File subFile : files) {
                traverse(subFile);
            }
        }
    } else {
        // file -> do something
    }

二、基本输入流

1. InputStream

InputStream 是所有输入流的父类,提供基础的 IO 读取服务,通过 read() 方法即可读取文件内容,当读到最后一位时返回 -1

注意 InputStream 为无缓冲读取,即读取的字节内容后需要立即写入,在读取大文件时则会导致产生大量的磁盘 IO 操作,因此在实际应用中使用更多的通常是后续提到的缓存流。

InputStream 较为常见的读取方式有如下两种:

  • read(): 一次只能读取一个字节数据,当内容较大时则会产生较多的 IO 操作从而影响性能。
  • read(byte[]): 可指定单次读取的字节大小,通过增加单次读取的数量从而降低读取频次,进而提升性能。
public void InputDemo() {
    File file = new File("src\\main\\resources\\info.txt");
    try (InputStream in = new FileInputStream(file)) {
        // read single byte
        in.read();
        // read with buffer
        in.read(new byte[1024]);
        // read specify size from byte array
        in.read(new byte[1024], 0, 1024);
        // skip specify size from resource
        in.skip(4);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

2. FileInputStream

FileInputStream 继承自 InputStream ,用于读取文件内容,其提供两类文件初始化方式,选择传入文件路径或传入 File 类。

下述示例即为一个简单的文件内存读取输出样例:

public void FileInputDemo(){
    File file = new File("src\\main\\resources\\info.txt");
    try(FileInputStream is = new FileInputStream(file)) {
        int ch;
        // batch: 单次读取大小
        byte[] batch = new byte[256];
        while ((ch = is.read(batch)) != -1) {
            System.out.write(ch);
        }
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

3. InputStreamReader

InputStreamReader 是字节流 (byte) 与字符流 (char) 之间的桥梁,能将字节流输出为字符流,并且能为字节流指定字符集,可输出一个个的字符。

字节流操作汉字或特殊符号语言的时候容易乱码,因此读取文本时可使用字符流实现,但操作二进制文件(比如图片、音频、视频)必须使用字节流。

public void InputStreamReaderDemo(){
    File file = new File("src\\main\\resources\\info.txt");
    try (
            FileInputStream fis = new FileInputStream(file);
            InputStreamReader isr = new InputStreamReader(fis, StandardCharsets.UTF_8)
    ) {
        int ch;
        while ((ch = isr.read()) != -1) {
            System.out.write(ch);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

4. DataInputStream

DataInputStream 支持以 Java 原始数据类型的形式从输入流中读取数据,提供了一组 readXXX() 方法读取不同类型的原始数据类型。

public void DataInputStreamDemo() {
    File file = new File("src\\main\\resources\\info.txt");
    try (
            FileInputStream fis = new FileInputStream(file);
            DataInputStream dis = new DataInputStream(fis)
    ) {
        int ch;
        while ((ch = dis.read()) != -1) {
            System.out.write(ch);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

5. ByteArrayInputStream

ByteArrayInputStream 用于读取内存数据,可以从字节数组中读取数据。

public void ByteArrayInputStreamDemo() {
    String msg = "The message from ByteArrayInputStream.";
    byte[] data = msg.getBytes();
    try (ByteArrayInputStream bis = new ByteArrayInputStream(data)) {
        int ch;
        while ((ch = bis.read()) != -1) {
            System.out.write(ch);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

三、基本输出流

1. OutputStream

OutputStream 为所有输出流的父类,可以实现数据的写入服务,与 InputStream 一样其同样没有缓冲机制会导致大量 IO 操作。

通过 write() 方法即可实现数据的写入,与 read() 类似可通过传入 byte 数组一次性写入多字节数据。

public void OutputStreamDemo() {
    String location = "src\\main\\resources\\info.txt";
    try (FileOutputStream fos = new FileOutputStream(location)) {
        // write single data
        fos.write(10);
        // write specify byte
        fos.write(new byte[1024]);
        // write specify size from byte array
        fos.write(new byte[1024], 0, 1024);
        // send cache data to destination
        out.flush();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

2. FileOutputStream

FileOutputStream 继承于 OutputStream,可以实现文件媒体文件的写入服务。

需要注意其只能写入字节数据,如将字符串写入文件需要先将其转为 byte 类型。

public void FileOutputDemo(){
    String location = "src\\main\\resources\\info.txt";
    String message = "Message from FileOutputStream.";
    byte[] bytes = message.getBytes();        
    try (OutputStream out = new FileOutputStream(location)) {
        // write data
        out.write(bytes);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
    System.out.println("write complete");
}

3. OutputStreamWriter

OutputStreamWriterInputStreamReader 相对应,可以直接将字符直接写入文件。

public void FileOutputWriteDemo() {
    String location = "src\\main\\resources\\info.txt";
    try (
            FileOutputStream fos = new FileOutputStream(location);
            OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF-8")
    ) {
        // Don't have to convert data type
        osw.write(message);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
    System.out.println("write complete");
}

4. DataOutputStream

DataOutputStreamDataInputStream 类似,允许以机器无关方式的方式从写入基本 Java 数据类型。

5. ByteArrayOutputStream

ByteArrayOutputStream 可用于往内存写入数据。

四、缓冲输入流

1. BufferedReader

BufferedReader 字符缓冲输入流,提供通用的缓冲方式文本读取。

通过 readLine() 读取一个文本行,从字符输入流中读取文本,缓冲各个字符,从而提供字符、数组和行的高效读取。

public void BufferReadDemo(){
    File file = new File("src\\main\\resources\\info.txt");
    try (
            FileReader fr = new FileReader(file);
            BufferedReader bf = new BufferedReader(fr)
    ) {
        String line;
        StringBuilder builder = new StringBuilder();
        while ((line = bf.readLine()) != null) {
            builder.append(line);
        }
        System.out.println(builder);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

2. BufferedInputStream

在之前介绍了 InputStream 是与目标对象建立了一个连接,再通过 read() 与 write() 进行数据的读取与写入,当读取到数据后必须立即进行写入操作。

BufferedInputStream 则在内部自动维护大小为 8192 的缓存区,如果缓存区没有数据或者数据不足才会从底层 InputStream 中读取数据,能有效的减少磁盘 IO 操作从而提高性能。

image.png

public void FileInputDemo(){
    File sourceFile = new File("src\\main\\resources\\info.txt");
    try (
            FileInputStream fis = new FileInputStream(sourceFile);
            BufferedInputStream bis = new BufferedInputStream(fis)
    ) {
        int ch;
        byte[] buffer = new byte[1024];
        while ((ch = bis.read(buffer)) != -1) {
            System.out.write(buffer, 0, chs);
        }
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

五、缓冲输出流

1. BufferedWriter

BufferedWriterBufferedReader 对应,提供带缓存区的写入。

public void BufferWriteDemo() {
    String msg = "This is a test from BufferedWriter.";
    File file = new File("src\\main\\resources\\test.txt");

    try (
            FileWriter fw = new FileWriter(file);
            BufferedWriter bw = new BufferedWriter(fw)
    ) {
        bw.append(msg);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
    System.out.println("write complete");
}

2. BufferedOutputStream

BufferedOutputStreamBufferedInputStream 对应,内部实现了一个缓冲区数组从而实现更高效的写入服务。

public void BufferedOutputStreamDemo() {
    String msg = "This is message from BufferedOutputStream.";
    File file = new File("src\\main\\resources\\test.txt");

    try (
            FileOutputStream fos = new FileOutputStream(file);
            BufferedOutputStream bos = new BufferedOutputStream(fos)
    ) {
        bos.write(msg.getBytes());
    } catch (IOException e) {
        e.printStackTrace();
    }
    System.out.println("Message 4 write complete.");
}

六、文件读写

1. 模式介绍

RandomAccessFile 独立于基本的 IO 流,是专门为文件读写提供的一种设计。在初始化对象时传入文件路径并指定操作模式,其中操作模式包含下列四种。

  • r

    以只读方式打开指定文件,如果试图对该 RandomAccessFile 指定的文件执行写入方法则会抛出 IOException

  • rw

    以读取、写入方式打开指定文件,如果该文件不存在,则尝试创建文件。

  • rws

    以读取、写入方式打开指定文件,相对于 rw 模式,还要求对文件的内容或元数据的每个更新都同步写入到底层存储设备,默认情形下(rw模式下)是使用 buffer 的,只有 cache 满的或者使用 close() 时候才真正的写到文件。

  • rwd

    rws 类似,只是仅对文件的内容同步更新到磁盘,而不修改文件的元数据。

    public void initDemo() {
        String location = "src\\main\\resources\\info.txt";
        try (RandomAccessFile raf = new RandomAccessFile(location, "rw")) {
            // 获取当前指针位置
            raf.getFilePointer();
            // 设置当前指针位置
            raf.seek(10);
            // 读取单字节数据
            raf.read();
            // 写入内容
            raf.write("message".getBytes());
            // 读取 int 值, 同理还有 readLong() 等等
            raf.readInt();
            // 写入 int 值, 同理还有 writeLong() 等等
            raf.writeInt(1);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    

2. 读写示例

  • 文件读取

    通过 RandomAccessFile 实现本地文件内容读取。

    public void ReadDemo() {
        String sourcePath = "src\\main\\resources\\info.txt";
        try (RandomAccessFile raf = new RandomAccessFile(sourcePath, "r")) {
            int ch;
            // 设置每次读取大小
            byte[] buffer = new byte[1024];
            while ((ch = raf.read(buffer)) != -1) {
                System.out.write(ch);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    
  • 文件写入

    通过 RandomAccessFile 实现内容数据的写入。

    public void WriteDemo() {
        String msg = "The message from RandomAccessFile.";
        String targetPath = "src\\main\\resources\\info.txt";
        try (RandomAccessFile raf = new RandomAccessFile(targetPath, "w")) {
            // Write data
            raf.write(msg.getBytes());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    

七、序列化转化

在计算机中任何数据都是以二进制的形式存储,因此当项目中涉及到数据对象的传输存储时,通过我们需要将复杂的对象数据转化为字节数据,下面介绍几类常见的对象数据转化。

1. 字节转换

  • byte转InputStream

    byte[] 转为 InputStream

    public static InputStream byteToInputStream(byte[] bytes) {
        return new ByteArrayInputStream(bytes);
    }
    
  • InputStream转byte

    InputStream 转为 byte[]

    public static byte[] inputStreamToByte(InputStream in) {
        try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
            int ch;
            byte[] buff = new byte[100];
            while ((ch = in.read(buff, 0, 100)) > 0) {
                bos.write(buff, 0, ch);
            }
            return bos.toByteArray();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    
  • byte转OutputStream

    byte[] 转为 OutputStream

    public static OutputStream byteToOutputStream(byte[] bytes) {
        try (OutputStream out = new ByteArrayOutputStream()) {
            out.write(bytes);
            return out;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    

2. 对象转换

  • Object转byte

    bean 对象转为 byte[] 数组。

    public static <T> byte[] objectToByte(T t) {
        byte[] bytes = null;
        try (
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream out = new ObjectOutputStream(bos)
        ) {
            out.writeObject(t);
            out.flush();
            bytes = bos.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return bytes;
    }
    
  • byte转Object

    byte[] 数组转为 bean 对象。

    public static <T> T byteToObject(byte[] bytes, Class<T> tClass) {
        Object obj;
        try (
            ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
            ObjectInputStream ois = new ObjectInputStream(bis)
        ) {
            obj = ois.readObject();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return (T) obj;
    }