Java IO 流
IO 流
IO 流是 Java 中用于处理输入和输出操作的核心概念。Java 提供了丰富的 IO 类库,支持文件操作、网络通信等多种场景。
分类
按照流的方向可以分为:输入流和输出流。输入流用于读取数据,输出流用于写入数据。
按照流的数据类型可以分为:字节流和字符流。字节流可以处理所有类型的数据,而字符流专门用于处理文本数据。
字节流
字节流是 Java 中用于处理字节数据的核心概念。字节流可以处理所有类型的数据,如图片、音频、视频等。字节流提供了一系列方法,用于读取、写入和操作字节数据。
字节流提供了两个接口:InputStream和OutputStream。InputStream用于读取字节数据,OutputStream用于写入字节数据。但是我们不需要直接使用这两个接口,而是使用它们的子类。分别是:FileInputStream和FileOutputStream。
FileOutputStream
FileOutputStream 构造方法
| 构造方法 | 描述 |
|---|---|
| FileOutputStream(File file) | 通过文件对象创建一个字节输出流 |
| FileOutputStream(String name) | 通过文件路径创建一个字节输出流 |
| FileOutputStream(File file, boolean append) | 通过文件对象创建一个字节输出流,第二个参数为 true 则追加内容 |
| FileOutputStream(String name, boolean append) | 通过文件路径创建一个字节输出流,第二个参数为 true 则追加内容 |
FileOutputStream 常用方法
| 方法 | 描述 |
|---|---|
| write(int b) | 写入一个字节 |
| write(byte[] b) | 写入字节数组 |
| write(byte[] b, int off, int len) | 写入字节数组的一部分 |
| close() | 关闭输出流,释放资源 |
| getChannel() | 获取与此输出流关联的文件通道 |
| getFD() | 获取此输出流的文件描述符对象 |
| nullOutputStream() | 返回一个空输出流,用于忽略输出 |
| flush() | 刷新输出流,将缓冲区的数据写入目标 |
FileOutputStream 示例代码
▼java复制代码// 创建文件对象,如果文件不存在则创建新文件,如果文件存在,新写入的内容会覆盖原有内容 File file = new File(System.getProperty("user.dir") + File.separator + "test.txt"); // 与文件之间建立输出流 FileOutputStream fileOutputStream = new FileOutputStream(file); // 传入的参数必须是字节 fileOutputStream.write("ABC".getBytes()); // 关闭资源 fileOutputStream.close();
▼java复制代码File file = new File(System.getProperty("user.dir") + File.separator + "test.txt"); // 与文件之间建立输出流,第二个参数为 true 则追加内容 FileOutputStream fileOutputStream = new FileOutputStream(file, true); // 传入的参数必须是字节 fileOutputStream.write("ABC".getBytes()); // 关闭资源 fileOutputStream.close();
▼java复制代码File file = new File(System.getProperty("user.dir") + File.separator + "test.txt"); // 与文件之间建立输出流 FileOutputStream fileOutputStream = new FileOutputStream(file); // 传入的参数必须是字节,可以使用换行符实现换行 fileOutputStream.write("ABC\r\nDEF".getBytes()); // 关闭资源 fileOutputStream.close();
FileInputStream
FileInputStream 构造方法
| 构造方法 | 描述 |
|---|---|
| FileInputStream(File file) | 通过文件对象创建 |
| FileInputStream(String path) | 通过文件路径创建 |
| FileInputStream(FileDescriptor fd) | 通过文件描述符创建 |
FileInputStream 常用方法
| 方法 | 描述 |
|---|---|
| read() | 读取一个字节 |
| read(byte[] b) | 读取字节数组 |
| read(byte[] b, int off, int len) | 读取字节数组 |
| readAllBytes() | 读取所有字节 |
| readNBytes(int len) | 读取指定数量的字节 |
| transferTo(OutputStream out) | 将此输入流的数据传输到指定的输出流 |
| skip(long n) | 跳过指定数量的字节 |
| available() | 获取输入流中剩余的字节数 |
| close() | 关闭输入流,释放资源 |
| getFD() | 获取此输入流的文件描述符对象 |
| getChannel() | 获取与此输入流关联的文件通道 |
| nullInputStream() | 返回一个空输入流,用于忽略输入 |
| readNBytes(byte[] b, int off, int len) | 读取指定数量的字节,并跳过指定数量的字节 |
| skipNBytes(long n) | 跳过指定数量字节 |
| markSupported() | 判断流是否支持 mark 和 reset 方法 |
| mark(int readAheadLimit) | 标记当前位置,并设置读取 ahead 的限制 |
| reset() | 恢复到 mark 的位置 |
FileInputStream 示例代码
▼java复制代码File file = new File(System.getProperty("user.dir") + File.separator + "test.txt"); FileInputStream fileInputStream = new FileInputStream(file); // 读取全部字节 byte[] bytes = fileInputStream.readAllBytes(); // 将字节转换为字符串并输出 String str = new String(bytes); System.out.println(str); fileInputStream.close();
▼java复制代码File file = new File(System.getProperty("user.dir") + File.separator + "test.txt"); FileInputStream fileInputStream = new FileInputStream(file); // 使用循环读取文件内容 byte[] buffer = new byte[1024]; int len; while ((len = fileInputStream.read(buffer)) != -1) { System.out.println(new String(buffer, 0, len)); } fileInputStream.close();
文件拷贝
▼java复制代码// 文件拷贝,将 test.txt 文件内容复制到 test-copy.txt 文件中,test-copy.txt 不存在也没关系,会自动创建 File originFile = new File(System.getProperty("user.dir") + File.separator + "test.txt"); File targetFile = new File(System.getProperty("user.dir") + File.separator + "test-copy.txt"); // 建立输入流和输出流,输入流与源文件关联,输出流与目标文件关联 FileInputStream fileInputStream = new FileInputStream(originFile); FileOutputStream fileOutputStream = new FileOutputStream(targetFile); // 读取源文件,写入目标文件,不用考虑内存,一次性读取全部内容 byte[] bytes = fileInputStream.readAllBytes(); fileOutputStream.write(bytes); // 关闭资源 fileInputStream.close(); fileOutputStream.close();
▼java复制代码File originFile = new File(System.getProperty("user.dir") + File.separator + "test.txt"); File targetFile = new File(System.getProperty("user.dir") + File.separator + "test-copy.txt"); FileInputStream fileInputStream = new FileInputStream(originFile); FileOutputStream fileOutputStream = new FileOutputStream(targetFile); // 使用循环读取源文件,写入目标文件,适合大文件拷贝,一部分一部分读取,避免内存溢出 byte[] buffer = new byte[1024]; int length; while ((length = fileInputStream.read(buffer)) > 0) { fileOutputStream.write(buffer, 0, length); } fileInputStream.close(); fileOutputStream.close();
try-with-resources 语句
在前边我们编写代码时,所有的异常都采用了抛出处理,若我们使用 try-catch 语句捕获异常时,但是这样会有一个问题,那就是如果在 close 方法之前发生了异常,close 方法就不会被执行,从而导致资源未被释放。
▼java复制代码File file = new File(System.getProperty("user.dir") + File.separator + "test.txt"); try { FileInputStream fileInputStream = new FileInputStream(file); byte[] bytes = fileInputStream.readAllBytes(); String str = new String(bytes); System.out.println(str); fileInputStream.close(); } catch (IOException exception) { exception.printStackTrace(); }
为了解决这个问题,我们可以使用 try-catch-finally 语句,在 finally 块中关闭资源。
▼java复制代码// try...catch...finally 语句 File file = new File(System.getProperty("user.dir") + File.separator + "test.txt"); FileInputStream fileInputStream = null; try { fileInputStream = new FileInputStream(file); byte[] bytes = fileInputStream.readAllBytes(); String str = new String(bytes); System.out.println(str); } catch (IOException exception) { exception.printStackTrace(); } finally { try { if (fileInputStream != null) { fileInputStream.close(); } } catch (IOException e) { e.printStackTrace(); } }
但是这样的代码就很难看,而且容易出错。为了解决这个问题,Java 7 引入了 try-with-resources 语句,可以自动关闭资源。前提是该资源实现了 AutoCloseable 接口。
▼java复制代码// try-with-resources 语句 File file = new File(System.getProperty("user.dir") + File.separator + "test.txt"); try (FileInputStream fileInputStream = new FileInputStream(file)) { byte[] bytes = fileInputStream.readAllBytes(); String str = new String(bytes); System.out.println(str); } catch (IOException exception) { exception.printStackTrace(); }
乱码
在使用字节流去读取文件内容时,容易出现乱码的问题,因为字节流无法处理字符编码问题。
因为有些字符在不同的编码下,占用的字节数不同,比如中文,在 UTF-8 编码下占用 3 个字节,而在 GBK 编码下占用 2 个字节。如果使用字节流按错误的字节数去读取文件内容,就会导致乱码。
假设现在文件中有一个中文汉字 你,转为十进制字节码为:228 189 160,也就是说,在读取文件时,必须把这三个字节码全部读取,才能正确显示这个汉字。如果我们每次只读取两个字节码 228 189,160,首先系统会认为这是两个字符,其次系统无法识别,或者识别错误,就会出现乱码或显示不正确的字符。
UTF-8 其实是 Unicode 的一个变种,它使用 1-4 个字节来表示一个字符。
▼txt复制代码// test.txt 文件内容,编码格式为 UTF-8,所以一个中文字符占用 3 个字节 你好
▼java复制代码File file = new File(System.getProperty("user.dir") + File.separator + "test.txt"); FileInputStream fileInputStream = new FileInputStream(file); // 两个字节为一个单位的读取文件内容,但是一个中文字符占用 3 个字节,所以会出现乱码 // 将 2 改成 3 就不会出现乱码 byte[] bytes = new byte[2]; int length; while ((length = fileInputStream.read(bytes)) != -1) { System.out.println(new String(bytes, 0, length)); } fileInputStream.close(); // 输出结果: // � // �� // ��
编码和解码
| 方法 | 功能 |
|---|---|
| String(byte[] bytes, String charsetName) | 将字节数组转为字符串 |
| byte[] getBytes(String charsetName) | 将字符串转为字节数组 |
▼java复制代码String str = "你好"; // 默认编码,IDEA 默认是 UTF-8 byte[] defaultBytes = str.getBytes(); System.out.println(Arrays.toString(defaultBytes)); // [-28, -67, -96, -27, -91, -67] // UTF-8 byte[] utf_8Bytes = str.getBytes(StandardCharsets.UTF_8); System.out.println(Arrays.toString(utf_8Bytes)); // [-28, -67, -96, -27, -91, -67] // GBK byte[] gbkBytes = str.getBytes("GBK"); System.out.println(Arrays.toString(gbkBytes)); // [-60, -29, -70, -61] // 解码,默认是 UTF-8 String str1 = new String(defaultBytes); System.out.println(str1); // 你好 // UTF-8 String str2 = new String(defaultBytes, StandardCharsets.UTF_8); System.out.println(str2); // 你好 // GBK,解码方式和编码不同,会乱码 String str3 = new String(defaultBytes, "GBK"); System.out.println(str3); // 浣犲ソ // 解码方式和编码相同 String str4 = new String(gbkBytes, "GBK"); System.out.println(str4); // 你好
字符流
字符流的底层其实就是字节流,但是在其基础上完善了字符编码和解码的问题。字符流专门用于处理文本数据,能够自动处理字符编码和解码的问题。
字符流同样提供了两个接口:Reader 和 Writer。 Reader 用于读取字符数据,Writer 用于写入字符数据。但是我们不需要直接使用这两个接口,而是使用它们的子类。分别是:FileReader 和 FileWriter。
FileWriter
FileWriter 构造方法
| 构造方法 | 功能 |
|---|---|
| FileWriter(String fileName) | 通过文件路径创建一个字符输出流 |
| FileWriter(String fileName, boolean append) | 通过文件路径创建一个字符输出流 |
| FileWriter(File file) | 通过文件对象创建一个字符输出流 |
| FileWriter(File file, boolean append) | 通过文件对象创建一个字符输出 |
| FileWriter(FileDescriptor fd) | 通过文件描述符创建一个字符输出流 |
| FileWriter(String fileName, Charset charset) | 通过文件路径和字符集创建 |
| FileWriter(String fileName, Charset charset, boolean append) | 通过文件路径、字符集和追加标志创建 |
| FileWriter(File file, Charset charset) | 通过文件对象和字符集创建 |
| FileWriter(File file, Charset charset, boolean append) | 通过文件对象、字符集和追加标志创建 |
FileWriter 常用方法
| 方法 | 功能 |
|---|---|
| getEncoding() | 返回字符输出流的编码 |
| write(int c) | 写入一个字符 |
| write(char[] cbuf, int off, int len) | 写入字符数组的一部分 |
| write(String str, int off, int len) | 写入字符串的一部分 |
| append(CharSequence csq, int start, int end) | 写入字符序列的一部分 |
| append(CharSequence csq) | 写入一个字符序列 |
| flush() | 刷新缓冲区,将数据写入文件 |
| close() | 关闭字符输出流,将缓冲区中的数据写入文件 |
| nullWriter() | 返回一个空输出流,用于忽略输出 |
| write(char[] cbuf) | 写入字符数组 |
| write(String str) | 写入字符串 |
| append(char c) | 写入一个字符 |
FileWriter 示例代码
▼java复制代码File file = new File(System.getProperty("user.dir") + File.separator + "test.txt"); FileWriter fileWriter = new FileWriter(file); // 字符串 fileWriter.write("你好"); // 数组 fileWriter.write(new char[]{'a', 'b', 'c'}); fileWriter.close();
▼java复制代码File file = new File(System.getProperty("user.dir") + File.separator + "test.txt"); // 指定字符集,并且追加内容 FileWriter fileWriter = new FileWriter(file, Charset.forName("UTF-8"), true); // 字符串 fileWriter.write("你好"); // 数组 fileWriter.write(new char[]{'a', 'b', 'c'}); fileWriter.close();
FileReader
FileReader 构造方法
| 构造方法 | 功能 |
|---|---|
| FileReader(File file) | 通过文件对象创建一个字符输入流 |
| FileReader(String fileName) | 通过文件路径创建一个字符输入流 |
| FileReader(FileDescriptor fd) | 通过文件描述符创建一个字符输入流 |
| FileReader(File file, Charset charset) | 通过文件对象和字符集创建 |
| FileReader(String fileName, Charset charset) | 通过文件路径和字符集创建 |
FileReader 常用方法
| 方法 | 功能 |
|---|---|
| getEncoding() | 返回字符输入流的编码 |
| read(CharBuffer target) | 读取字符到 CharBuffer 对象中 |
| read() | 读取一个字符 |
| read(char[] cbuf, int off, int len) | 读取字符数组 |
| read(char[] cbuf) | 读取字符数组 |
| ready() | 判断流是否已经准备好 |
| close() | 关闭流 |
| nullRead() | 返回一个 int 类型的值,如果返回-1,则表示已经读完 |
| skip(long n) | 跳过指定数量的字符 |
| markSupported() | 判断流是否支持 mark 和 reset 方法 |
| mark(int readAheadLimit) | 设置 mark 的位置,readAheadLimit 表示可以读取的最大字节数 |
| reset() | 恢复到 mark 的位置 |
| transferTo(Writer out) | 将此输入流的数据传输到指定的输出流 |
FileReader 示例代码
▼java复制代码File file = new File(System.getProperty("user.dir") + File.separator + "test.txt"); FileReader fileReader = new FileReader(file); int flag; while ((flag = fileReader.read()) != -1) { System.out.print((char)flag); // 你好 } fileReader.close();
▼java复制代码File file = new File(System.getProperty("user.dir") + File.separator + "test.txt"); FileReader fileReader = new FileReader(file); char[] buffer = new char[1024]; int len; while ((len = fileReader.read(buffer)) != -1) { System.out.print(new String(buffer, 0, len)); } fileReader.close();
字符流原理
在创建字符流对象时,底层会创建一个缓冲区,大小为 8KB。当我们调用字符流的 read 或 write 方法时,实际上是先将数据读入缓冲区或写入缓冲区,当缓冲区满时,才会将数据写入文件或从文件读取数据到缓冲区。在读取数据时,如果缓冲区为空,则会从文件中读取数据到缓冲区,然后依次从缓冲区中取出数据,当缓冲区的数据也被读取完时,才会再次从文件中继续读取数据,新读取的数据会覆盖缓冲区中的数据,但是如果新的数据长度小于缓冲区,那么缓冲区中就会有部分数据无法被覆盖,能保留上一次读取的数据。当文件内容被加载至缓冲区后,即使文件删除或者内容被修改,字符流仍能通过缓冲区获取文件数据。在写入数据时,字符流其实是现象内容添加到缓冲区,只有当:1.缓冲区满了、2.调用了 flush 方法、3.调用了 close 方法时,才会将数据写入文件。
▼java复制代码File file = new File(System.getProperty("user.dir") + File.separator + "test.txt"); FileWriter fileWriter = new FileWriter(file); // 添加数据到缓冲区 fileWriter.write("你好"); // 刷新缓冲区,将数据写入文件 fileWriter.flush(); // 不会写入文件,而是写入缓冲区,除非调用 flush 方法或者 close 方法 fileWriter.write(new char[]{'a', 'b', 'c'}); // fileWriter.close();
字符流 练习
拷贝文件夹
▼java复制代码public static void main(String[] args) throws IOException { File originDir = new File("D:\\CodeProjects\\java-study"); File targetDir = new File("D:\\CodeProjects\\java-study-copy"); copyDir(originDir, targetDir); } public static void copyDir(File originDir, File targetDir) throws IOException { if (originDir.isFile()) { // copyFile(originDir, targetDir); // Files.copy JDK 9 新特性 Files.copy(originDir.toPath(), targetDir.toPath()); } else if (originDir.isDirectory()) { if (!targetDir.exists()) { targetDir.mkdir(); } File[] files = originDir.listFiles(); if (files != null) { for (File file : files) { copyDir(file, new File(targetDir, file.getName())); } } } } public static void copyFile(File originFile, File targetFile) { try ( FileInputStream fis = new FileInputStream(originFile); FileOutputStream fos = new FileOutputStream(targetFile); ){ byte[] buffer = new byte[1024]; int length; while ((length = fis.read(buffer)) > 0) { fos.write(buffer, 0, length); } } catch (IOException e) { e.printStackTrace(); } }
文件加密
▼java复制代码public static void main(String[] args) throws IOException { File originFile = new File("D:\\CodeProjects\\ad783e83a6d4434c8a1428b942c1a986.jpg"); File encryptionFile = new File("D:\\CodeProjects\\encryption.jpg"); // 加密 encryption(originFile, encryptionFile); // 解密 File decryptionFile = new File("D:\\CodeProjects\\decryption.jpg"); encryption(encryptionFile, decryptionFile); } public static void encryption(File originDir, File targetDir) throws IOException { FileInputStream fis = new FileInputStream(originDir); FileOutputStream fos = new FileOutputStream(targetDir); byte[] buffer = new byte[1024]; int length; while ((length = fis.read(buffer)) > 0) { for (int i = 0; i < buffer.length; i++) { buffer[i] = (byte) (buffer[i] ^ i); } fos.write(buffer, 0, length); } fis.close(); fos.close(); }
数据排序
▼java复制代码File file = new File(System.getProperty("user.dir") + File.separator + "test.txt"); StringBuilder stringBuilder= new StringBuilder(); FileReader fr = new FileReader(file); char[] buffer = new char[1024]; int length; while ((length = fr.read(buffer)) != -1) { stringBuilder.append(new String(buffer, 0, length)); } fr.close(); String string = stringBuilder.toString(); char[] charArray = string.toCharArray(); Arrays.sort(charArray); System.out.println(Arrays.toString(charArray)); FileWriter fw = new FileWriter(file); fw.write(charArray); fw.close();
缓冲流
前边学习了字节流和字符流,一共四个类:FileInputStream、FileOutputStream、FileReader 和 FileWriter。这四个类在进行读写操作时,效率较低,功能欠缺。为了提高读写效率,Java 提供了缓冲流。 缓冲流在字节流和字符流的基础上,增加了一个缓冲区,读写数据时,先将数据读入缓冲区或写入缓冲区,当缓冲区满时,才会将数据写入文件或从文件读取数据到缓冲区。缓冲流有四个类:BufferedInputStream、BufferedOutputStream、BufferedReader 和 BufferedWriter。提高读写效率。
缓冲流并不是一个新的流,而是对已有流的包装。使用缓冲流时,需要将已有的流作为参数传入缓冲流的构造方法中。
虽然字符流中包含了缓冲区,但是缓冲流中的缓冲区是可以自定义大小的。且缓冲流中还提供了更便捷的读写方式,总的来说还是有一定效率的提升的。
字节缓冲流
字节缓冲流 构造方法
| 构造方法 | 描述 |
|---|---|
| BufferedInputStream(InputStream in) | 创建一个 BufferedInputStream 对象,该对象将数据写入到指定的 InputStream 对象中 |
| BufferedInputStream(InputStream in, int size) | 创建一个 BufferedInputStream 对象,该对象将数据写入到指定的 InputStream 对象中,并指定缓冲区大小 |
| BufferedOutputStream(OutputStream out) | 创建一个 BufferedOutputStream 对象,该对象将数据写入到指定的 OutputStream 对象中 |
| BufferedOutputStream(OutputStream out, int size) | 创建一个 BufferedOutputStream 对象,该对象将数据写入到指定的 OutputStream 对象中,并指定缓冲区大小 |
字节缓冲流 常用方法
BufferedInputStream 继承 FileInputStream,所以它有 FileInputStream 和 InputStream 的所有方法。
BufferedOutputStream 继承 FileOutputStream,所以它有 FileOutputStream 和 OutputStream 的所有方法。
字节缓冲流 代码示例
▼java复制代码File originFile = new File(System.getProperty("user.dir") + File.separator + "test.txt"); File targetFile = new File(System.getProperty("user.dir") + File.separator + "test-copy.txt"); // 创建缓冲流,需要传入基本流对象 BufferedInputStream bis = new BufferedInputStream(new FileInputStream(originFile)); BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(targetFile)); // byte[] bytes = bis.readAllBytes(); // bos.write(bytes); // 直接使用 transferTo 方法进行文件拷贝 bis.transferTo(bos); bis.close(); bos.close();
字符缓冲流
字符缓冲流 构造方法
| 构造方法 | 描述 |
|---|---|
| BufferedReader(Reader in) | 创建一个 BufferedReader 对象,该对象将数据写入到指定的 Reader 对象中 |
| BufferedReader(Reader in, int size) | 创建一个 BufferedReader 对象,该对象将数据写入到指定的 Reader 对象中,并指定缓冲区大小 |
| BufferedWriter(Writer out) | 创建一个 BufferedWriter 对象,该对象将数据写入到指定的 Writer 对象中 |
| BufferedWriter(Writer out, int size) | 创建一个 BufferedWriter 对象,该对象将数据写入到指定的 Writer 对象中,并指定缓冲区大小 |
字符缓冲流 常用方法
BufferedReader 继承 FileReader,所以它有 FileReader 和 Reader 的所有方法。
BufferedWriter 继承 FileWriter,所以它有 FileWriter 和 Writer 的所有方法。
BufferedReader 独有的方法
| 方法 | 描述 |
|---|---|
| readLine() | 读取一行数据 |
| lines() | 读取所有行数据 |
BufferedWriter 独有的方法
| 方法 | 描述 |
|---|---|
| newLine() | 换行,可以根据不同的操作系统进行换行 |
字符缓冲流 代码示例
▼java复制代码File originFile = new File(System.getProperty("user.dir") + File.separator + "test.txt"); BufferedReader br = new BufferedReader(new FileReader(originFile)); // String line; // while ((line = br.readLine()) != null) { // System.out.println(line); // } Stream<String> lines = br.lines(); List<String> collect = lines.collect(Collectors.toList()); System.out.println(collect); br.close();
▼java复制代码File originFile = new File(System.getProperty("user.dir") + File.separator + "test.txt"); BufferedWriter bw = new BufferedWriter(new FileWriter(originFile)); bw.write("Hello"); // 写入一个换行符,会根据操作系统自动匹配对应的换行符 bw.newLine(); bw.write("World"); bw.close();
字符缓冲流 练习
比较基本字节流和缓冲字节流的效率差异
▼java复制代码public static void main(String[] args) throws IOException { File originFile = new File(System.getProperty("user.dir") + File.separator + "test.txt"); File targetFile = new File(System.getProperty("user.dir") + File.separator + "test-copy.txt"); long start = System.currentTimeMillis(); // copyFile1(originFile, targetFile); // 209 copyFile2(originFile, targetFile); // 69 long end = System.currentTimeMillis(); System.out.println(end - start); } public static void copyFile1(File originFile, File targetFile) throws IOException { FileInputStream fis = new FileInputStream(originFile); FileOutputStream fos = new FileOutputStream(targetFile); byte[] buffer = new byte[1024]; int length; while ((length = fis.read(buffer)) > 0) { fos.write(buffer, 0, length); } fis.close(); fos.close(); } public static void copyFile2(File originFile, File targetFile) throws IOException { BufferedInputStream bis = new BufferedInputStream(new FileInputStream(originFile)); BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(targetFile)); byte[] buffer = new byte[1024]; int length; while ((length = bis.read(buffer)) > 0) { bos.write(buffer, 0, length); } bis.close(); bos.close(); }
读取文件,根据段落序号将内容排序
▼java复制代码File originFile = new File(System.getProperty("user.dir") + File.separator + "test.txt"); File targetFile = new File(System.getProperty("user.dir") + File.separator + "test-copy.txt"); BufferedReader bis = new BufferedReader(new FileReader(originFile)); BufferedWriter bos = new BufferedWriter(new FileWriter(targetFile)); String[] array = bis.lines().sorted((s1, s2) -> { String index1 = s1.split("\\.")[0]; String index2 = s2.split("\\.")[0]; return Integer.parseInt(index1) - Integer.parseInt(index2); }).toArray(String[]::new); for (String s : array) { bos.write(s); bos.newLine(); } bis.close(); bos.close();
转换流
转换流是字符流,用于将字节流转换为字符流。属于高级流,用于包装基本流。 转换流是字节流和字符流之间的桥梁。用于将字节流转换为字符流,或者将字符流转换为字节流。转换流有两个类:InputStreamReader 和 OutputStreamWriter。
在 JDK 11 之后,FileReader 和 FileWriter 中也可以指定字符集,所以在读取文件时,转换流不再需要。而在其他场景下,转换流依然有其存在的价值。
转换流 构造方法
转换输入流
| 构造方法 | 描述 |
|---|---|
| InputStreamReader(InputStream in) | 创建一个 InputStreamReader 对象,该对象将字节流转换为字符流 |
| InputStreamReader(InputStream in, String charsetName) | 创建一个 InputStreamReader 对象,该对象将字节流转换为字符流,并指定字符集 |
| InputStreamReader(InputStream in, Charset cs) | 创建一个 InputStreamReader 对象,该对象将字节流转换为字符流,并指定字符集 |
| InputStreamReader(InputStream in, CharsetDecoder dec) | 创建一个 InputStreamReader 对象,该对象将字节流转换为字符流,并指定字符集解码器 |
转换输出流
| OutputStreamWriter(OutputStream out) | 创建一个 OutputStreamWriter 对象,该对象将字符流转换为字节流 | | OutputStreamWriter(OutputStream out, String charsetName) | 创建一个 OutputStreamWriter 对象,该对象将字符流转换为字节流,并指定字符集 | | OutputStreamWriter(OutputStream out, Charset cs) | 创建一个 OutputStreamWriter 对象,该对象将字符流转换为字节流,并指定字符集 | | OutputStreamWriter(OutputStream out, CharsetEncoder enc) | 创建一个 OutputStreamWriter 对象,该对象将字符流转换为字节流,并指定字符集编码器 |
转换流 常用方法
-
转换输入流继承 Reader,所以它有 Reader 的所有方法。
-
转换输出流继承 Writer,所以它有 Writer 的所有方法。
转换流 代码示例
▼java复制代码File originFile = new File(System.getProperty("user.dir") + File.separator + "test.txt"); File targetFile = new File(System.getProperty("user.dir") + File.separator + "test-copy.txt"); // UTF-8 编码的文件,使用 InputStreamReader 读取,OutputStreamWriter 写入 GBK 编码的文件 InputStreamReader isr = new InputStreamReader(new FileInputStream(originFile), Charset.forName("UTF-8")); OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(targetFile), Charset.forName("GBK")); char[] buf = new char[1024]; int len; while ((len = isr.read(buf)) != -1) { osw.write(buf, 0, len); } isr.close(); osw.close();
▼java复制代码File originFile = new File(System.getProperty("user.dir") + File.separator + "test.txt"); File targetFile = new File(System.getProperty("user.dir") + File.separator + "test-copy.txt"); // 将字节流通过转换流转为字符流 InputStreamReader isr = new InputStreamReader(new FileInputStream(originFile), Charset.forName("UTF-8")); OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(targetFile), Charset.forName("GBK")); // 再将字符流通过缓冲流进行包装 BufferedReader br = new BufferedReader(isr); BufferedWriter bw = new BufferedWriter(osw); // 读取所有行,存入数组,再逐行写入目标文件 String[] array = br.lines().toArray(String[]::new); for (String s : array) { bw.write(s); bw.newLine(); } isr.close(); osw.close();
序列化流
序列化流是字节流,用于将 Java 对象转换为字节流。序列化流是高级流,用于包装基本流。序列化流是 Java 对象和字节流之间的桥梁。用于将 Java 对象转换为字节流,或者将字节流转换为 Java 对象。序列化流有两个类:ObjectInputStream 和 ObjectOutputStream。
在使用序列化流操作 Java 对象时,必须保证对象是可序列化的。必须让类实现 Serializable 接口,或者将类声明为可序列化的。
在操作 Java 对象时,若不想某个属性被序列化,可以在属性声明时加上 transient 关键字。
序列化流 构造方法
| 构造方法 | 描述 |
|---|---|
| ObjectInputStream(InputStream in) | 创建一个 ObjectInputStream 对象,该对象将字节流转换为 Java 对象 |
| ObjectOutputStream(OutputStream out) | 创建一个 ObjectOutputStream 对象,该对象将 Java 对象转换为字节流 |
序列化流 常用方法
序列化输入流
| 方法 | 描述 |
|---|---|
| readObject() | 读取对象 |
| readUnshared() | 读取对象,并返回一个非共享对象 |
| defaultReadObject() | 读取对象,并调用默认方法 |
| readFields() | 读取对象 |
| registerValidation(ObjectInputValidation obj, int prio) | 注册对象验证 |
序列化输出流
| 方法 | 描述 |
|---|---|
| writeObject(Object obj) | 写入对象 |
| useProtocolVersion(int version) | 设置序列化协议版本 |
| writeUnshared(Object obj) | 写入对象,并返回一个非共享对象 |
| defaultWriteObject() | 写入对象,并调用默认方法 |
| putFields() | 写入对象 |
| writeFields() | 写入对象 |
| reset() | 重置对象 |
序列化流 代码示例
▼java复制代码import java.io.Serializable; // 必须实现 Serializable 接口 public class Student implements Serializable { // 固定的序列化版本号,防止序列化版本号不一致导致异常,可以让 IDEA 生成序列化版本号 @Serial private static final long serialVersionUID = 1L; private String name; private int age; // 忽略此属性的序列化 private transient String address; public Student() { } public Student(String name, int age, String address) { this.name = name; this.age = age; this.address = address; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", age=" + age + ", address='" + address + '\'' + '}'; } }
▼java复制代码File originFile = new File(System.getProperty("user.dir") + File.separator + "test.txt"); Student student = new Student("zhangsan", 20, "北京"); // 将 Java 对象转换为字节流,写入目标文件 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(originFile)); // address 属性被忽略,并不会写入目标文件 oos.writeObject(student); oos.close(); // 将字节流转换为 Java 对象,并读取目标文件 ObjectInputStream ois = new ObjectInputStream(new FileInputStream(originFile)); Object o = ois.readObject(); // address 属性被忽略,所以为 null if (o instanceof Student tempStudent) { System.out.println(tempStudent); // Student{name='zhangsan', age=20, address='null'} } ois.close();
序列化流 练习
随机写入 N 个 Java 对象,并保存到文件中。在将文件内容读取出来时,将文件内容转换为 Java 对象。
▼java复制代码File originFile = new File(System.getProperty("user.dir") + File.separator + "test.txt"); List<Student> studentList = new ArrayList<Student>(); for (int i = 0; i < 20; i++) { studentList.add(new Student("学生" + i, 20 + i, "北京")); } System.out.println(studentList); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(originFile)); // 不需要逐个写入 Student 对象,可以写入 List<Student> 对象 oos.writeObject(studentList); oos.close(); ObjectInputStream ois = new ObjectInputStream(new FileInputStream(originFile)); // 读取的时候直接读取 List<Student> 对象,就不需要考虑 Student 对象的个数了 Object o = ois.readObject(); if (o instanceof List newStudentList) { System.out.println(newStudentList); } ois.close();
打印流
打印流是 Java 语言内置的,用于将数据写入到文件中。打印流只能用于输出数据,不能用于输入数据。所以打印流只有两个类:PrintStream 和 PrintWriter。
分别对应字节打印流和字符打印流。
打印流 构造方法
字节打印流
| 构造方法 | 描述 |
|---|---|
| PrintStream(OutputStream out) | 创建一个 PrintStream 对象,该对象将数据写入到指定的 OutputStream 中 |
| PrintStream(OutputStream out, boolean autoFlush) | 创建一个 PrintStream 对象,该对象将数据写入到指定的 OutputStream 中,并设置自动刷新 |
| PrintStream(OutputStream out, boolean autoFlush, String encoding) | 创建一个 PrintStream 对象,该对象将数据写入到指定的 OutputStream 中,并设置自动刷新和字符编码 |
| PrintStream(OutputStream out, boolean autoFlush, Charset charset) | 创建一个 PrintStream 对象,该对象将数据写入到指定的 OutputStream 中,并设置自动刷新和字符编码 |
| PrintStream(String fileName) | 创建一个 PrintStream 对象,该对象将数据写入到指定的文件中 |
| PrintStream(String fileName, String csn) | 创建一个 PrintStream 对象,该对象将数据写入到指定的文件中,并设置字符编码 |
| PrintStream(String fileName, Charset charset) | 创建一个 PrintStream 对象,该对象将数据写入到指定的文件中,并设置字符编码 |
| PrintStream(File file) | 创建一个 PrintStream 对象,该对象将数据写入到指定的文件中 |
| PrintStream(File file, String csn) | 创建一个 PrintStream 对象,该对象将数据写入到指定的文件中,并设置字符编码 |
| PrintStream(File file, Charset charset) | 创建一个 PrintStream 对象,该对象将数据写入到指定的文件中,并设置字符编码 |
字符打印流
| 构造方法 | 描述 |
|---|---|
| PrintWriter(Writer out) | 创建一个 PrintWriter 对象,该对象将数据写入到指定的 Writer 中 |
| PrintWriter(Writer out, boolean autoFlush) | 创建一个 PrintWriter 对象,该对象将数据写入到指定的 Writer 中,并设置自动刷新 |
| PrintWriter(OutputStream out) | 创建一个 PrintWriter 对象,该对象将数据写入到指定的 OutputStream 中 |
| PrintWriter(OutputStream out, boolean autoFlush) | 创建一个 PrintWriter 对象,该对象将数据写入到指定的 OutputStream 中,并设置自动刷新 |
| PrintWriter(OutputStream out, boolean autoFlush, Charset charset) | 创建一个 PrintWriter 对象,该对象将数据写入到指定的 OutputStream 中,并设置自动刷新和字符编码 |
| PrintWriter(String fileName) | 创建一个 PrintWriter 对象,该对象将数据写入到指定的文件中 |
| PrintWriter(String fileName, String csn) | 创建一个 PrintWriter 对象,该对象将数据写入到指定的文件中,并设置字符编码 |
| PrintWriter(String fileName, Charset charset) | 创建一个 PrintWriter 对象,该对象将数据写入到指定的文件中,并设置字符编码 |
| PrintWriter(File file) | 创建一个 PrintWriter 对象,该对象将数据写入到指定的文件中 |
| PrintWriter(File file, String csn) | 创建一个 PrintWriter 对象,该对象将数据写入到指定的文件中,并设置字符编码 |
| PrintWriter(File file, Charset charset) | 创建一个 PrintWriter 对象,该对象将数据写入到指定的文件中,并设置字符编码 |
打印流 常用方法
| 方法 | 描述 |
|---|---|
| void write(int b) | 写入一个字节 / 字符 |
| void print(xxx) | 打印任意数据 |
| void println(xxx) | 打印任意是数据,自带换行,自动刷新 |
| void printf(xxx) | 格式化打印任意数据 |
| void flush() | 刷新缓冲区,将数据写入到文件中 |
| void frommat(xxx) | 格式化打印任意数据 |
| void append(xxx) | 追加任意数据 |
打印流 代码示例
▼java复制代码File originFile = new File(System.getProperty("user.dir") + File.separator + "test.txt"); FileOutputStream fos = new FileOutputStream(originFile); PrintStream out = new PrintStream(fos, true, Charset.forName("UTF-8")); // 输出,带换行 out.println("Hello World"); // 原样输出 out.print(97); // 格式化输出 out.printf("%s你好", "小李"); // 格式化输出 out.format("%s你好", "小明"); // 追加 out.append('a'); out.close();
▼java复制代码File originFile = new File(System.getProperty("user.dir") + File.separator + "test.txt"); FileWriter fw = new FileWriter(originFile); PrintWriter out = new PrintWriter(fw, true); out.println("Hello World"); out.print(97); out.printf("%s你好", "小李"); out.format("%s你好", "小明"); out.append('a'); out.close();
压缩流
压缩流:是字节流,用于对数据进行压缩和解压缩。压缩流是高级流,用于包装基本流。压缩流有两个类:ZIPInputStream 和 ZIPOutputStream。分别用于解压缩和压缩数据。压缩流只适用于扩展名为 zip 格式的文件。
除了 ZIPXXX 流,还有 GZIPXXX 流,用于对数据进行 GZIP 压缩和解压缩。
压缩流 构造方法
ZipInputStream 构造方法
| 构造方法 | 描述 |
|---|---|
| ZipInputStream(InputStream in) | 创建一个 ZipInputStream 对象,该对象将数据从指定的 InputStream 中读取 |
| ZipInputStream(InputStream in, Charset charset) | 创建一个 ZipInputStream 对象,该对象将数据从指定的 InputStream 中读取,并设置字符编码 |
ZipOutputStream 构造方法
| 构造方法 | 描述 |
|---|---|
| ZipOutputStream(OutputStream out) | 创建一个 ZipOutputStream 对象,该对象将数据写入到指定的 OutputStream 中 |
| ZipOutputStream(OutputStream out, Charset charset) | 创建一个 ZipOutputStream 对象,该对象将数据写入到指定的 OutputStream 中,并设置字符编码 |
压缩流 常用方法
ZipInputStream 常用方法
ZipInputStream 继承自 FileInputStream 的所有方法。
| 方法 | 描述 |
|---|---|
| getNextEntry() | 获取下一个条目 |
| closeEntry() | 关闭当前条目 |
ZipOutputStream 常用方法
ZipOutputStream 继承自 FileOutputStream 的所有方法。
| 方法 | 描述 |
|---|---|
| setComment(String comment) | 设置注释 |
| setMethod(int method) | 设置压缩方法 |
| setLevel(int level) | 设置压缩级别 |
| putNextEntry(ZipEntry e) | 添加下一个条目 |
| closeEntry() | 关闭当前条目 |
ZipEntry
| 方法 | 描述 |
|---|---|
| ZipEntry(String name) | 创建一个 ZipEntry 对象,该对象表示一个条目 |
| ZipEntry(ZipEntry e) | 创建一个 ZipEntry 对象,该对象表示一个条目 |
| getName() | 获取条目名称 |
| set / getTime(long time) | 设置 / 获取条目时间 |
| set / getTimeLocal(LocalDateTime time) | 设置 / 获取条目时间 |
| set / getLastModifiedTime(FileTime time) | 设置 / 获取条目最后修改时间 |
| set / getLastAccessTime(FileTime time) | 设置 / 获取条目最后访问时间 |
| set / getCreationTime(FileTime time) | 设置 / 获取条目创建时间 |
| set / getSize(long size) | 设置 / 获取条目大小 |
| set / getCompressedSize(long size) | 设置 / 获取条目压缩后大小 |
| set / getCrc(long crc) | 设置 / 获取条目校验和 |
| set / getMethod(int method) | 设置 / 获取条目压缩方法 |
| set / getExtra(byte[] extra) | 设置 / 获取条目扩展数据 |
| set / getComment(String comment) | 获取条目注释 |
| isDirectory() | 判断条目是否为目录 |
压缩流 示例代码
▼java复制代码File zipFile = new File(System.getProperty("user.dir") + File.separator + "test.zip"); File unzipDir = new File(System.getProperty("user.dir") + File.separator + "test"); // 解压 ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFile)); ZipEntry nextEntry; while ((nextEntry = zis.getNextEntry()) != null) { File file = new File(unzipDir, nextEntry.getName()); boolean directory = nextEntry.isDirectory(); if (directory) { file.mkdirs(); } else { byte[] bytes = zis.readAllBytes(); FileOutputStream fileOutputStream = new FileOutputStream(file); fileOutputStream.write(bytes); fileOutputStream.close(); } }
▼java复制代码// 压缩一个文件 File zipFile = new File(System.getProperty("user.dir") + File.separator + "test.zip"); File file = new File(System.getProperty("user.dir") + File.separator + "test.txt"); ZipOutputStream zipOutputStream = new ZipOutputStream(new FileOutputStream(zipFile)); ZipEntry zipEntry = new ZipEntry(file.getName()); zipOutputStream.putNextEntry(zipEntry); FileInputStream fileInputStream = new FileInputStream(file); byte[] bytes = fileInputStream.readAllBytes(); zipOutputStream.write(bytes); fileInputStream.close(); zipOutputStream.close();
▼java复制代码// 压缩一个目录 public static void main(String[] args) throws Exception { File dir = new File(System.getProperty("user.dir")); File zipDir = new File(dir.getParentFile(), dir.getName() + ".zip"); ZipOutputStream zipOutputStream = new ZipOutputStream(new FileOutputStream(zipDir)); zip(dir, zipOutputStream, dir.getName()); zipOutputStream.close(); } public static void zip(File dir, ZipOutputStream zipOutputStream, String entryPath) throws Exception { if (dir.isFile()) { zipOutputStream.putNextEntry(new ZipEntry(entryPath)); FileInputStream fileInputStream = new FileInputStream(dir); zipOutputStream.write(fileInputStream.readAllBytes()); fileInputStream.close(); return; } File[] files = dir.listFiles(); if (files != null) { for (File file : files) { zip(file, zipOutputStream, entryPath.concat(File.separator).concat(file.getName())); } } }
Commons-IO 工具类
Commons-IO 是一个 Java 工具类库,提供了很多 IO 操作的封装,如文件操作、流操作、压缩操作、文件匹配操作等等。
Hutool 工具类
Hutool 是一个 Java 工具类库,提供了很多 IO 操作的封装,如文件操作、流操作、压缩操作、文件匹配操作等等。
▼java复制代码// 获取深度搜索服务 String key = "xxxxxx"; DeepSeekService deepSeekService = AIUtil.getDeepSeekService(new AIConfigBuilder(ModelName.DEEPSEEK.getValue()).setApiKey(key).build()); String chat = deepSeekService.chat("你好"); System.out.println(chat);
Properties 类
Properties 是一个双列集合,具有和 Map 同样的特点。
构造方法
| 方法名 | 描述 |
|---|---|
| Properties() | 创建一个空的 Properties 对象 |
| Properties(int initialCapacity) | 创建一个指定初始容量的 Properties 集合 |
| Properties(Properties defaults) | 创建一个具有默认属性的 Properties 集合 |
常用方法
| 方法名 | 描述 |
|---|---|
| setProperty(String key, String value) | 设置属性 |
| load(Reader reader) | 从输入流中读取属性列表 |
| load(InputStream inStream) | 从输入流中读取属性列表 |
| store(Writer writer, String comments) | 将属性列表写入输出流 |
| store(OutputStream out, String comments) | 将属性列表写入输出流 |
| loadFromXML(InputStream in) | 从 XML 输入流中读取属性列表 |
| storeToXML(OutputStream os, String comment) | 将属性列表写入 XML 输出流 |
| storeToXML(OutputStream os, String comment, String encoding) | 将属性列表写入 XML 输出流,并指定编码 |
| storeToXML(OutputStream os, String comment, Charset charset) | 将属性列表写入 XML 输出流,并指定编码 |
| getProperty(String key) | 获取属性值 |
| getProperty(String key, String defaultValue) | 获取属性值,如果属性不存在则返回默认值 |
| propertyNames() | 获取所有属性的键 |
| stringPropertyNames() | 获取所有属性的键,并以字符串形式返回 |
| list(PrintStream out) | 将属性列表输出到指定的打印流 |
| list(PrintWriter out) | 将属性列表输出到指定的打印流 |
代码示例
▼java复制代码Properties prop = new Properties(); prop.put("name", "Tom"); prop.put("age", "18"); System.out.println(prop); // {name=Tom, age=18}
▼java复制代码File propertiesFile = new File(System.getProperty("user.dir") + File.separator + "test.properties"); InputStream inputStream = new FileInputStream(propertiesFile); Properties prop = new Properties(); prop.load(inputStream); System.out.println(prop);
综合练习
带权重的随机点名
▼java复制代码// 姓名文件,格式:姓名-权重 File file = new File(System.getProperty("user.dir") + File.separator + "name.txt"); // 读取文件,并转为学生对象 BufferedReader br = new BufferedReader(new FileReader(file)); List<Student> studentList = br.lines().map(e -> { String[] split = e.split("-"); String name = split[0]; double weight = Double.parseDouble(split[1]); return new Student(name, weight); }).toList(); br.close(); // 计算权重和 double weightSum = studentList.stream().map(Student::getWeight).reduce(0.0, Double::sum); // 按照顺序计算每一个学生的权重范围 double[] weightArr = new double[studentList.size()]; // 用于记录上一个学神的权重 double prevStudentWeight = 0.0; for (int i = 0; i < studentList.size(); i++) { // 在当前学生的权重上加上前一个学生的权重就是当前学生的权重范围 weightArr[i] = studentList.get(i).getWeight() / weightSum + prevStudentWeight; // 更新 prevStudentWeight = weightArr[i]; } // 随机生成一个权重值 double weight = Math.random(); // 使用二分查找判断该权重值在哪个权重范围 int index = Arrays.binarySearch(weightArr, weight); // 根据查找返回的下标计算真实下标 int realIndex = -index - 1; // 根据下标获取学生信息 Student student = studentList.get(realIndex); System.out.println(student); // 将当前学生的权重在此基础上除以 2 student.setWeight(student.getWeight() / 2); // 将全部姓名在写回文件 BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(file)); for (Student s : studentList) { bufferedWriter.write(s.getName() + "-" + s.getWeight()); bufferedWriter.newLine(); } bufferedWriter.close();
