何为 IO 流?
在计算机中,内存和磁盘需要进行数据传输(内存从磁盘中读入数据,进行处理,然后写入磁盘中),而数据传输需要通道。所以,这里的 “IO 流”就是数据传输的通道。
内存和磁盘数据交互图如下:
大致意思:内存通过输入流从磁盘中读取数据,这个过程也称为读;内存通过输出流将数据保存到磁盘中,这个过程也称为写。
所以,简单来说,IO 流的作用就是 通过 IO 流,可以完成对磁盘文件的读和写。
字节流和字符流的区别?
字节流:按照字节方式读取,一次读一个字节,可以读取任意类型的文件。如:文本文件、图片、视频文件,等…字符流:按照字符方式读取,一次读取一个字符。它是为了更方便读取普通的文本文件,无法读取图片、word文件、视频文件等…举个例子:
有一个 test.txt 文件,它里面的内容是:z爱中国
用字节流读:
第一次读:读一个字节,正好读到 ‘a’ 第二次读:读一个字节,正好读到 ‘中’ 的一半 第三次读:读一个字节,读到 ‘中’ 的另一半
Tips:在 Windows 系统中,字母只占一个字节(在 Java 中,字母(char) 占用两个字节,但这个文件与 Java 无关,它只是 Windows 操作系统上的一个文件),而汉字占两个字节。
用字符流读:
第一次读:读一个字符,正好读到 ‘a’ 第而次读:读一个字节,正好读到 ‘中’
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”举例,因为其它流与之相似
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(); } } }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);场景:使用文件字节流实现文件的赋值:把文件 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(); } }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(); } }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(); } }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 就不说了。
DataOutputStream:此流可以将数据以及数据类型写入文件中。 注意:此文件不是普通文件(无法用记事本打开)
此类流是标准输出流。
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("我是中国人"); } }示例:
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(); } }主要用于:对象的序列化和反序列化
示例(序列化):
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 接口。
好了,一些流的基本使用就到这儿了。多看看它们的 API 文档吧。