I/O 问题是任何编程语言都没法回避的问题,能够说 I/O 问题是整我的机交互的核心问题,由于 I/O 是机器获取和交换信息的主要渠道。在当今这个数据大爆炸时代,I/O 问题尤为突出,很容易成为一个性能瓶颈。html
I/O ? 或者输入/输出 ? 指的是计算机与外部世界或者一个程序与计算机的其他部分的之间的接口。它对于任何计算机系统都很是关键,于是全部 I/O 的主体其实是内置在操做系统中的。单独的程序通常是让系统为它们完成大部分的工做。java
I
:就是从硬盘将内容读取到内存中O
:就是从内存将内容读取到硬盘中其中有些状况下
I/O
是没有和硬盘进行交互的,例如管道流,涉及到两个线程之间的通讯。管道自己是一个分配在内存中的循环缓冲区,只能被链接它的两个线程使用。程序员
Java中的I/O操做类在包java.io
下面,大概将近有80多个类,可是这些类能够分为三组sql
InputStream
和OutputStream
Writer
和 Reader
File
而后在各个接口下还有其各自的包装类,其运用到了装饰模式,为其增长一些功能,而Java的I/O复杂也在这,不一样的装饰模式建立类的代码也不一样。编程
InputStream
的做用是用来表示那些从不一样数据源产生输入的类,这些数据源包括数组
Internet
中的Socket
链接InputStream
的类图,OutputStream
类图和这个相似缓存
类 | 功能 | 构造器参数 | 如何使用 |
---|---|---|---|
ByteArrayInputStream |
容许将内存的缓冲区当作InputStream 使用 |
缓冲区,字节将其从中取出 | 做为数据源:将其与FilterInputStream 对象相连以提供有用的接口 |
StringBufferInputStream |
将String转换成InputStream |
字符串,底层实现实际使用StringBuffer |
做为数据源:将其与FilterInputStream 对象相连提供有用接口 |
FileInputStream |
用于从文件中读取信息 | 字符串,表示文件名,文件或者FileDescriptor 对象 |
做为一种数据源,将其与FilterInputStream 对象相连提供有用接口 |
PipedInputStream |
产生用于写入相关PipedOutputStream 的额数据,实现管道化的概念 |
PipedOutputStream |
做为多线程的数据源:将其与FilterInputStream 对象相连提供有用接口 |
FilterInputStream |
抽象类,做为装饰器的接口,为其余的InputStream 提供有用的功能 |
Java的I/O类库须要多种不一样功能的组合,这正是装饰模式的理由所在。而这也是java的I/O类库中存在Filter(过滤器)类的缘由所在,Filter做为全部装饰类的基类。网络
类 | 功能 |
---|---|
BufferedInputStream |
使用它能够防止每次读取都进行与磁盘的交互,使用缓冲区进行一次性读取固定值的之后再向磁盘中执行写操做,减小了与磁盘的交互次数。提升速度 |
DataInputStream |
容许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型 |
举个简单使用过滤器进行读取一个文件的内容并输出,例子以下:数据结构
public static void main(String[] args) throws IOException { InputStream inputStream = new BufferedInputStream(new FileInputStream("/Users/hupengfei/Downloads/a.sql")); byte[] buffer = new byte[1024]; while ( inputStream.read(buffer)!=-1){ System.out.println(new String(buffer)); } inputStream.close(); }
复制一个文件的例子:多线程
public static void main(String[] args) throws IOException { InputStream inputStream =new BufferedInputStream(new FileInputStream("/Users/hupengfei/Downloads/leijicheng.png")); OutputStream outputStream =new BufferedOutputStream(new FileOutputStream("/Users/hupengfei/Downloads/fuzhi.png")); byte [] buffer = new byte[1024]; while (inputStream.read(buffer)!=-1){ outputStream.write(buffer); } outputStream.flush(); inputStream.close(); outputStream.close(); }
若是要使用
BufferedOutputStream
进行在文件中写入的话,那么在缓冲区写完以后要记得调用flush()
清空缓冲区。强行将缓冲区中的数据写出。不然可能没法写出数据。
不论是磁盘仍是网络传输,最小的存储单元都是字节,而不是字符,因此 I/O 操做的都是字节而不是字符,可是为啥有操做字符的 I/O 接口呢?这是由于咱们的程序中一般操做的数据都是以字符形式,为了操做方便固然要提供一个直接写字符的 I/O 接口。
仍是老规矩,咱们先来看一下关于Reader
的类图,对应的字节流是InputStream
其中的InputStreamReader
是能够将InputStream
转换为Reader
即将字节翻译为字符。其中为何要设计Reader
和Writer
,主要是为了国际化,以前的字节流仅仅支持8位的字节流,不能很好的处理16位的Unicode字符,因为Unicode用于字符国际化,因此添加了Reader
和Writer
是为了在全部的I/O操做中都支持Unicode。
在某些场合,面向字节流InputStream
和OutputStream
才是正确的解决方案,特别是在java.util.zip
类库就是面向字节流而不是面向字符的。所以,最明智的作法就是尽可能优先使用Reader
和Writer
,一旦程序没法编译,那么咱们就会发现本身不得不使用面向字节类库。
仍是写一个相关的读取文件的简单例子
public static void main(String[] args) throws IOException { BufferedReader bufferedReader = new BufferedReader(new FileReader("/Users/hupengfei/Downloads/a.sql")); String date; StringBuilder stringBuilder = new StringBuilder(); while ((date = bufferedReader.readLine()) != null){ stringBuilder.append(date +"\n"); } bufferedReader.close(); System.out.println(stringBuilder.toString()); }
调用
readLine()
方法时要添加换行符,由于readLine()
自动将换行符给删除了
在JDK1.4
中添加了NIO类,咱们也能够称之为新I/O。NIO 的建立目的是为了让 Java 程序员能够实现高速 I/O 而无需编写自定义的本机代码。NIO 将最耗时的 I/O 操做(即填充和提取缓冲区)转移回操做系统,于是能够极大地提升速度。
速度的提升来自于所使用的结构更接近于操做系统执行I/O的方式:通道(Channel)和缓冲器(Buffer)
通道和缓冲器是NIO中的核心对象,几乎每个I/O操做中都会使用它们。通道是对原I/O包中的流的模拟。到任何地方(来自任何地方)的数据都得必须经过一个Channel对象。一个Buffer实质上是一个容器对象。发送给一个通道的全部对象都必须首先放到Buffer缓冲器中。
咱们能够将它们想象成一个煤矿,通道就是一个包含煤矿(数据)的矿藏,而缓冲器就是派送到矿藏中的矿车,矿车载满煤炭而归,咱们再从矿车上获取煤炭。也就是说,咱们并无直接和通道交互,咱们只是和缓冲器进行交互。
Buffer是一个对象,它包含着一些须要读取的数据或者是要传输的数据。在NIO中加入了Buffer对象,体现了和以前的I/O的一个重要的区别。在面向流的I/O中咱们直接经过流对象直接和数据进行交互的,可是在NIO中咱们和数据的交互必须经过Buffer了。
缓冲器实质上是一个数组。一般它是一个字节的数组,可是也可使用其余种类的数组。可是一个缓冲器不只仅是一个数组,缓冲器提供了对数据结构化的访问,并且还能够跟踪系统的读写进程。
接下来咱们能够看一下Buffer
相关的实现类
每个 Buffer
类都是 Buffer
接口的一个实例。 除了 ByteBuffer
,每个 Buffer
类都有彻底同样的操做,只是它们所处理的数据类型不同。由于大多数标准 I/O 操做都使用 ByteBuffer
,因此它具备全部共享的缓冲区操做以及一些特有的操做。
ByteBuffer
是惟一一个直接与通道交互的缓冲器——也就说,能够存储未加工字节的缓冲器。当咱们查看ByteBuffer
源码时会发现其经过告知分配多少存储空间来建立一个ByteBuffer
对象,而且还有一个方法选择集,用于以原始的字节形式或者基本数据类型输出和读取数据。可是,也没办法输出或者读取对象,便是是字符串的对象也不行。这种处理方式虽然很低级,可是正好,由于这是大多数操做系统中更有效的映射方式。
Channel
是一个对象,缓冲器能够经过它进行读取和写入数据。和原来的I/O作个比较,通道就像个流。正如前面所提到的,Channel
是不和数据进行交互。可是它和流有一点不一样,就是通道是双向的,而流只能是单向的(只能是InputStream或者OutputStream),可是通道能够用于读、写或者是同时用于读写。
在以前的I/O中有三个类被修改,能够用来产生FileChannel
对象。这三个类是FileInputStream
、FileOutputStream
以及既用于读也用于写的RandomAccessFile
。
下面就举个建立FileChannel
的例子。
FileChannel in = new FileInputStream("fileName").getChannel();
我会举一个简单的例子来演示如何使用NIO对文件进行复制的操做。仍是上面所说的,NIO中对数据操做的是缓冲器,和缓冲器交互的通道,因此如今须要咱们有两个对象一个是Buffer
和Channel
。
public static void main(String[] args) throws IOException { //获取读通道 FileChannel in = new FileInputStream("/Users/hupengfei/Downloads/hu.sql").getChannel(); //获取写通道 FileChannel out = new FileOutputStream("/Users/hupengfei/Downloads/a.sql").getChannel(); //为缓冲器进行初始化大小 ByteBuffer byteBuffer =ByteBuffer.allocate(1024); while (in.read(byteBuffer)!=-1){ //作好让人读的准备 byteBuffer.flip(); out.write(byteBuffer); //清除数据 byteBuffer.clear(); } }
一旦要用从缓冲器中读取数据的话,那么就要调用缓冲器的flip()
方法,让它作好让别人读取字节的准备。那么写完数据之后就要调用缓存器的clear()
方法对全部的内部的指针从新安排,以便缓冲器在另外一个read()
操做期间可以作好接受数据的准备。而后数据就会从源文件中源源不断的读到了目标文件中。
clear()
方法在源码中有介绍,此方法不会实际的清除在缓冲器的数据。
固然上面的方法也能够简便,直接将两个通道进行相连只须要调用transferTo()
方法,这个也是复制文件的效果。
public static void main(String[] args) throws IOException { FileChannel in = new FileInputStream("/Users/hupengfei/Downloads/hu.sql").getChannel(); FileChannel out = new FileOutputStream("/Users/hupengfei/Downloads/a.sql").getChannel(); in.transferTo(0,in.size(),out); }