本文参考:时光孤岛
概念
流是一个抽象的概念,它代表一串数据的集合,当Java程序需要从数据源读取数据时,就需要开启一个到数据源的流。同样,当程序需要输出数据到目的地时,也需要开启一个流。流的创建是为了更方便地处理数据的输入和输出。
分类
处理的数据类型不同:字符流和字节流。
流的方向不同:输入流和输出流。
字符流与字节流的区别
字符流的由来: 因为数据编码的不同,而有了对字符进行高效操作的流对象。本质其实就是基于字节流读取时,去查了指定的码表,可以看作是字节流的特殊升级。
(1)读写单位不同:字节流以字节(8bit)为单位,字符流以字符为单位,根据码表映射字符,一次可能读多个字节。
(2)处理对象不同:字节流能处理所有类型的数据(如图片、avi等),而字符流只能处理字符类型的数据。
(3)字节流在操作的时候本身是不会用到缓冲区的,是文件本身的直接操作的;而字符流在操作的时候下后是会用到缓冲区的,是通过缓冲区来操作文件。
结论:优先选用字节流。首先因为硬盘上的所有文件都是以字节的形式进行传输或者保存的,包括图片等内容。但是字符只是在内存中才会形成的,所以在开发中,字节流使用广泛。
输入流与输出流的区别
输入流只能进行读操作,输出流只能进行写操作。从文件读取数据输入Java程序是输入流,从Java程序输出数据写入到文件是输出流。
IO流类图结构
InputStream
常用类 | 含义 |
---|---|
InputStream | InputStream 抽象类是字节输入流所有类的超类,它以字节为单位从数据源中读取数据 |
FileInputStream | 以字节为单位从文件中读取数据 |
FilterInputStream | 封装其它的输入流,并为它们提供额外的功能 |
BufferedInputStream | 给输入流提供缓冲功能,提高读取效率 |
DataInputStream | 装饰其它输入流,它允许应用程序以与机器无关方式从底层输入流中读取 Java基本数据类型 |
PushBackInputStream | 可以把读取进来的某些数据重新回退到输入流的缓冲区之中 |
ObjectInputStream | 从输入流读入对象,读取对象信息 |
PipedInputStream | 与其它线程共用的管道中读取数据 |
SequenceInputStream | 合并流 |
StringBufferInputStream | 以字节为单位从字符串中读取数据 |
ByteArrayInputStream | 在内存中创建一个字节数组缓冲区,从输入流读取的数据保存在该字节数组缓冲区中。 |
常用方法 | 含义 |
---|---|
public abstract int read() throws IOExecption | 该方法用于从输入流中读取数据的下一个字节,返回读到的字节值,若遇到流的末尾,返回-1。 |
public int read(byte[] b) throws IOExecption | 该方法用于从输入流中读取b.length个字节的数据,并将数据存储到缓冲区数组b中,返回的是实际读到的字节数。 |
public int read(byte[] b,int off,int len) throws IOExecption | 该方法用于从输入流中读取len个字节的数据,并从数组b的off位置开始写入到这个数组中。 |
public void close() throws IOExecption | 关闭此输入流,并释放与此输入流相关联的所有系统资源。 |
FileInputStream
简介
FileInputStream
是从文件中读取数据,所以我们让它从txt文件中读取数据,然后打印。
public class FileInputStreamTest {
public static void main(String[] args) throws Exception {
//1.得到数据文件
String filePath = "E:\\ExampleTest\\TestText.txt";
File file = new File(filePath);
//2.创建输入流
InputStream inputStream = new FileInputStream(file);
//3.循环读取文件内容,输入流中将最多bytes.length个字节的数据读入一个byte数组中,
//返回的是读取到的字节数。当文件读取到结尾时返回 -1,循环结束。
byte[] bytes = new byte[1024];
int length = 0;
while ((length = inputStream.read(bytes)) != -1) {
System.out.println("Length is " + length);
System.out.println(new String(bytes, 0, length));
}
//4.关闭输入流
inputStream.close();
}
}
如果文件有两个字节内容,则读取时将两个字节读取到bytes中,length等于2,然后再读取一次,因为到达文件末尾,则返回-1,while循环退出,关闭输入流,程序结束。
FilterInputStream
简介
FilterInputStream 只是简单地重写那些将所有请求传递给所包含输入流的 InputStream 的所有方法。FilterInputStream 的子类可进一步重写这些方法中的一些方法,并且还可以提供一些额外的方法和字段。从FilterInputStream类注释可以看出,需要关注的是FilterInputStream的子类,BufferedInputStream,DataInputStream,PushBackInputStream。
FliterInputStream不能直接创建,它的构造方法是protected
。
BufferedInputStream
简介
BufferedInputStream是带缓冲区的输入流,默认缓冲区大小是8M,能够减少访问磁盘的次数,提高文件读取性能。
我们从InputStream中读取数据时,一般都会是磁盘io,或者网络io,这都是耗费大量时间的操作,比如我们现在从文件读取20个字节,过一会又从文件读取20个字节,这就是两次io,浪费资源,有了BufferedInputStream,就解决这个两次io的问题,输入流在read时,干脆多读一部分数据进来,放在内存里,等你每次操作流的时候,读取的数据直接从内存中就可以拿到,这就减少了io次数,提升效率。
public class BufferedInputStreamTest{
public static void main(String[] args) throws Exception{
//1.得到数据文件
String filePath = "E:\\ExampleTest\\TestText.txt";
File file = new File(filePath);
//2.创建缓冲输入流
BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(file));
//3.循环读取文件内容,输入流中将最多bytes.length个字节的数据读入一个byte数组中,
//返回的是读取到的字节数。当文件读取到结尾时返回 -1,循环结束。
byte[] bytes = new byte[1024];
int length = 0;
while ((length = inputStream.read(bytes)) != -1) {
System.out.println("Length is " + length);
System.out.println(new String(bytes, 0, length));
}
//4.关闭输入流
inputStream.close();
}
}
DataInputStream
与DataOutputStream
简介
DataInputStream 是用来装饰其它输入流,DataOutputStream是用来修饰其他输出流,它允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型。应用程序可以使用DataOutputStream(数据输出流)写入数据然后由DataInputStream(数据输入流)读取数据。
public class DataInputStreamTest {
public static void main(String[] args) throws Exception{
//1.得到数据文件
String filePath = "E:\\ExampleTest\\TestText.txt";
File file = new File(filePath);
file.createNewFile();
//2.创建输出流,写入Java基本类型数据
DataOutputStream bufferedOutputStream = new DataOutputStream(new FileOutputStream(file));
bufferedOutputStream.writeInt(1);
bufferedOutputStream.writeDouble(3.14);
bufferedOutputStream.writeBoolean(true);
bufferedOutputStream.writeUTF("Focus");
bufferedOutputStream.flush();
bufferedOutputStream.close();
//3.创建输入流,读取Java基本类型数据
DataInputStream dataInputStream = new DataInputStream(new FileInputStream(file));
System.out.println(dataInputStream.readInt());
System.out.println(dataInputStream.readDouble());
System.out.println(dataInputStream.readBoolean());
System.out.println(dataInputStream.readUTF());
dataInputStream.close();
}
}
DataOutputStream和DataInputStream是相互配合使用,我们可以给文件中写入Java基本数据类型,然后再读取出来,也就是允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型,但是在读取时要保证顺序一致性,否则会导致java.io.EOFException。一个int是32位,4个字节,我们是用字节流的形式保存的,在看txt文件时,可能会乱码,但英文字符串会正常显示。
PushBackInputStream
简介
PushBackInputStream回退流,可以把读取的数据放回去再读取一遍。
public void unread(int b) throws IOException
是回退流中的回退方法。
比如上面的文本,我想把.
重新读一遍。
public class PushBackInputStreamTest {
public static void main(String[] args) throws Exception{
//1.得到数据文件
String filePath = "E:\\ExampleTest\\TestText.txt";
File file = new File(filePath);
//2.创建缓冲输入流
PushbackInputStream inputStream = new PushbackInputStream(new FileInputStream(file));
int temp = 0;
while ((temp = inputStream.read()) != -1) {
if (temp == '.') {
inputStream.unread((char)temp);
System.out.println("退回" + (char)temp);
temp = inputStream.read();
System.out.println((char)temp);
}else {
System.out.println((char)temp);
}
}
inputStream.close();
}
}
产生结果如下图所示:
ObjectInputStream
与ObjectOutputStream
简介
ObjectInputStream可以从输入流中读取Java对象,它和DataInputStream的区别是DataInputStream只能读取基本类型,而ObjectInutStream可以读取基本类型和对象,所读取的对象必须实例化。
这有一个实例化类Man
,我们先将它的对象写入文件中,然后读取出来。
public class Man implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private int age;
public Man(String name, int age) {
this.name = name;
this.age = 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;
}
}
public class ObjectInputStreamTest {
public static void main(String[] args) throws Exception{
//1.得到数据文件
String filePath = "E:\\ExampleTest\\TestText.txt";
File file = new File(filePath);
file.createNewFile();
//2.创建输出流,写入Java类
Man man = new Man("LiYunLong", 39);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(file));
objectOutputStream.writeObject(man);
objectOutputStream.writeUTF("End");
objectOutputStream.close();
//3.创建输入流,读取java类
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(file));
Man secondMan = (Man) objectInputStream.readObject();
System.out.println(secondMan.getName());
System.out.println(secondMan.getAge());
System.out.println(objectInputStream.readUTF());
objectInputStream.close();
}
}
文本内容如下所示:
PipedInputStream
与PipedOutputStream
简介
PipedInputStream可以用于两线程之间通信,在线程之间发送消息。
public void connect(PipedOutputStream src)
,public void connect(PipedInputStream snk)
分别是PipedInputStream与PipedOutputStream的方法,两者的实质没有区别,就是将两个线程之间的管道连接起来,实例代码如下:
public class PipedInputStreamTest {
public static void main(String[] args) throws IOException {
SenderThread senderThread = new SenderThread();
ReceiverThread receiverThread = new ReceiverThread();
//将两个线程连接起来
receiverThread.getPipedInputStream().connect(senderThread.getPipedOutputStream());
//与上面代码实质一样
//senderThread.getPipedOutputStream().connect(receiverThread.getPipedInputStream());
senderThread.run();
receiverThread.run();
}
}
//发送线程
public class SenderThread extends Thread {
private PipedOutputStream pipedOutputStream = new PipedOutputStream();
public PipedOutputStream getPipedOutputStream() {
return pipedOutputStream;
}
@Override
public void run() {
try {
//发送一条消息
pipedOutputStream.write("A message for you".getBytes());
pipedOutputStream.flush();
pipedOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//接受线程
public class ReceiverThread extends Thread{
private PipedInputStream pipedInputStream = new PipedInputStream();
public PipedInputStream getPipedInputStream() {
return pipedInputStream;
}
@Override
public void run() {
byte[] bytes = new byte[1024];
try {
//接受一条消息
pipedInputStream.read(bytes);
pipedInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(new String(bytes));
}
}
SequenceInputStream
简介
合并流,可以多个流合并成一个流,从而操作起来更加方便。它从输入流的有序集合开始,并从第一个输入流开始读取,直到到达文件末尾;接着从第二个输入流读取,依次类推,直到到达包含的最后一个输入流的文件末尾为止。
public class SequenceInputStreamTest{
public static void main(String[] args) throws Exception {
//1.创建第一个输入流
String filePathOne = "E:\\ExampleTest\\One.txt";
File fileOne = new File(filePathOne);
FileInputStream filtInputStreamOne = new FileInputStream(fileOne);
//2.创建第二个输入流
String filePathTwo = "E:\\ExampleTest\\Two.txt";
File fileTwo = new File(filePathTwo);
FileInputStream fileInputStreamTwo = new FileInputStream(fileTwo);
//3.将连个输入流合并在,打印出结果
SequenceInputStream sequenceInputStream = new SequenceInputStream(filtInputStreamOne, fileInputStreamTwo);
byte[] bytes = new byte[1024];
int length = 0;
while ((length = sequenceInputStream.read(bytes)) != -1) {
System.out.println(new String(bytes, 0, length));
}
filtInputStreamOne.close();
fileInputStreamTwo.close();
sequenceInputStream.close();
}
}
StringBufferInputStream
简介
StringBufferInputStream这个类已经打上了@Deprecated注解,所以不应该用这个类写代码,StringBufferInputStream类的本意是把字符串转换为字节流,然后进行读操作,但是这个类实现中仅仅使用了字符编码的低8位,不能把字符串中的所有字符(比如中文字符)正确转换为字节,因此这个类已经被废弃,取而代之的是StringReader类。
ByteArrayInputStream
简介
字节数组输入流在内存中创建一个字节数组缓冲区,从输入流读取的数据保存在该字节数组缓冲区中。
public class ByteArrayInputStreamTest {
public static void main(String[] args) throws Exception{
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream("Hello".getBytes());
byte[] bytes = new byte[1024];
byteArrayInputStream.read(bytes);
System.out.println(new String(bytes));
byteArrayInputStream.close();
}
}
至此,InputStream下的大部分类已经做了简短的概述,类很多,但可大体分成两种,直接继承InputStream的,和继承FilterInputStream的,前者主要是数据的来源,比如FileInputStream是文件,ObjectInputStream是类,PipedInputStream是线程,SequenceInputStream是输入流,StringBufferInputStream是String,ByteArrayInputStream是byte数组,而后者主要是对数据的操作,或者说方法,BufferedInputStream是缓冲,DataInputStream是实例化数据,PushBackInputStram是回退。我们可以用方法装饰数据来源,做一些优化,比如对文件输入流做缓冲,提高性能。所以整个Java IO类是装饰者模式。
OutputStream
常用类 | 含义 |
---|---|
OutputStream | OutputStream 抽象类是表示字节输出流的所有类的超类,它以字节为单位将数据写入数据源 |
FileOutputStream | 以字节为单位将数据写入到文件 |
FilterOutputStream | 装饰其它输出流,并为它们提供额外的功能 |
BufferedOutputStrean | 装饰其它输出流,给输出流提供缓冲功能,提高写入效率 |
DataOutputStrean | 装饰其它输出流,它允许应用程序以与机器无关方式向底层写入Java基本数据类型 |
PrintStream | 装饰其它输出流,使它们能够方便地打印各种数据值表示形式 |
ObjectOutputStream | 将对象信息写入到输出流 |
PipedOutputStream | 与其它线程共用的管道中写入数据 |
ByteArrayOutputStream | 在内存中创建一个字节数组缓冲区,所有发送到输出流的数据保存在该字节数组缓冲区中 |
常用方法 | 含义 |
---|---|
public abstract void write(int b) throws IOExecption | 该方法用于将指定的字节写入到输出流 |
public int write(byte[] b) throws IOExecption | 该方法用于将b.length个字节从指定的byte数组写入到输出流。 |
public int write(byte[] b,int off,int len) throws IOExecption | 该方法用于将len个字节的数据,并从数组b的off位置开始写入到输出流。 |
public void close() throws IOExecption | 关闭此输出流,并释放与此输出流相关联的所有系统资源。 |
FileOutputStream
简介
FileOutputStream是向文件中写入数据,所以我们给txt文件中写一些数据,然后看一下。
public class FileOutputStreamTest {
public static void main(String[] args) throws IOException {
//1.创建写入文件
File file = new File("E:\\ExampleTest\\TestText.txt");
file.createNewFile();
//2.创建输出流,然后写入数据
OutputStream outputStream = new FileOutputStream(file);
outputStream.write("Hello World".getBytes());
outputStream.flush();
outputStream.close();
}
}
在文件中的结果如下图所示:
FilterOutputStream
简介
FilterOutputStream类似于FilterInputStream,是一个抽象类,它的子类BufferedOutputStream,DataOutputStream,PrintStream可以修饰其它输出类,添加一些额外的功能。
BufferedOutputStream
简介
BufferedOutputStream类似于BufferInputStream,是个带缓冲区的输出流,可以用来修饰其它输出流,从而提高写入性能。
public class BufferedOutputStreamTest {
public static void main(String[] args) throws Exception {
//1.创建准备写入的文件
File file = new File("E:\\ExampleTest\\TestText.txt");
file.createNewFile();
//2.创建输出流,写入数据
OutputStream outputStream = new BufferedOutputStream(new FileOutputStream(file));
outputStream.write("BufferedOutputStreamTest".getBytes());
outputStream.flush();
outputStream.close();
}
}
PrintStream
简介
PrintStream是FilterOutputStram的子类,可以用来修饰其它输出流。PrintStream还提供了自动flush功能,往PrintStream写入的数据会立刻调用flush()函数。
public class PrintStreamTest {
public static void main(String[] args) throws Exception{
//1.创建准备写入的文件
File file = new File("E:\\ExampleTest\\TestText.txt");
file.createNewFile();
//2.创建输出流,写入数据
PrintStream printStream = new PrintStream(new FileOutputStream(file));
printStream.println("Hello World");
printStream.println("你好世界");
printStream.println(true);
printStream.close();
}
}
可以看到的是,不管是什么数据类型,PrintStream都会将其转变为字符串,然后输出。我们经常使用的System.out.println();中的out就是一个PrintStream对象。
ByteArrayOutputStream
简介
在创建ByteArrayOutputStream实例时,会在程序内部创建一个byte型数组的缓冲区,缓冲区会随着数据的不断写入而自动增长,可以使用toByteArray()或者toString()获取数据,也可以使用ByteArrayInputStream来获取。
public class ByteArrayOutputStreamTest {
public static void main(String[] args) throws Exception{
//1.创建准备写入的文件
File file = new File("E:\\ExampleTest\\TestText.txt");
file.createNewFile();
//2.创建输出流,写入数据
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byteArrayOutputStream.write(0);
byteArrayOutputStream.write("ok".getBytes());
byteArrayOutputStream.write(1);
//3.使用输入流读取数据
byte[] bytes = byteArrayOutputStream.toByteArray();
ByteArrayInputStream arrayInputStream = new ByteArrayInputStream(bytes);
int length = 0;
while ((length = arrayInputStream.read()) != -1) {
System.out.println(length);
}
byteArrayOutputStream.close();
arrayInputStream.close();
}
}