Java BIO
一、字节流
1. 概要
字节流有两个核心抽象类:InputStream 和 OutputStream。所有的字节流类都继承自这两个抽象类。
InputStream 负责输入,OutputStream 负责输出。
字节流主要操作byte类型数据。
以下为 JDK8 版本中字节流的族谱图:
由上图可以看出,InputStream 和 OutputStream对于数据源的操作往往是成对出现的。
2. InputStream
InputStream的作用是用来表示哪些从不同数据源产生输入的类。
InputStream类型表
类 | 功能 | 构造器 |
ByteArrayInputStream | 允许将内存的缓冲区当做InputStream使用 | 缓冲区,字节将从中取出 |
StringBufferInputStream | 将String转换成InputStream | 字符串。底层实现实际使用StringBuffer |
FileInputStream | 从文件中读取信息 | 字符串,表示文件名、文件或FileDescriptor对象 |
PipedInputStream | 产生用于写入相关PipedOutputStream的数据。实现“管道化”概念 | PipedOutputStream |
SequenceInputStream | 将多个InputStream对象合并为一个InputStream | 两个InputStream对象或一个容纳InputStream对象的容器Enumeration |
FilterInputStream | 抽象类,作为“装饰器”的接口。其中“装饰器”为其他InputStream类提供有用功能 |
3. OutputStream
OutputStream决定了数据的输出形式。
OutputStream类型表
类 | 功能 | 构造器 |
ByteArrayOutputStream | 允许将内存的缓冲区当做InputStream使用 | 缓冲区初始化尺寸(可选的) |
FileOutputStream | 从文件中读取信息 | 字符串,表示文件名、文件或FileDescriptor对象 |
PipedOutputStream | 产生用于写入相关PipedOutputStream的数据。实现“管道化”概念 | PipedInputStream |
FilterOutputStream | 抽象类,作为“装饰器”的接口。其中“装饰器”为其他OutputStream类提供有用功能 |
4. 文件字节流
文件字节流有两个类:FileOutputStream 和 FileInputStream。
它们提供了方法将字节写入到文件和将数据以字节形式从文件中读取出来。
一般情形下,文件字节流操作遵循以下几个步骤:
(1)使用File类绑定一个文件。
(2)把File对象绑定到流对象上。
(3)进行读、写操作。
(4)关闭输入、输出。
FileOutputStream
向文件写入内容
import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; public class FileOutputStreamDemo { public static void write1(OutputStream out, byte[] b) throws IOException { out.write(b); // 将内容输出,保存文件 } public static void write2(OutputStream out, byte[] b) throws IOException { for (int i = 0; i < b.length; i++) { // 采用循环方式写入 out.write(b[i]); // 每次只写入一个内容 } } public static void main(String args[]) throws Exception { // 第1步、使用File类找到一个文件 File f = new File("d:" + File.separator + "test.txt"); // 声明File对象 // 第2步、通过子类实例化父类对象 OutputStream out = new FileOutputStream(f); // 通过对象多态性,进行实例化 // 实例化时,默认为覆盖原文件内容方式;如果添加true参数,则变为对原文件追加内容的方式。 // OutputStream out = new FileOutputStream(f, true); // 第3步、进行写操作 String str = "Hello World\r\n"; // 准备一个字符串 byte b[] = str.getBytes(); // 只能输出byte数组,所以将字符串变为byte数组 write1(out, b); // write2(out, b); // 第4步、关闭输出流 out.close(); } };
FileInputStream
向文件读取内容
import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; public class FileInputStreamDemo { public static void read1(InputStream input, byte[] b) throws IOException { int len = input.read(b); // 读取内容 System.out.println("读入数据的长度:" + len); } public static void read2(InputStream input, byte[] b) throws IOException { for (int i = 0; i < b.length; i++) { b[i] = (byte) input.read(); // 读取内容 } } public static void read3(InputStream input, byte[] b) throws IOException { int len = 0; int temp = 0; // 接收每一个读取进来的数据 while ((temp = input.read()) != -1) { // 表示还有内容,文件没有读完 b[len] = (byte) temp; len++; } } public static void main(String args[]) throws Exception { // 异常抛出,不处理 // 第1步、使用File类找到一个文件 File f = new File("d:" + File.separator + "test.txt"); // 声明File对象 // 第2步、通过子类实例化父类对象 InputStream input = new FileInputStream(f); // 准备好一个输入的对象 // 第3步、进行读操作 // 有三种读取方式,体会其差异 byte[] b = new byte[(int) f.length()]; read1(input, b); // read2(input, b); // read3(input, b); // 第4步、关闭输入流 input.close(); System.out.println("内容为:\n" + new String(b)); // 把byte数组变为字符串输出 } };
5. 内存操作流
ByteArrayInputStream 和 ByteArrayOutputStream是用来完成内存的输入和输出功能。
内存操作流一般在生成一些临时信息时才使用。
如果临时信息保存在文件中,还需要在有效期过后删除文件,这样比较麻烦。
使用内存操作流完成一个转换为小写字母的程序
import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; public class ByteArrayDemo { public static void main(String args[]) { String str = "HELLOWORLD"; ByteArrayInputStream bis = new ByteArrayInputStream(str.getBytes()); // 内存输入流 ByteArrayOutputStream bos = new ByteArrayOutputStream(); // 内存输出流 // 用ByteArrayInputStream读取字符串内容, // 全转为小写后,写入ByteArrayOutputStream int temp = 0; while((temp = bis.read()) != -1) { char c = (char) temp; // 读取的数字变为字符 bos.write(Character.toLowerCase(c)); // 将字符变为小写 } // 所有的数据就全部都在ByteArrayOutputStream中 String newStr = bos.toString(); // 取出内容 try { bis.close(); bos.close(); } catch(IOException e) { e.printStackTrace(); } System.out.println(newStr); } };
6. 管道流
管道流的主要作用是可以进行两个线程间的通信。
如果要进行管道通信,则必须把PipedOutputStream连接在PipedInputStream上。
为此,PipedOutputStream中提供了connect()方法。
import java.io.IOException; import java.io.PipedInputStream; import java.io.PipedOutputStream; class Send implements Runnable { private PipedOutputStream pos = null; // 管道输出流 public Send() { this.pos = new PipedOutputStream(); // 实例化输出流 } public void run() { String str = "Hello World!!!"; // 要输出的内容 try { this.pos.write(str.getBytes()); } catch (IOException e) { e.printStackTrace(); } try { this.pos.close(); } catch (IOException e) { e.printStackTrace(); } } public PipedOutputStream getPos() { // 得到此线程的管道输出流 return this.pos; } }; class Receive implements Runnable { private PipedInputStream pis = null; // 管道输入流 public Receive() { this.pis = new PipedInputStream(); // 实例化输入流 } public void run() { byte b[] = new byte[1024]; // 接收内容 int len = 0; try { len = this.pis.read(b); // 读取内容 } catch (IOException e) { e.printStackTrace(); } try { this.pis.close(); // 关闭 } catch (IOException e) { e.printStackTrace(); } System.out.println("接收的内容为:" + new String(b, 0, len)); } public PipedInputStream getPis() { return this.pis; } }; public class PipedDemo { public static void main(String args[]) { Send s = new Send(); Receive r = new Receive(); try { s.getPos().connect(r.getPis()); // 连接管道 } catch (IOException e) { e.printStackTrace(); } new Thread(s).start(); // 启动线程 new Thread(r).start(); // 启动线程 } };
7. 数据操作流
数据操作流提供了格式化读入和输出数据的方法,分别为DataInputStream 和 DataOutputStream。
DataOutputStream
将格式化的订单数据写入到文件
import java.io.DataOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; public class DataOutputStreamDemo { public static void main(String args[]) throws IOException { // 第1步、使用File类找到一个文件 File f = new File("d:" + File.separator + "order.txt"); // 第2步、通过子类实例化父类对象 DataOutputStream dos = new DataOutputStream(new FileOutputStream(f)); // 实例化数据输出流对象 // 第3步、进行写操作 String names[] = { "衬衣", "手套", "围巾" }; // 商品名称 float prices[] = { 98.3f, 30.3f, 50.5f }; // 商品价格 int nums[] = { 3, 2, 1 }; // 商品数量 // 循环输出 for (int i = 0; i < names.length; i++) { dos.writeChars(names[i]); dos.writeChar('\t'); dos.writeFloat(prices[i]); dos.writeChar('\t'); dos.writeInt(nums[i]); dos.writeChar('\n'); } // 第4步、关闭输出流 dos.close(); } };
DataInputStream
将订单文件中的格式化数据读取出来
import java.io.DataInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; public class DataInputStreamDemo { public static void main(String args[]) throws IOException { // 所有异常抛出 // 第1步、使用File类找到一个文件 File f = new File("d:" + File.separator + "order.txt"); // 第2步、通过子类实例化父类对象 DataInputStream dis = new DataInputStream(new FileInputStream(f)); // 实例化数据输入流对象 // 第3步、进行读操作 String name = null; // 接收名称 float price = 0.0f; // 接收价格 int num = 0; // 接收数量 char temp[] = null; // 接收商品名称 int len = 0; // 保存读取数据的个数 char c = 0; // '\u0000' try { while (true) { temp = new char[200]; // 开辟空间 len = 0; while ((c = dis.readChar()) != '\t') { // 接收内容 temp[len] = c; len++; // 读取长度加1 } name = new String(temp, 0, len); // 将字符数组变为String price = dis.readFloat(); // 读取价格 dis.readChar(); // 读取\t num = dis.readInt(); // 读取int dis.readChar(); // 读取\n System.out.printf("名称:%s;价格:%5.2f;数量:%d\n", name, price, num); } } catch (Exception e) { } // 第4步、关闭输入流 dis.close(); } };
8. 合并流
合并流的主要功能是将多个InputStream合并为一个InputStream流。
合并流的功能由SequenceInputStream完成。
需要稍微留意的是,由前面的字节流族谱图,可以得知并没有对应的OutputStream。
将两个文件内容合并为一个文件
import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.SequenceInputStream; public class SequenceDemo { public static void main(String args[]) throws IOException { // 第1步,绑定要操作的文件 InputStream is1 = new FileInputStream("d:" + File.separator + "a.txt"); // 输入流1 InputStream is2 = new FileInputStream("d:" + File.separator + "b.txt"); // 输入流2 OutputStream os = new FileOutputStream("d:" + File.separator + "ab.txt"); // 输出流 // 第2步,绑定要合并的InputStream对象到SequenceInputStream SequenceInputStream sis = new SequenceInputStream(is1, is2); // 实例化合并流 // 读取两个InputStream流的数据,然后合并输出到OutputStream int temp = 0; // 接收内容 while ((temp = sis.read()) != -1) { // 循环输出 os.write(temp); // 保存内容 } // 第4步,关闭相关流 sis.close(); os.close(); is1.close(); is2.close(); } };
9. 对象流(序列化与反序列化流)
ObjectInputStream: 反序列化流
ObjectOutputStream: 序列化流
构造方法
ObjectOutputStream(OutputStream out)
ObjectInputStream(InputStream in)
常用方法
writeObject(obj) 将 obj 对象写入到流中
readObject 读取流中的数据
EOFException表示输入过程中意外地到达文件尾或流尾的信号
二、字符流
Java程序中,一个字符等于两个字节。
Reader 和 Writer 两个就是专门用于操作字符流的类。
1. Writer
Writer是一个字符流的抽象类。
定义如下:
public abstract class Writer implements Appendable, Closeable, Flushable
直接子类
- BufferedWriter
- CharArrayWriter
- FilterWriter
- OutputStreamWriter
- PipedWriter
- PrintWriter
- StringWriter
2. Reader
Reader是读取字符流的抽象类。
定义如下:
public abstract class Reader implements Readable, Closeable
直接子类
- BufferedReader
- CharArrayReader
- FilterReader
- InputStreamReader
- PipedReader
- StringReader
3. 字节流 vs 字符流
字节流和字符流的使用方式非常相似,都有read()、write()、close()、flush()这样的方法。
字符流写入 要刷新/关流才能写入(刷新也是调用了flush(刷新)方法)
就像水龙头带软管,开水龙头要等一会儿水才能流出来,就和需要刷新一样字节流不需要刷新,是因为字节流是一个字节一个字节读和写的,而字符流是三个字节或者两个字节读和写的,所以
字符流需要刷新,而字节流不需要刷新
4. 文件字符流
文件字符流有两个类:FileWriter 和 FileReader。
它们提供了方法将字符写入到文件和将数据以字符形式从文件中读取出来。
一般情形下,文件字节流操作遵循以下几个步骤:
(1)使用File类绑定一个文件。
(2)把File对象绑定到流对象上。
(3)进行读、写操作。
(4)关闭输入、输出。
FileWriter
向文件中写入内容
import java.io.File; import java.io.Writer; import java.io.FileWriter; public class WriterDemo { public static void main(String args[]) throws Exception { // 异常抛出,不处理 // 第1步、使用File类找到一个文件 File f = new File("d:" + File.separator + "test.txt"); // 声明File对象 // 第2步、通过子类实例化父类对象 Writer out = new FileWriter(f); // Writer out = new FileWriter(f, true); // 追加内容方式 // 第3步、进行写操作 String str = "Hello World!!!\r\n"; out.write(str); // 将内容输出 // 第4步、关闭输出流 out.close(); } };
FileReader
从文件中读取内容
import java.io.File; import java.io.IOException; import java.io.Reader; import java.io.FileReader; public class ReaderDemo01 { // 将所有内容直接读取到数组中 public static int read1(Reader input, char[] c) throws IOException { int len = input.read(c); // 读取内容 return len; } // 每次读取一个字符,直到遇到字符值为-1,表示读文件结束 public static int read2(Reader input, char[] c) throws IOException { int temp = 0; // 接收每一个内容 int len = 0; // 读取内容 while ((temp = input.read()) != -1) { // 如果不是-1就表示还有内容,可以继续读取 c[len] = (char) temp; len++; } return len; } public static void main(String args[]) throws Exception { // 第1步、使用File类找到一个文件 File f = new File("d:" + File.separator + "test.txt"); // 第2步、通过子类实例化父类对象 Reader input = new FileReader(f); // 第3步、进行读操作 char c[] = new char[1024]; // int len = read1(input, c); int len = read2(input, c); // 第4步、关闭输出流 input.close(); System.out.println("内容为:" + new String(c, 0, len)); // 把字符数组变为字符串输出 } };
5. 带缓冲区的字符流
带缓冲区的字符输入流:BufferedReader:常用方法:readLine() 读取一行,如果为文件末尾,返回值为null。
带缓冲区的字符输出流:BufferedWriter:常用方法:writer(string)将字符串写入 到输出流。 newLine()根据系统的行分割符进行换行。
6. 转换流
InputStreamReader 将字节流转换成字符流 输入
OutputStreamWriter 将字节流转换成字符流 输出
构造方法
InputStreamReader(InputStream in)
InputStreamReader(InputStream in, String cs)
OutputStreamWriter(OutputStream out)
OutputStreamWriter(OutputStream out, String charsetName)
作用
① 可以将字节流转换成字符流
② 可以使用指定的编码来读取或写入流。
参考