Java 中的 IO 流的基本使用

it2025-04-13  22

1. IO 流简介

何为 IO 流?

在计算机中,内存和磁盘需要进行数据传输(内存从磁盘中读入数据,进行处理,然后写入磁盘中),而数据传输需要通道。所以,这里的 “IO 流”就是数据传输的通道。

内存和磁盘数据交互图如下:

大致意思:内存通过输入流从磁盘中读取数据,这个过程也称为读;内存通过输出流将数据保存到磁盘中,这个过程也称为写。

所以,简单来说,IO 流的作用就是 通过 IO 流,可以完成对磁盘文件的读和写。

2. IO 流的分类

按流的方向分(以内存为参照物):输入流(往内存中去)、输出流(从内存中出来)按数据的读取方式:字节流、字符流

字节流和字符流的区别?

字节流:按照字节方式读取,一次读一个字节,可以读取任意类型的文件。如:文本文件、图片、视频文件,等…字符流:按照字符方式读取,一次读取一个字符。它是为了更方便读取普通的文本文件,无法读取图片、word文件、视频文件等…

举个例子:

有一个 test.txt 文件,它里面的内容是:z爱中国

用字节流读:

第一次读:读一个字节,正好读到 ‘a’ 第二次读:读一个字节,正好读到 ‘中’ 的一半 第三次读:读一个字节,读到 ‘中’ 的另一半

Tips:在 Windows 系统中,字母只占一个字节(在 Java 中,字母(char) 占用两个字节,但这个文件与 Java 无关,它只是 Windows 操作系统上的一个文件),而汉字占两个字节。

用字符流读:

第一次读:读一个字符,正好读到 ‘a’ 第而次读:读一个字节,正好读到 ‘中’

3. Java 中的 IO 流

Java 中的 IO 流都在 java.io.* 包下,这块被称为“四大家族”(所有的 IO 类只有四类),四大家族的首领(这四类 IO 流的顶级父类)如下:

java.io.InputStream:字节输入流java.io.OutputStream:字节输出流java.io.Reader:字符输入流java.io.Writer:字符输出流

特点:

四大家族的首领都是抽象类所有的 IO 流都实现了 java.io.Closeable 接口,都是可关闭的(close() 方法),用完之后要关闭,不然会占用很多资源所有的输出流都实现了 java.io.Flushable 接口,都是可刷新的(flush() 方法),用完之后,记得刷新

Java 中需要掌握的 IO 流有16个,如下:

文件专属(操作文件):

java.io.FileInputStreamjava.io.FileOutputStreamjava.io.FileReaderjava.io.FileWriter

转换流(将字节流转换为字符流):

java.io.InputStreamReaderjava.io.OutputStreamWriter

缓冲专属:

java.io.BufferedInputStreamjava.io.BufferedOutputStreamjava.io.BufferedReaderjava.io.BufferedWriter

数据流专属:

java.io.DataInputStreamjava.io.DataOutputStream

标准输出流:

java.io.PrintStreamjava.io.PrintWriter

对象专属流:

java.io.ObjectInputStreamjava.io.ObjectOutputStream

这里重点以“FileInputStream/FileOutputStream”举例,因为其它流与之相似

3.1 FileInputStream/FileOutputStream

3.1.1 FileInputStream

FileInputStream:文件字节输入流

场景:使用文件字节流读取磁盘中的一个文件temp.txt,其内容为:abcdef

示例一:

public class FileStreamDemo { public static void main(String[] args) { // 文件绝对路径名 String name = "E:\\zzc\\temp.txt"; FileInputStream fis = null; try { fis = new FileInputStream(name); // 开始读 // read() 方法返回的是:读取到的“字节”本身。 // 例如:读取到的字符是'a',则返回97 int data = fis.read(); System.out.println(data); // 97 data = fis.read(); System.out.println(data); // 98 data = fis.read(); System.out.println(data); // 99 data = fis.read(); System.out.println(data); // 100 data = fis.read(); System.out.println(data); // 101 data = fis.read(); System.out.println(data); // 102 // 读取到文件末尾返回 -1 data = fis.read(); System.out.println(data); // -1 } catch (Exception e) { e.printStackTrace(); } finally { if (null != fis) { try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } } } }

如果一个文件内容巨多,照上面那种方式肯定就不行了。所以,下面用循环的方式进行读取。

示例二:

public class FileStreamDemo { public static void main(String[] args) { // 文件绝对路径名 String name = "E:\\zzc\\temp.txt"; FileInputStream fis = null; try { fis = new FileInputStream(name); int data = 0; while ((data = fis.read()) != -1) { System.out.println(data); } } catch (Exception e) { e.printStackTrace(); } finally { if (null != fis) { try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } } } }

但是,上面还会有一个问题:就是一次只读取一个字节,那么要读取所有字节,就要读取多次。这就会意味着内存要和磁盘频繁交互,这样性能会严重降低。所以,就要考虑一次能否读取多个字节。

查看 API 文档,可知:调用 read(byte[] b) 方法

示例三:

public class FileStreamDemo { public static void main(String[] args) throws Exception{ // 文件绝对路径名 String name = "E:\\zzc\\temp.txt"; FileInputStream fis = new FileInputStream(name); byte[] bytes = new byte[4]; int length = 0; while ((length = fis.read(bytes)) != -1) { // 将 byte 数组转换成字符串 System.out.print(new String(bytes, 0, length)); } if (null != fis) { fis.close(); } } }

3.1.2 FileOutputStream

FileOutputStream:文件字节输出流

场景:使用文件输出流向磁盘某个文件写入数据

示例一:

public class FileOutputStreamDemo { public static void main(String[] args) throws Exception{ // temp.txt 文件不存在时,会自动新建 FileOutputStream fos = new FileOutputStream("temp.txt"); byte[] bytes = {97, 98, 99, 100}; fos.write(bytes); // 写完之后,一定要刷新 fos.flush(); fos.close(); } }

注意:如果文件不存在,则会新建文件。而且每次向此文件中写入数据之前,都会清空此文件。

那么,每次向此文件写入数据时,能不能向此文件中追加内容呢?

使用这个构造方法:

FileOutputStream(String name, boolean append):创建一个向具有指定 name 的文件中写入数据的输出文件流。如果第二个参数为 true,则将字节写入文件末尾处,而不是写入文件开始处。

如何向文件中写入汉字呢?

使用如下方式:

String data = "我爱中国"; // 将字符串转换为字节数组 byte[] bytes = data.getBytes(); fos.write(bytes);

3.1.3 使用文件字节流

场景:使用文件字节流实现文件的赋值:把文件 temp.txt 中的内容复制到 test.txt 文件中去。

示例:

public class FileStreamDemo { public static void main(String[] args) throws Exception{ FileInputStream fis = new FileInputStream("E:\\zzc\\temp.txt"); FileOutputStream fos = new FileOutputStream("E:\\zzc\\test.txt"); byte[] bytes = new byte[1024]; int len = 0; while ((len = fis.read(bytes)) != -1) { fos.write(bytes, 0, len); } if (null != fos) { fos.flush(); } if (null != fis) { fis.close(); } fos.close(); } }

3.2 FileReader/FileWriter

3.2.1 FileReader

FileReader:文件字符输入流,只能读取普通文本,方便、快捷

场景:使用文件字符输入流读取一个txt文件(包含中文)

示例:

public class FileReaderDemo { public static void main(String[] args) throws Exception{ FileReader fr = new FileReader("temp.txt"); char[] chars = new char[4]; int length = 0; while ((length = fr.read(chars)) != -1) { System.out.print(new String(chars, 0, length)); } fr.close(); } }

3.2.2 FileWriter

FileWriter:文件字符输出流,只能写普通文本

场景:使用文件字符输出流向一个txt文件写入数据(包含中文)

public class FileWriterDemo { public static void main(String[] args) throws Exception { FileWriter fw = new FileWriter("fw.txt"); char[] chars = {'我', '是', '中', '国', '人'}; fw.write(chars); fw.flush(); fw.close(); } }

3.3 BufferedReader/BufferedWriter

3.3.1 BufferedReader

BufferedReader:带有缓冲区的字符输入流。使用此流时,不需要自定义 byte/char 数组。自带缓冲。

示例一:

public class BufferedReaderDemo { public static void main(String[] args) throws Exception { BufferedReader br = new BufferedReader(new FileReader("temp.txt")); String line = null; while (null != (line = br.readLine())) { System.out.print(line); } br.close(); } }

示例二:

public class BufferedReaderDemo { public static void main(String[] args) throws Exception { BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("temp.txt"))); String line = null; while (null != (line = br.readLine())) { System.out.print(line); } br.close(); } }

示例一与示例二的区别:示例二中使用了字节流来读取 temp.txt,但 BufferedReader 的构造方法中只能传入 Reader 类型的参数。所以,这里使用了 字节输入流来转换。

这里 BufferedWriter 就不说了。

3.4 DataOutputStream/DataInputStream

DataOutputStream:此流可以将数据以及数据类型写入文件中。 注意:此文件不是普通文件(无法用记事本打开)

3.5 PrintStream/PrintWriter

此类流是标准输出流。

3.5.1 PrintStream

PrintStream 是字节输出流,默认输出到控制台。

示例:

public class PrintStreamDemo { public static void main(String[] args) { String msg = "Hello PS"; PrintStream out = System.out; // System.out.println(msg); out.println(msg); } }

既然是默认输出到控制台。那么可以改变标准输出流的方向吗?

当然可以。

public class PrintWriterDemo { public static void main(String[] args) throws Exception{ // 标准输出流不再指向控制台,而是指向 log 文件 PrintStream ps = new PrintStream(new FileOutputStream("log")); // 修改输出方向,将输出方向从控制台修改为 log 文件 System.setOut(ps); ps.print("我是中国人"); } }

3.5.2 PrintWriter

示例:

public class PrintWriterDemo { public static void main(String[] args) throws Exception{ // 若此文件不存在,则创建 PrintWriter pw = new PrintWriter("pw.txt"); String data = "Hello PrintWriter"; pw.write(data); // 使用字符输出流,则需要调用 flush() 方法 // 字符内部也是用字节。相当于字符内部有缓存区,如果不调用 flush() 方法,那么数据只会存留在缓冲区中, // 并不会输出到文本中去 pw.flush(); // 当调用 close() 时,它会先调用 flush() 方法。即使不显示调用 flush() 方法 pw.close(); } }

3.6 ObjectOutputStream/ObjectInputStream

主要用于:对象的序列化和反序列化

示例(序列化):

public class User implements Serializable { private static final long serialVersionUID = -2002612691013855369L; private String name; private int age; public User(String name, int age) { this.name = name; this.age = age; } ... }

User 类实现了 Serializable 接口

public class ObjectOutputStreamDemo { public static void main(String[] args) throws Exception{ ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("users.txt")); User user = new User("zzc", 18); oos.writeObject(user); oos.flush(); oos.close(); } }

注意:参与序列化和反序列化的对象都要实现 Serializable 接口。

3.7 关于内存的流

public class MemoryStream { public static void main(String[] args) throws Exception{ // 1. 内存字节流 ByteArrayOutputStream baos = new ByteArrayOutputStream(); String data = "Hello Memory"; // 将数据写入内存 baos.write(data.getBytes()); // 获取内存中的数据 byte[] bytes = baos.toByteArray(); //System.out.println(new String(bytes)); baos.close(); ByteArrayInputStream bais = new ByteArrayInputStream(bytes); int ch; while ((ch = bais.read()) != -1) { System.out.print((char) ch); } bais.close(); // 2. 内存字符数组流 CharArrayWriter caw = new CharArrayWriter(); char[] chs = {'H', 'e', 'l', 'l', 'o'}; caw.write(chs); caw.close(); // 返回内存中数据的引用 char[] data = caw.toCharArray(); CharArrayReader car = new CharArrayReader(data); int ch; while ((ch = car.read()) != -1) { System.out.print((char) ch); } car.close(); // 3. 内存字符串流 StringWriter sw = new StringWriter(); String data = "Hello World"; sw.write(data); sw.close(); String s = sw.toString(); StringReader sr = new StringReader(s); int ch; while((ch = sr.read()) != -1) { System.out.print((char) ch); } sr.close(); } }

好了,一些流的基本使用就到这儿了。多看看它们的 API 文档吧。

最新回复(0)