概述
1.什么是流?
流是一个抽象概念,是对数据源的抽象,数据源可以是文件/网络/内存等。Java程序中数据的输入/输出都是以流的方式进行,同时,流具有方向性,以当前应用程序作为参考,数据从程序流向数据源称之为输出流;数据从数据源流向当前应用程序称之为输入流
2.流的种类?
- 字节流/字符流: 按照应用从流中读出/写入的数据类型是byte / char划分
- 输入流/输出流: 按照流的流向划分
3.数据源
- File : FileXXXX
- Memory : ByteArrayXXXStream等
- Network: SocketInputStream等
4.包装上述流,增加额外功能
- 线程间输入输出数据:Thread a(writer) -> Thread b(Reader) --> PipedXXX
- 缓存流:读取/写入数据提供缓冲,BufferedXXXX
- 多个输入流:保存一组输入流,挨个读取每个输入流中的数据 SequenceInputStream
- 读取/写入java原始类型/对象类型: ObjectXXXX, DataXXXX
示例代码
下文中示例代码中依赖的常量
public final class TestConstants {
public static final String READ_FILE_NAME = "src/main/resources/test-case-stream-read.txt";
public static final String WRITE_FILE_NAME = "src/main/resources/test-case-stream-write.txt";
}
文件
字节流操作示例:直接从磁盘中的文件读取/写入数据
/**
* 字节流: 数据源是文件
*/
@Test
public void testFile() {
byte[] dataSource = "Data Source: File".getBytes();
try (FileOutputStream out = new FileOutputStream(TestConstants.WRITE_FILE_NAME)) {
out.write(dataSource);
} catch (IOException e) {
e.printStackTrace();
}
byte[] readData = new byte[dataSource.length];
try (FileInputStream in = new FileInputStream(TestConstants.WRITE_FILE_NAME)) {
in.read(readData);
} catch (IOException e) {
e.printStackTrace();
}
Assert.assertArrayEquals(dataSource, readData);
}
字符流操作示例:内部使用InputStreamReader/OutputStreamWriter桥接,完成底层File中字节到字符的转换,转换实现类使用StreamDecoder/StreamEncoder
/**
* 字符流: 文件作为数据源
*/
@Test
public void testFile() {
String dataSource = "Data Source: File";
try (FileWriter writer = new FileWriter(TestConstants.WRITE_FILE_NAME)) {
writer.write(dataSource);
} catch (IOException e) {
e.printStackTrace();
}
char[] readData = new char[dataSource.length()];
try (FileReader reader = new FileReader(TestConstants.WRITE_FILE_NAME)) {
reader.read(readData);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
Assert.assertEquals(dataSource, new String(readData));
}
内存
字节流操作示例:直接读取/写入内存中的字节数组,close方法不影响流的使用。
/**
* 字节流:数组源是内存中的字节数组
*/
@Test
public void testByteArray() {
byte[] dataSource = "Data Source: Byte array".getBytes();
//写入操作
byte[] dataFromOut = null; //作为读取的输入源
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
out.write(dataSource); // 写数据到内存中的字节数组
dataFromOut = out.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
//读取操作
byte[] result = new byte[dataFromOut.length];
try (ByteArrayInputStream in = new ByteArrayInputStream(dataFromOut)) {
in.read(result);
} catch (IOException e) {
e.printStackTrace();
}
Assert.assertArrayEquals(result, dataSource);
}
字符流操作示例: 直接读取/写入内存中的字符数组,close方法不影响流的使用。
/**
* 字符流: 数据源是字符数组
*/
@Test
public void testCharArray() throws IOException {
String dataSource = "Data Source: char array";
//写入操作
CharArrayWriter writer = new CharArrayWriter();
writer.write(dataSource);
String writerData = writer.toString();
Assert.assertEquals(writerData, dataSource);
//读取操作
CharArrayReader reader = new CharArrayReader(writer.toCharArray());
char[] readerData = new char[dataSource.length()];
reader.read(readerData);
Assert.assertEquals(dataSource, new String(readerData));
}
增加额外功能
字节流 -字符流 桥接:实现字符流与字节流之间的相互转换。继承自Reader接口,依赖InputStream接口,使用StreamDecoder完成转换工作,所有方法的请求都委托给StreamDecoder去完成。
/**
* 字符流: 实现字符流和字节流直接的转换
*/
@Test
public void testBridge() {
String dataSource = "byte Stream - char Stream";
try (OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(TestConstants.WRITE_FILE_NAME))) {
writer.write(dataSource);
} catch (IOException e) {
e.printStackTrace();
}
try (InputStreamReader reader = new InputStreamReader(new FileInputStream(TestConstants.WRITE_FILE_NAME))) {
char[] readData = new char[dataSource.length()];
reader.read(readData, 0, readData.length);
Assert.assertArrayEquals(dataSource.toCharArray(), readData);
} catch (IOException e) {
e.printStackTrace();
}
}
缓冲功能:内容使用数组来存储待读取和待写入的数据,提供一个缓冲区。
/**
* 字节流: 包装文件流,增加缓冲功能, 提高流的读取/写入效率
*/
@Test
public void testBuffered() {
byte[] dataSource = "Data Source: other input stream -> file".getBytes();
try (BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(TestConstants.WRITE_FILE_NAME))) {
out.write(dataSource);
} catch (IOException e) {
e.printStackTrace();
}
byte[] readData = new byte[dataSource.length];
try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(TestConstants.WRITE_FILE_NAME))) {
in.read(readData);
} catch (IOException e) {
e.printStackTrace();
}
Assert.assertArrayEquals(dataSource, readData);
}
/**
* 字符流: 增加缓存功能, 读取一行文本方法readLine()
*/
@Test
public void testBuffered() {
String[] dataSource = {"test1", "test2", "test3", "", "测试1", "测试2"};
try (BufferedWriter writer = new BufferedWriter(new FileWriter(TestConstants.WRITE_FILE_NAME))) {
for(String str : dataSource) {
writer.write(str);
writer.newLine();
}
} catch (IOException e) {
e.printStackTrace();
}
try (BufferedReader reader = new BufferedReader(new FileReader(TestConstants.WRITE_FILE_NAME))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
管道:缓冲区在读线程这边(PipedInputStream类中)
/**
*字节流: 在线程之间单向传递字节数据
*/
@Test
public void testBytePiped() throws IOException {
byte[] dataSource = "Data Source: byte pipe test".getBytes();
PipedOutputStream out = new PipedOutputStream();
PipedInputStream in = new PipedInputStream(out);
//发送数据线程
new Thread(()->{
try {
out.write(dataSource);
} catch (IOException e) {
e.printStackTrace();
}
}).start();
byte[] readData = new byte[dataSource.length];
in.read(readData);
Assert.assertArrayEquals(dataSource, readData);
}
/**
* 字符流: 实现线程之间的单向管道
*/
@Test
public void testPiped() throws IOException {
String dataSource = "Data Source :Piped test";
PipedWriter writer = new PipedWriter();
PipedReader reader = new PipedReader(writer);
//发送数据线程,本线程接收数据
new Thread(() -> {
try {
writer.write(dataSource);
} catch (IOException e) {
e.printStackTrace();
}
}).start();
char[] readData = new char[100];
int count = reader.read(readData);
System.out.println(new String(readData, 0, count));
}
退回读出的数据:使用一个数组存储被退回的数据,每次读取优先读取该数组,后读取流中的数据
/**
* 字节流: 读取一个字节,然后退回,重新读取
* @throws FileNotFoundException
*/
@Test
public void testPushBack() throws FileNotFoundException {
try (PushbackInputStream in = new PushbackInputStream(new FileInputStream(TestConstants.READ_FILE_NAME))) {
int value1 = in.read();
in.unread(value1);
int value2 = in.read();
Assert.assertEquals(value1, value2);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 字符流: 读取一个字节后退回到流中,重新读取
*/
@Test
public void testPushBack(){
try (PushbackReader reader = new PushbackReader(new FileReader(TestConstants.READ_FILE_NAME))) {
char ch1 = (char)reader.read();
reader.unread(ch1);
char ch2 = (char) reader.read();
Assert.assertEquals(ch1, ch2);
} catch (IOException e) {
e.printStackTrace();
}
}
多个输入流,循环读取:
/**
* 读取多个流中的数据
* @throws IOException
*/
@Test
public void testSequenceInputStream() throws IOException {
byte[] dataSource1 = "Input Stream 1".getBytes();
byte[] dataSource2 = "Input Stream 2".getBytes();
ByteArrayInputStream in1 = new ByteArrayInputStream(dataSource1);
ByteArrayInputStream in2 = new ByteArrayInputStream(dataSource2);
SequenceInputStream in = new SequenceInputStream(in1, in2);
int length = Math.max(dataSource1.length, dataSource1.length);
byte[] data = new byte[length];
int count;
while ((count = in.read(data)) != -1) {
System.out.println(new String(data, 0, count));
}
in.close();
}
读取Java基本类型和String类型数据
/**
* 字节流: 基本类型和String的写入和读取
*/
@Test
public void testReadPrimitive() {
int dataSourceInt = 1;
String dataSource = "Text Content";
//写入数据
try (DataOutputStream out = new DataOutputStream(new FileOutputStream(TestConstants.WRITE_FILE_NAME))) {
out.writeInt(dataSourceInt);
out.writeUTF(dataSource);
} catch (IOException e) {
e.printStackTrace();
}
//读取数据
try (DataInputStream in = new DataInputStream(new FileInputStream(TestConstants.WRITE_FILE_NAME))) {
int value1 = in.readInt();
String value2 = in.readUTF();
Assert.assertEquals(dataSourceInt, value1);
Assert.assertEquals(dataSource, value2);
} catch (IOException e) {
e.printStackTrace();
}
}
读取/写入对象类型到字节:
/**
* 测试数据类型,需要实现序列化
*/
public class TestData implements Serializable {
private static final long serialVersionUID = 7926567675530245735L;
private String name;
private int age;
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;
}
}
/**
* 字节流: 读取/写入对象
*/
@Test
public void testObjectStream() {
TestData testData = new TestData();
testData.setName("object Stream");
testData.setAge(24);
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(TestConstants.WRITE_FILE_NAME))) {
out.writeObject(testData);
} catch (IOException e) {
e.printStackTrace();
}
TestData object = null;
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(TestConstants.WRITE_FILE_NAME))) {
object = (TestData) in.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Assert.assertEquals(testData.getName(), object.getName());
Assert.assertEquals(testData.getAge(), object.getAge());
}
java.io package
注意:数据源是一个File,Memory数据结构,在下文中称为原始流,数据源是其他底层数据流下文称为包装流
input stream (字节流)
描述:数据流入应用程序(data source -> application),读取的数据是字节
相关类
- FileInputStream : 数据源是文件
- ByteArrayInputStream:数据源是内存中的字节数组
- SequenceInputStream:定义一组输入流,读取完一个流后,自动切换到下一个,直到最后一个流被读取完。每次read操作只会读取一个流中的内容。
- PipedInputStream:定义一个管道,实现线程之间的数据传递,接收数据端
- ObjectInputStream:读取使用ObejctOutputStream写入的对象
- FilterInputStream:包装InputStream,增加额外功能的类继承自该类,包装器模式
- BufferedInputStream:增强输入流功能,带有一个缓存区
- DataInputStream:读取DataOutputStream写入的基本类型值和String
- PushbackInputStream:支持读取的数据推回流中,稍后在读取
- StringBufferInputStream and LineNumberStream:已废弃
output stream(字节流)
描述:数据流出应用程序(application ->data source),写的数据是字节
类介绍
- FileOutputStream:往文件中写入数据,支持追加写入
- ByteArrayInputStream:往字节数据中写入数据,可使用toByteArray(),toString()取回数据,也可以写入其他输出流WriterTo(out)
- PipedOutputStream :定一个管道,实现线程之间的数据传递,发送数据
- FilterOutputStream :包装其他输出流
- ObjectOutputStream : 直接写入对象类型,可使用ObjectInputStream读取
- BufferedOutputStream :带缓冲区的写入
- DataOutputStream :写入java基本类型数据
Reader(字符流)
描述:数据流入应用程序(data source -> application),读取的数据是字符,字节到字符的转换使用StreamDecoder类,该类也继承自Reader
类介绍
- FileReader:输入源是磁盘文件,继承自InputStreamReader,依赖FileInputStream
- BufferedReader:带缓冲区,读取原始流
- CharArrayReader : 输入源是内存中的字符数组
- StringReader : 输入源是内存中的字符串
- InputStreamReader: 字节流和字符流的桥,使用StreamDecoder完成字节流到字符流的转换
- PipedReader:管道,线程之间的通信,数据接收端
- FilterReader: 包装底层数据流
- PushbackReader:支持推回功能
- LineNumberReader:增加当前行号功能
Writer(字符流)
描述:数据流出应用程序(application -> data source),写入的数据是字符(串),字符到字节的转换使用StreamEncoder类,该类也继承自Writer
类介绍
- FileWriter:输出源是磁盘文件,继承自OutputStreamWriter,依赖FileOutputStream
- BufferedWriter:带缓冲区,写入原始流
- CharArrayWriter : 输出源是内存中的字符数组
- StringWriter : 输出源是内存中的字符串
- OutputStreamWriter: 字符流和字节流的桥接,使用StreamEncoder完成字符流到字节流的转换
- PipedWriter:管道,线程之间的通信,数据发送端