JAVA IO 详细介绍
目录
一、什么是IO?
1.1 IO的介绍
IO是指对数据流的输入和输出,也称为IO流。Java所有的I/O机制都是基于数据流进行输入输出,这些数据流表示了字符或者字节数据的流动序列。Java的I/O流提供了读写数据的标准方法。任何Java中表示数据源的对象都会提供以数据流的方式读写它的数据的方法。
1.2 流的介绍
1.2.1 流的特征
IO流的好处是简单易用,缺点是效率较低。
1.2.2 数据流的特征
数据流是一组有序,有起点和终点的字节的数据序列(如图),它包括输入流和输出流。
1.2.3 输入流的特征
输入流是程序从输入流读取数据源。数据源包括外界(键盘、文件、网络…),即是将数据源读入到程序的通信通道。比较通俗的来讲就是外部的文件通过输入流,传入到咱们的程序当中,程序去读取输入流内容(如图)。
1.2.4 输出流的特征
输出流是程序向输出流写入数据。将程序中的数据输出到外界(显示器、打印机、文件、网络…)的通信通道。比较通俗的来讲就是程序中的内容通过输出流,输出给外部(如图)。
二、Java IO类库的框架
2.1 Java IO的类型
通过第一点描述得知,其实Java的IO类库总体来说其实并不复杂。从是读写的的维度看,Java IO可以分为
- 输入流:InputStream和Reader
- 输出流:OutputStream和Writer
从其处理流的类型的维度上看,Java IO又可以分为:
- 字节流:InputStream和OutputStream
- 字符流:Reader和Writer
下面这幅图就清晰的描述了JavaIO的分类:
字节流 | 字符流 | |
---|---|---|
输入流 | InputStream | Reader |
输出流 | OutputStream | Writer |
2.2 Java IO的类库
上面我们介绍了Java IO中的四各类:InputStream、OutputStream、Reader、Writer,其实在我们的实际应用中,我们用到的一般是它们的子类,之所以设计这么多子类,目的就是让每一个类都负责不同的功能,以方便我们开发各种应用。各类用途汇总如下:
- 文件访问
- 网络访问
- 内存缓存访问
- 线程内部通信(管道)
- 缓冲
- 过滤
- 解析
- 读写文本 (Readers / Writers)
- 读写基本类型数据 (long, int etc.)
- 读写对象
下面按I/O类型来总体分类:
- Memory :
(1)从/向内存数组读写数据: CharArrayReader、 CharArrayWriter、ByteArrayInputStream、ByteArrayOutputStream
(2)从/向内存字符串读写数据 StringReader、StringWriter、StringBufferInputStream - Pipe管道:实现管道的输入和输出(进程间通信): PipedReader、PipedWriter、PipedInputStream、PipedOutputStream
- File 文件流:对文件进行读、写操作 :FileReader、FileWriter、FileInputStream、FileOutputStream
- ObjectSerialization 对象输入、输出:ObjectInputStream、ObjectOutputStream
- DataConversion数据流:按基本数据类型读、写(处理的数据是Java的基本类型(如布尔型,字节,整数和浮点数)):DataInputStream、DataOutputStream
- Printing:包含方便的打印方法 :PrintWriter、PrintStream
- Buffering缓冲:在读入或写出时,对数据进行缓存,以减少I/O的次数:BufferedReader、BufferedWriter、BufferedInputStream、BufferedOutputStream
- Filtering 滤流:在数据进行读或写时进行过滤:FilterReader、FilterWriter、FilterInputStream、FilterOutputStream
- Concatenation合并输入:把多个输入流连接成一个输入流 :SequenceInputStream
- Counting计数 :在读入数据时对行记数 :LineNumberReader、LineNumberInputStream
- Peeking Ahead:通过缓存机制,进行预读 :PushbackReader、PushbackInputStream
- Converting between Bytes and Characters:按照一定的编码/解码标准将字节流转换为字符流,或进行反向转换(Stream到Reader,Writer的转换类):InputStreamReader、OutputStreamWriter
下面就以这张图来大体了解一下这些类的继承关系
三、Java IO的基本用法
3.1 字节流InputStream/OutputStream
3.1.1 InputStream抽象类
InputStream 为字节输入流,它本身为一个抽象类,必须依靠其子类实现各种功能。继承自InputStream 的流都是向程序中输入数据的,且数据单位为字节(8bit),InputStream是输入字节数据用的类,所以InputStream类提供了3种重载的read方法.以下是Inputstream类中的常用方法:
- public abstract int read( ):读取一个byte的数据,返回值是高位补0的int类型值。若返回值=-1说明没有读取到任何字节读取工作结束。
- public int read(byte b[ ]):读取b.length个字节的数据放到b数组中。返回值是读取的字节数。该方法实际上是调用下一个方法实现的 。
- public int read(byte b[ ], int off, int len):从输入流中最多读取len个字节的数据,存放到偏移量为off的b数组中。
- public int available( ):返回输入流中可以读取的字节数。注意:若输入阻塞,当前线程将被挂起,如果InputStream对象调用这个方法的话,它只会返回0,这个方法必须由继承InputStream类的子类对象调用才有用。
- public long skip(long n):忽略输入流中的n个字节,返回值是实际忽略的字节数, 跳过一些字节来读取 。
- public int close( ) :我们在使用完后,必须对我们打开的流进行关闭。
主要的子类:
3.1.2 OutputStream抽象类
OutputStream提供了3个write方法来做数据的输出,这个是和InputStream是相对应的。
- public void write(byte b[ ]):将参数b中的字节写到输出流。
- public void write(byte b[ ], int off, int len) :将参数b的从偏移量off开始的len个字节写到输出流。
- public abstract void write(int b) :先将int转换为byte类型,把低字节写入到输出流中。
- public void flush( ) : 将数据缓冲区中数据全部输出,并清空缓冲区。
- public void close( ) : 关闭输出流并释放与流相关的系统资源。
主要的子类:
3.1.3 文件输入流:FileInputStream类
FileInputStream可以使用read()方法一次读入一个字节,并以int类型返回,或者是使用read()方法时读入至一个byte数组,byte数组的元素有多少个,就读入多少个字节。在将整个文件读取完成或写入完毕的过程中,这么一个byte数组通常被当作缓冲区,因为这么一个byte数组通常扮演承接数据的中间角色。
例:用字节流去读文件
public static void main(String[] args) throws Exception { //在E盘中新建一个Test.txt文件,内容填入 “hello China!” File file = new File("E:\\Test.txt"); byte[] byteArray = new byte[(int) file.length()]; //因为媒介对象是文件,所以用到子类是FileInputStream InputStream is = new FileInputStream(file); int size = is.read(byteArray); System.out.println("大小:" + size + ";内容:" + new String(byteArray)); //输出为:大小:14;内容:hello China! is.close(); }
3.1.4 文件输出流:FileOutputStream类
用来处理以文件作为数据输出目的数据流;或者说是从内存区读数据入文件
例:用字节流去写文件
public static void main(String[] args) throws Exception { String hello = "hello China!"; byte[] byteArray = hello.getBytes(); File file = new File("E:/Test.txt"); //因为媒介对象是文件,所以用到子类是FileOutputStream OutputStream os = new FileOutputStream(file); os.write(byteArray); //执行后,观察E盘是否新建了一个Test.txt的文本文件,文件内容为"hello China!" os.close(); }
3.1.5 缓冲输入输出流 BufferedInputStream/ BufferedOutputStream
在第一大点有提到就是流它的读取效率是比较低的,原始的InputStream对数据读取的过程都是一个字节一个字节操作的,而BufferedInputStream在其内部提供了一个buffer,在读数据时,会一次读取一大块数据到buffer中,这样比单字节的操作效率要高的多,特别是进程磁盘IO和对大量数据进行读写的时候。所以BufferedInputStream顾名思义,就是在对流进行写入时提供一个buffer来提高IO效率。
BufferedInputStream: BufferedInputStream比FileInputStream多了一个缓冲区。它提供了一个缓冲数组,每次调用read方法的时候,它首先尝试从缓冲区里读取数据,若读取失败(缓冲区无可读数据),则选择从物理数据源(譬如文件)读取新数据(这里会尝试尽可能读取多的字节)放入到缓冲区中,最后再将缓冲区中的内容部分或全部返回给用户.由于从缓冲区里读取数据远比直接从物理数据源(譬如文件)读取速度快。BufferedInputStream的默认缓冲区大小是8192字节。当每次读取的数据量很小时,FileInputStream每次都是从硬盘读入,而BufferedInputStream大部分是从缓冲区读入。读取内存速度比读取硬盘速度快得多,因此BufferedInputStream效率高.
使用BufferedInputStream十分简单,只要把普通的输入流和BufferedInputStream组合到一起即可。我们把上面的例子改造成用BufferedInputStream进行读文件,请看下面例子:
public static void main(String[] args) throws Exception { //在E盘中新建一个Test.txt文件,内容填入 “hello China!” File file = new File("E:\\Test.txt"); byte[] byteArray = new byte[(int) file.length()]; //可以在构造参数中传入buffer大小 InputStream is = new BufferedInputStream( new FileInputStream(file),2*1024); int size = is.read(byteArray); System.out.println("大小:" + size + ";内容:" + new String(byteArray)); //输出为:大小:14;内容:hello China! is.close(); }
BufferedOutputStream进行写文件,同理,请看下面例子:
public static void main(String[] args) throws Exception { String hello = "hello China!"; byte[] byteArray = hello.getBytes(); File file = new File("E:/Test.txt"); //可以在构造参数中传入buffer大小 OutputStream os = new BufferedOutputStream(new FileOutputStream(file),2*1024); os.write(byteArray); //执行后,观察E盘是否新建了一个Test.txt的文本文件,文件内容为"hello China!" os.close(); }
备注:关于如何设置buffer的大小,我们应根据我们的硬件状况来确定。对于磁盘IO来说,如果硬盘每次读取4KB大小的文件块,那么我们最好设置成这个大小的整数倍。因为磁盘对于顺序读的效率是特别高的,所以如果buffer再设置的大写可能会带来更好的效率,比如设置成44KB或84KB。
还需要注意一点的就是磁盘本身就会有缓存,在这种情况下,BufferedInputStream会一次读取磁盘缓存大小的数据,而不是分多次的去读。所以要想得到一个最优的buffer值,我们必须得知道磁盘每次读的块大小和其缓存大小,然后根据多次试验的结果来得到最佳的buffer大小。
3.2 字符流Writer/Reader
3.2.1 Reader抽象类
Reader是用于读取字符流的抽象类。子类必须实现的方法只有 read(char[], int, int) 和 close()。但是,多数子类将重写此处定义的一些方法,以提供更高的效率和/或其他功能。
主要的子类:
例:用字符流去读文件
public static void main(String[] args) throws Exception{ //在E盘中新建一个Test.txt文件,内容填入 “hello China!” File file = new File("E:\\Test.txt"); //因为媒介对象是文件,所以用到子类是FileReader Reader reader= new FileReader(file); char [] byteArray= new char[(int) file.length()]; int size= reader.read( byteArray); System. out.println("大小:"+size +";内容:" +new String(byteArray)); //输出为:大小:14;内容:hello China! reader.close(); }
3.2.2 Writer抽象类
写入字符流的抽象类。子类必须实现的方法仅有 write(char[], int, int)、flush() 和 close()。但是,多数子类将重写此处定义的一些方法,以提供更高的效率和/或其他功能。
主要的子类:
例:用字符流去写文件
public static void main(String[] args) throws Exception{ String hello= new String("hello China!"); File file= new File("E:\\Test.txt"); //因为是用字符流来读媒介,所以对应的是Writer,又因为媒介对象是文件,所以用到子类是FileWriter Writer os= new FileWriter(file); os.write(hello); //执行后,观察E盘是否新建了一个Test.txt的文本文件,文件内容为"hello China!" os.close(); }
3.2.3 BufferedReader和BufferedWriter
BufferedReader、BufferedWriter 的作用基本和BufferedInputStream、BufferedOutputStream一致,具体用法和原理都差不多 ,只不过一个是面向字符流一个是面向字节流。同样,我们将改造字符流中的例4,给其加上buffer功能,例:
public static void main(String[] args) throws Exception{ String hello= new String( "hello China!"); File file= new File( "E:\\Test.txt"); //因为是用字符流来读媒介,所以对应的是Writer,又因为媒介对象是文件,所以用到子类是FileWriter Writer os= new BufferedWriter(new FileWriter(file)); os.write( hello); //执行后,观察E盘是否新建了一个Test.txt的文本文件,文件内容为"hello China!" os.close(); }
四、如何选择IO流?
4.1 字节流与字符流的区别?
想要选择用哪种IO流,首先,肯定是要先区分字节流与字符流之间的差异,以下是我总结几点:
- 字节流和字符流的用法几乎完成全一样,区别在于字节流和字符流所操作的数据单元不同,字节流操作的单元是数据单元是8位的字节,字符流操作的是数据单元为16位的字符。
- 字节流一般用来处理图像、视频、音频、PPT、Word等类型的文件。字符流一般用于处理纯文本类型的文件,如TXT文件等,但不能处理图像视频等非文本文件。用一句话说就是:字节流可以处理一切文件,而字符流只能处理纯文本文件。
- 字节流本身没有缓冲区,缓冲字节流相对于字节流,效率提升非常高。而字符流本身就带有缓冲区,缓冲字符流相对于字符流效率提升就不是那么大了。
4.2 确定是数据源和数据目的(输入还是输出)
输入:InputStream、Reader
输出:OutputStream、Writer
4.3 明确操作的数据对象是否是纯文本
纯文本:Reader,Writer
非纯文本:InputStream,OutputStream
五、如何关闭流?
5.1 必须关闭的流
当我们新建一个java流对象之后,不仅在内存中创建了一个相应类的实例对象,而且还占用了相应的系统资源,比如:文件句柄、端口、网络连接等。在内存中的实例对象,当没有引用指向的时候,java垃圾收集器会按照相应的策略自动回收,但是却无法对系统资源进行释放。所以,我们需要主动调用close()方法释放java流对象。在java7之后,可以使用try-with-resources语句来释放java流对象,从而避免了try-catch-finally语句的繁琐,尤其是在finally子句中,close()方法也会抛出异常。
关闭流通用写法:
OutputStream out = null;try {out = new FileOutputStream("");// ...操作流代码} catch (Exception e) {e.printStackTrace();} finally {try {if (out != null) {out.close();}} catch (Exception e) {e.printStackTrace();}}
5.2 内存流可以不用关闭
有些stream是不需要关闭的,比如ByteArrayOutputStream、ByteArrayIntputStream, StringWriter,点进ByteArrayOutputStream的close()方法发现,里面实现代码是空的,如图:
为什么会这样呢?因为ByteArrayOutputStream内部实现是一个byte[]数组,也就是基于内存的字节数据访问,并没有占有文件句柄、端口、网络连接,就算不关闭,用完之后垃圾回收器也会回收。即然是操作内存,就要考虑内存大小,如果字节流太大就要考虑内存溢出。
但是,我这边还是建议,只要是流,我们就给他关闭以下,反正不吃亏,万一那天记混了,线上环境报错吃亏的就是咱们了!
参考:
https://guisu.blog.csdn.net/article/details/7418161
https://blog.csdn.net/suifeng3051/article/details/48344587
来源地址:https://blog.csdn.net/weixin_44273248/article/details/128374908