Java基础:流Stream详解
作者:CodingALife
写在前面
从Java 1.0开始,引入java.io包;到Java 1.4再扩展了java.nio包;再到java 1.7又添加了新的流类,使得Java的流机制变得十分强大。
一、"流"概念
James Gosling所著《Java程序设计》中描述Java I/O流模式图如下。Program是中间环节,用于对Source进行处理,然后输出到Dest处。
Java中的"流"就是指把数据从一个对象移动到另一个对象的流动模式的抽象。其实Java的流模式用水流或者电流模型来解释是很容易理解的。
James Gosling的Java流模式图与水流模式图概念映射。数据源(data source)即水库,数据目的地(data destination)就是脸盆,数据(data)就是水,流(stream)实例化就是在管子中流动的水流。
输入流(input stream)就是用水泵从水库中抽出来要到水管中的水,输出流(output stream)经过水龙头将要达到脸盆中的水,计算机内存(memory)就是上图中的水流管道,关闭输入流(close input stream)就是关闭水泵开关,关闭输出流(close output stream)就是关闭关闭水龙头开关。
更进一步说,具体的水库和脸盆分别对应于Java中输入流对象和输出流对象。水流可以分成一粒一粒的水分子,这些水分子映射成计算机二进制位(bit)0/1,其组成的水滴映射成计算机字节流(字节是计算机储存信息的基本单位)。字节流和字符流在物理层面的实现都是比特流,二进制数据流可以认为是字节流,而字符流是遵循unicode编码规则的字节流。因此计算机中的"流"概念实际上就是指字节数据(bytes data)从源对象对按顺序流向目标对象的一种流动形式。
二、流的分类
1、按流的方向分为:输入流、输出流
判断当前流是输入流还是输出流的依据是二进制数据相对于计算机内存的位置,输入流是输入计算机内存是二进制数据,输出流是从计算机内存输出的二进制数据。而计算机程序在运行期间会储存在到计算机内存中,因此总的来说就是数据的来源、取向是相对程序而言的。比如键盘键入数据属于输入流,内存数据持久化到磁盘中属于输出流。
说明:本图片取自互联网,纠正补充为流的来源有网络连接、内存块、磁盘(文件)、键盘等;流的去向也基本是这些。
2、按流处理数据的单位分为:字节流、字符流
从物理层面来看,流中数据都是二进制比特流。而计算机中储存信息的基本单位是字节(Byte)。因此,可以认为计算机中信息传输在底层是靠字节流来实现的。字符流只是通过不同的字符编码方式,对字节流的封装,即字符流的实现还是得依靠字节流。
3、按流的功能分为:节点流(又称低级流)、过滤流(又称高级流、处理流、包装流)
节点流(Node Stream)是流管道两端直接连接data source和data destination上的,即为取放数据的真实载体,在流通道本身不对数据做任何加工,因而也被称为低级流。
节点流从一个特定的数据源读写数据。即节点流是直接操作文件,网络等的流,例如FileInputStream和FileOutputStream,他们直接从文件中读取或往文件中写入字节流。
“连接”在已存在的流(节点流或处理流)之上通过对数据的处理为程序提供更为强大的读写功能。过滤流是使用一个已经存在的输入流或输出流连接创建的,过滤流就是对节点流进行一系列的包装。例如BufferedInputStream和BufferedOutputStream,使用已经存在的节点流来构造,提供带缓冲的读写,提高了读写的效率,以及DataInputStream和DataOutputStream,使用已经存在的节点流来构造,提供了读写Java中的基本数据类型的功能。他们都属于过滤流。
public static void main(String[] args) throws IOException { // 节点流FileOutputStream直接以A.txt作为数据源操作 FileOutputStream fileOutputStream = new FileOutputStream("A.txt"); // 过滤流BufferedOutputStream进一步装饰节点流,提供缓冲写 BufferedOutputStream bufferedOutputStream = new BufferedOutputStream( fileOutputStream); // 过滤流DataOutputStream进一步装饰过滤流,使其提供基本数据类型的写 DataOutputStream out = new DataOutputStream(bufferedOutputStream); out.writeInt(3); out.writeBoolean(true); out.flush(); out.close(); // 此处输入节点流,过滤流正好跟上边输出对应,读者可举一反三 DataInputStream in = new DataInputStream(new BufferedInputStream( new FileInputStream("A.txt"))); System.out.println(in.readInt()); System.out.println(in.readBoolean()); in.close(); }
理解:
FileOutputStream
是根据二进制010101一个一个字节处理BufferedOutputStream
是对字节封装成buffered,以缓冲区处理DataOutputStream
是以字符串形式,类似(“hello”)处理。
4、字节流与字符流区别
- 字节流默认是不带缓冲区的,而字符流默认是带缓冲区的。
- 字节流是底层数据流,是数据有意义的最小单位。字符流是字节流的包装,底层实现是字节流。
- 字节流读取的时候,读到一个字节就返回一个字节;字符流使用了字节流读到一个或多个字节(中文对应的字节数是两个,在UTF-8码表中是3个字节)时。先去查指定的编码表,将查到的字符返回。
- 字节流可以处理所有类型数据,如:图片,MP3,AVI视频文件,而字符流只能处理字符数据。只要是处理纯文本数据,就要优先考虑使用字符流,除此之外都用字节流。文本文件可以用字节流来实现,当然使用字符流速度会更快。
三、流的方法
1、字节流
字节输入流类:FileInputStream、BufferedInputStream和DataInputStream
FileInputStream:此类用于从本地文件系统中读取文件内容。
构造方法:
FileInputStream(File file):
打开一个到实际文件的连接来创建一个FileInputStream,该文件通过文件系统中的File对象file指定。FileInputStream(String name):
打开一个到实际文件的连接来创建一个FileInputStream,该文件通过文件系统中的路径名name指定。
常用方法:
int available():
返回下一次对此输入流调用的方法不受阻塞地从此输入流读取(或跳过)的估计剩余字节数。void close():
关闭此文件输入流并释放与该流关联的所有系统资源。
BufferedInputStream:此类本身带有一个缓冲区,在读取数据时,先放到缓冲区中,可以减少对数据源的访问,提高运行的效率。
构造方法:
BufferedInputStream(InputStream in):
创建一个BufferedInputStream并保存其参数,即输入流in,以便将来使用。BufferedInputStream(InputStream in,int size):
创建一个具有指定缓冲区大小的BufferedInputStream并保存其参数,即输入流in,以便将来使用。
常用方法:
int available():
返回下一次对此输入流调用的方法不受阻塞地从此输入流读取(或跳过)的估计剩余字节数。void close():
关闭此输入流并释放与该流关联的所有系统资源。int read():
从输入流中读取数据的下一个字节。int read(byte[] b,int off,int len):
从此字节输入流中给定偏移量处开始将各字节读取到指定的byte数组中。
DataInputStream:该类提供一些基于多字节读取方法,从而可以读取基本数据类型的数据。
构造方法:
DataInputStream(InputStream in):
使用指定的底层InputStream创建一个DataInputStream。
常用方法:
int read(byte[] b):
从包含的输入流中读取一定数量的字节,并将它们存储到缓冲区数组b中。int read(byte[] b,int off,int len):
从包含的输入流中将最多len个字节读入一个byte数组中。
字节输出流类:FileOutputStream、BufferedOutputStream和DataOutputStream
FileOutputStream:此类用于从本地文件系统的文件中写入数据。
构造方法:
FileOutputStream(File file):
创建一个向指定File对象表示的文件中写入数据的文件输出流。FileOutputStream(String name):创建一个向具有指定名称的文件中写入数据的输出文件流。
常用方法:
void close():
关闭此文件输出流并释放与此流有关的所有系统资源。FileDescriptor getFD():
返回与此流有关的文件描述符。void write(byte[] b):
将b.length个字节从指定byte数组写入此文件输出流中。void write(byte[] b,int off,int len):
将指定byte数组中从偏移量off开始的len个字节写入此文件输出流。void write(int b):
将指定字节写入此文件输出流。
BufferedOutputStream:此类本身带有一个缓冲区,在写入数据时,先放到缓冲区中,实现缓冲的数据流。
构造方法:
BufferedOutputStream(OutputStream out):
创建一个新的缓冲输出流,来将数据写入指定的底层输入流。BufferedOutputStream(OutputStream out,int size):
创建一个新的缓冲输出流,来将具有指定缓冲区大小的数据写入指定的底层输出流。
常用方法:
void flush():
刷新此缓冲的输出流。void write(byte[] b,int off,int len):
将指定byte数组中从偏移量off开始的len个字节写入此缓冲的输出流。void write(int b):
将指定的字节写入此缓冲的输出流。
DataOutputStream(OutputStream out):创建一个新的数据输出流,将数据写入指定基础输出流。
常用方法:
void flush():
清空此数据输出流。int size():
返回计数器written的当前值,即到目前为止写入此数据输出流的字节数。void write(byte[] b,int off,int len):
将指定byte数组中从偏移量off开始的len个字节写入基础输出流。void write(int b):
将指定字节(参数b的八个低位)写入基础输出流。
2、字符流
FileReader:用来读取字符文件的便捷类。此类的构造方法假定默认字符编码和默认字节缓冲区大小都是适当的
构造方法:
FileReader(File file):在给定从中读取数据的File的情况下创建一个新的FileReader。FileReader(String fileName):在给定从中读取数据的文件名的情况下创建一个新的FileReader。
BufferedReader类是Reader类的子类,为Reader对象添加字符缓冲器,为数据输入分配内存存储空间,存取数据更为有效。
构造方法:
BufferedReader(Reader in):
创建一个使用默认大小输入缓冲区的缓冲字符输入流。BufferedReader(Reader in,int sz):
创建一个使用指定大小输入缓冲区的缓冲字符输入流。
操作方法:
void close():
关闭该流并释放与之关联的所有资源。void mark(int readAheadLimit):
标记流中的当前为止。boolean markSupported();
判断此流是否支持mark()操作。int read():
读取单个字符。int read(char[] cbuf,int off,int len):
将字符读入数组的某一部分。String readLine():
读取一个文本行。boolean ready():
判断此流是否已准备好被读取。void reset():
将流重置到最新的标记。long skip(long n):
跳过字符。
FileWriter:用来写入字符文件的便捷类,可用于写入字符流。
构造方法:
FileWriter(File file):
根据给定的File对象构造一个FileWriter对象。
FileWriter(String filename):
根据给定的文件名构造一个FileWriter对象。
BufferedWriter: 将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。
缓冲流的目的:
操作流的时候,习惯定义一个byte/char数组。
int read(): 每次都从磁盘文件中读取一个字节。 直接操作磁盘文件性能极低。
解决方案:定义一个数组作为缓冲区。
byte[] buffer = new byte[1024];该数组其实就是一个缓冲区。
一次性从磁盘文件中读取1024个字节。如此以来,操作磁盘文件的次数少了,性能得以提升。提供的默认缓存区大小是8192(1024*8),我们一般不用修改大小
public class BufferStreamDemo { public static void main(String[] args) throws Exception { File file = new File("file/aaa.txt"); BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file)); out.write("中国".getBytes()); out.close(); BufferedInputStream in = new BufferedInputStream(new FileInputStream(file)); byte[] buffer = new byte[1024]; int len = -1; while((len = in.read(buffer)) != -1){ System.out.println(new String(buffer, 0, len)); } in.close(); } } public class BufferCharacterDemo { public static void main(String[] args) throws Exception { File file = new File("file/aaa.txt"); BufferedWriter in = new BufferedWriter(new FileWriter(file,true)); in.newLine();//用来换行等同于‘\n' in.write("美国"); in.newLine(); in.write("马来西亚"); in.close(); BufferedReader out = new BufferedReader(new FileReader(file)); String line = null; //按行读取 while((line = out.readLine()) != null){ System.out.println(line); } out.close(); } }
4、流相关设计模式
处理流/包装流(相对于节点流更高级) 装饰设计模式/包装模式:
- 隐藏了底层的节点流的差异,并对外提供了更方便的输入/输出功能,让我们只关心高级流的操作.
- 使用处理流包装了节点流,程序直接操作处理流,让节点流与底层的设备做IO操作.
- 只需要关闭处理流即可.
参考:https://blog.csdn.net/dreamzuora/article/details/79691702
总结
本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注脚本之家的更多内容!