1、IO简介
数据流是一组有序,有起点和终点的字节的数据序列。包括输入流和输出流。
流序列中的数据既可以是未经加工的原始二进制数据,也可以是经一定编码处理后符合某种格式规定的特定数据。因此Java中的流分为两种:
- 字节流:数据流中最小的数据单元是字节
- 字符流:数据流中最小的数据单元是字符,
Java中的字符是Unicode编码,一个字符占用两个字节。
Java.io包中最重要的就是5个类和一个接口。
5个类指的是File、OutputStream、InputStream、Writer、Reader;
一个接口指的是Serializable。掌握了这些就掌握了Java I/O的精髓了。
2、Java I/O主要包括如下3层次:
- 流式部分——最主要的部分。如:OutputStream、InputStream、Writer、Reader等
- 非流式部分——如:File类、RandomAccessFile类和FileDescriptor等类
- 其他——文件读取部分的与安全相关的类,如:SerializablePermission类,以及与本地操作系统相关的文件系统的类,如:FileSystem类和Win32FileSystem类和WinNTFileSystem类。
image.png
3、字节流的学习
在具体的学习流之前,我们必须要学的一个设计模式是装饰模式。因为从流的整个发展历史,出现的各种类之间的关
系看,都是沿用了修饰模式,都是一个类的功能可以用来修饰其他类,然后组合成为一个比较复杂的流。比如说:
DataOutputStream out = new DataOutputStream(
new BufferedOutputStream(
new FileOutputStream(
new File(file)));
从上面的代码块中大家不难看出这些类的关系:
为了向文件中写入数据,首先需要创建一个FileOutputStream,然后为了提升访问的效率,所以将它发送给具备缓存功能的BufferedOutput-Stream,而为了实现与机器类型无关的java基本类型数据的输出,所以,我们将缓存的流传递给了DataOutputStream。从上面的关系,我们可以看到,其根本目的都是为outputSteam添加额外的功能。而这种额外功能的添加就是采用了装饰模式来构建的代码。因此,学习流,必须要学好装饰模式。
4、字节流
image.png
字节流的学习过程
为什么要按照一个学习路线来呢?原因是他们的功能决定的。
OutputStream -> FileOutputStream/FilterOutputStream ->DataOutputStream->bufferedOutputStream
相应的学习InputStream方法就好了。
FilterOutputStream
从学习的角度来,我们应该先掌握FilterOutputStream, 以及FileOutputStream,这两个类是基本的类,从继承关系可以不难发现他们都是对 abstract 类 OutputStream的拓展,是它的子类。然而,伴随着 对 Stream流的功能的拓展,所以就出现了 DataOutputStream,(将java中的基础数据类型写入数据字节输出流中、保存在存储介质中、然后可以用DataOutputStream从存储介质中读取到程序中还原成java基础类型)。这里多提一句、DataOutputStream、FilterOutputStream三个类的关系的这种设计既使用了装饰器模式 避免了类的爆炸式增长。
BufferedOutputStream
为了提升Stream的执行效率,所以出现了bufferedOutputStream。bufferedOutputStream就是将本地添加了一个缓存的数组。在使用bufferedOutputStream之前每次从磁盘读入数据的时候都是需要访问多少byte数据就向磁盘中读多少个byte的数据,而出现bufferedOutputSteam之后,策略就改了,会先读取整个缓存空间相应大小的数据,这样就是从磁盘读取了一块比较大的数据,然后缓存起来,从而减少了对磁盘的访问的次数以达到提升性能的目的。
另外一方面,我们知道了outputStream(输出流)的发展历史后,我们便可以知道如何使用outpuSteam了,同样
的方法,我们可以运用到inputStream中来,这样对称的解释就出现到了inputStream相关的中来了,于是,我们对
整个字节流就有了全方位的理解,所以这样子我们就不会感觉到流的复杂了。这个时候对于其他的一些字节流的使用
(byteArrayOutputStream/PipeOutputStream/ObjectOutputStream)的学习就自需要在使用的时候看看API即可。
5、字符流
image.png
image.png
1:如果只用FileOutputStream fileOutputStream = new FileOutputStream("d:/text.txt"); 不是也能输出到"d:/text.txt"吗?为什么要用其它两个呢?能起到什么作用呢?
答案: FileOutputStream :是字节流,它一个字节一个字节的向外边送数据 OutputStreamWriter:是字符流,它一个字符一个字符的向外边送数据
2:它们有什么区别么?
答案: 英文字符占一个字节,中文是一个字符,至少占俩字节。 如果用stream,你读出来的英语再倒也罢了,读出来的中文可就是乱码或者一个个“????”。 如果你用WRITER,就不会有乱码了
3:BufferedWriter Buffer是一个缓冲区,为什么要用BUFFER呢?
答案: 如果你直接用stream或者writer,你的硬盘可能就是读一个字符或者一个字节 就去读写 硬盘一次,IO负担巨大。可是你用了Buffer,你的硬盘就是读了一堆数据之后,读写一下硬 盘。这样对你硬盘有好处。
6、字节流与字符流的关系
那么字节输入流和字符输入流之间的关系是怎样的呢?请看下图
image.png
7、字节流与字符流的区别
字节流和字符流使用是非常相似的,那么除了操作代码的不同之外,还有哪些不同呢?
字节流在操作的时候本身是不会用到缓冲区(内存)的,是与文件本身直接操作的,而字符流在操作的时候是使 用到缓冲区的字节流在操作文件时,即使不关闭资源(close方法),文件也能输出,但是如果字符流不使用close方 法的话,则不会输出任何内容,说明字符流用的是缓冲区,并且可以使用flush方法强制进行刷新缓冲区,这时才能在 不close的情况下输出内容
8、那开发中究竟用字节流好还是用字符流好呢?
在所有的硬盘上保存文件或进行传输的时候都是以字节的方法进行的,包括图片也是按字节完成,而字符是只有在内存中才会形成的,所以使用字节的操作是最多的。
如果要java程序实现一个拷贝功能,应该选用字节流进行操作(可能拷贝的是图片),并且采用边读边写的方式(节省内存)
9、RandomAccessFile
image.png
image.png
10、NIO——FileChannel
Channel是对I/O操作的封装。
FileChannel配合着ByteBuffer,将读写的数据缓存到内存中,然后以批量/缓 存的方式read/write,省去了非批量操作时的重复中间操作,操纵大文件时可 以显著提高效率(和Stream以byte数组方式有什么区别?经过测试,效率上几 乎无区别)。
11、字节流与字符流的转换
虽然Java支持字节流和字符流,但有时需要在字节流和字符流两者之间转换。
InputStreamReader和OutputStreamWriter,这两个为类是字节流和字符流之间相互转换的类。
1、InputSreamReader用于将一个字节流中的字节解码成字符:
有两个构造方法
//功能:用默认字符集创建一个InputStreamReader对象
InputStreamReader(InputStream in);
//功能:接收已指定字符集名的字符串,并用该字符创建对象
InputStreamReader(InputStream in,String CharsetName);
//字节流转化为字符流
var input = FileInputStream("aa/aa.txt")
val inputStreamReader = InputStreamReader(input)
2、OutputStream用于将写入的字符编码成字节后写入一个字节流。
同样有两个构造方法:
//功能:用默认字符集创建一个OutputStreamWriter对象;
OutputStreamWriter(OutputStream out);
功能:接收已指定字符集名的字符串,并用该字符集创建OutputStreamWrite对象
OutputStreamWriter(OutputStream out,String CharSetName);
var out = FileOutputStream ("aa/aa.txt")
val outputStreamWriter = OutputStreamWriter(out)
3、为了避免频繁的转换字节流和字符流,对以上两个类进行了封装。
BufferedWriter类封装了OutputStreamWriter类;
BufferedReader类封装了InputStreamReader类;
//封装格式:
BufferedWriter out=new BufferedWriter(new OutputStreamWriter(System.out)); BufferedReader in= new BufferedReader(new InputStreamReader(System.in);
利用下面的语句,可以从控制台读取一行字符串:
BufferedReader in=new BufferedReader(new InputStreamReader(System.in)); String line=in.readLine();












网友评论