浅谈Java IO流

今天主要来回顾一下有关Java IO流的知识,后面还会对NIO进行介绍。html

Java IO

定义

Java的IO流是实现输入和输出的基础,能够方便的实现数据的输入和输出操做。java

IO流的分类

  • 按照流的流向分,能够分为输入流和输出流;
  • 按照操做单元划分,能够划分为字节流和字符流;
  • 按照流的角色划分为节点流和处理流。

Java Io流的40多个类都是从以下4个抽象类基类中派生出来的。数组

  • InputStream/Reader: 全部的输入流的基类,前者是字节输入流,后者是字符输入流。
  • OutputStream/Writer: 全部输出流的基类,前者是字节输出流,后者是字符输出流。

它们都是一些抽象基类,没法直接建立实例。异步

经常使用的IO流的用法

在InputStreamhe里面包含以下3个方法:socket

int read();
从输入流中读取单个字节,返回所读取的字节数据。
int read(byte[] b);
从输入流中最多读取b.length个字节的数据,并将其存储在字节数组b中,返回实际读取的字节数。
int read(byte[] b,int off,int len); 
从输入流中最多读取len个字节的数据,并将其存储在数组b中,放入数组b中时,并非从数组起点开始,而是
从off位置开始,返回实际读取的字节数。

OutputStream和Writer的用法也很是类似,两个流都提供了以下三个方法:学习

void write(int c); 
将指定的字节/字符输出到输出流中,其中c便可以表明字节,也能够表明字符。
void write(byte[]/char[] buf);
将字节数组/字符数组中的数据输出到指定输出流中。
void write(byte[]/char[] buf, int off,int len ); 
将字节数组/字符数组中从off位置开始,长度为len的字节/字符输出到输出流中。

由于字符流直接以字符做为操做单位,因此Writer能够用字符串来代替字符数组,即以String对象做为参数。Writer里面还包含以下两个方法。大数据

void write(String str);
将str字符串里包含的字符输出到指定输出流中。
void write (String str, int off, int len); 
将str字符串里面从off位置开始,长度为len的字符输出到指定输出流中。

IO文件流

下面咱们来看看Java中基于IO的文件流的使用方式spa

  • FileInputStream 是InputStream子类,以FileInputStream 为例进行文件读取
File f =new File("d:/czy.txt");
            //建立基于文件的输入流
            FileInputStream fis =new FileInputStream(f);
            //建立字节数组,其长度就是文件的长度
            byte[] all =new byte[(int) f.length()];
            //以字节流的形式读取文件全部内容并返回给all数组
            fis.read(all);
            for (byte b : all) {
                System.out.println(b);
            }             
            //每次使用完流,都应该进行关闭
            fis.close();

FileReader使用方法和上面同样,只不过是把byte改为char而已。3d

  • FileWriter 是Writer的子类,以FileWriter 为例把字符串写入到文件
File f = new File("d:/czy.txt");
            // 建立基于文件的Writer
            FileWriter fr = new FileWriter(f)
            // 以字符流的形式把数据写入到文件中
            String data="abcdefg1234567890";
            char[] cs = data.toCharArray();
            fr.write(cs);

NIO

定义

Java NIO 是JDK 1.4以后新出的一套IO接口,NIO中的N能够理解为Non-blocking。
原来的I/O以流的方式处理数据,而NIO以块的方式处理数据。code

组成

NIO最重要的组成部分:

  • 通道 Channel
  • 缓冲区 Buffer
  • 选择器 Selector

什么是通道?

Channel是一个对象,能够经过它读取和写入数据。

全部数据都经过 Buffer 对象来处理。您永远不会将字节直接写入通道中,相反,您是将数据写入缓冲区。一样,您不会直接从通道中读取字节,而是将数据从通道读入缓冲区,再从缓冲区获取这个字节。

缓冲区实质上是一个数组。一般它是一个字节数组,可是也可使用其余种类的数组。
最经常使用的缓冲区类型是 ByteBuffer

读写操做

从文件中读取

第一步是获取通道。咱们从 FileInputStream 获取通道:

FileInputStream fin = new FileInputStream( "d:/czy.txt" );
FileChannel fc = fin.getChannel();

下一步是建立缓冲区:

ByteBuffer buffer = ByteBuffer.allocate( 1024 );

最后,须要将数据从通道读到缓冲区中,以下所示:

fc.read( buffer );

allocate() 方法分配一个具备指定大小的底层数组,并将它包装到一个缓冲区对象中,在本例中是一个 ByteBuffer。

您还能够将一个现有的数组转换为缓冲区,以下所示:

byte array[] = new byte[1024];
ByteBuffer buffer = ByteBuffer.wrap( array );

写入文件

在 NIO 中写入文件相似于从文件中读取。首先从 FileOutputStream 获取一个通道:

FileOutputStream fout = new FileOutputStream( "writesomebytes.txt" );
FileChannel fc = fout.getChannel();

下一步是建立一个缓冲区并在其中放入一些数据

ByteBuffer buffer = ByteBuffer.allocate(1024);
//从message数组取出数据 
for (int i=0; i<message.length; ++i) {
     buffer.put(message[i]);
}
buffer.flip();

最后一步是写入缓冲区中:

fc.write( buffer );

flip() 方法让缓冲区能够将新读入的数据写入另外一个通道,后面咱们会详细介绍。

缓冲区内部细节

状态变量

能够用三个值指定缓冲区在任意时刻的状态:

  • position
  • limit
  • capacity

Position

缓冲区实际上就是一个数组。
若是是写入数据,position指定了下一个字节将放到数组的哪个元素。
若是是输出数据,position`指定下一个字节来自数组的哪个元素。

Limit

limit变量代表还有多少数据须要取出或者还有多少空间能够放入数据。

position老是小于或者等于limit

Capacity

缓冲区的capacity代表能够储存在缓冲区中的最大数据容量。

limit决不能大于capacity

观察变量

咱们首先观察一个新建立的缓冲区。出于本例子的须要,咱们假设这个缓冲区的总容量为8个字节。Buffer的状态以下所示:

Buffer state

回想一下 ,limit决不能大于capacity,此例中这两个值都被设置为 8。咱们经过将它们指向数组的尾部以后(若是有第8个槽,则是第8个槽所在的位置)来讲明这点。

Array

position设置为0。若是咱们读一些数据到缓冲区中,那么下一个读取的数据就进入 slot 0 。若是咱们从缓冲区写一些数据,从缓冲区读取的下一个字节就来自 slot 0 。position设置以下所示:

Position setting

因为capacity不会改变,因此咱们在下面的讨论中能够忽略它。

第一次读取

如今咱们能够开始在新建立的缓冲区上进行读/写操做。首先从输入通道中读一些数据到缓冲区中。第一次读取获得三个字节。它们被放到数组中从position开始的位置,这时 position 被设置为 0。读完以后,position 就增长到 3,以下所示:

Position increased to 3

limit没有改变。

第二次读取

在第二次读取时,咱们从输入通道读取另外两个字节到缓冲区中。这两个字节储存在由position所指定的位置上,position于是增长 2:

Position increased by 2

limit没有改变。

flip

如今咱们要将数据写到输出通道中。在这以前,咱们必须调用flip()方法。这个方法作两件很是重要的事:

  1. 它将limit设置为当前position
  2. 它将position设置为 0。

前一小节中的图显示了在 flip 以前缓冲区的状况。下面是在 flip 以后的缓冲区:

Buffer after the flip

咱们如今能够将数据从缓冲区写入通道了。position被设置为 0,这意味着咱们获得的下一个字节是第一个字节。limit已被设置为原来的position,这意味着它包括之前读到的全部字节,而且一个字节也很少。

第一次写入

在第一次写入时,咱们从缓冲区中取四个字节并将它们写入输出通道。这使得position增长到 4,而limit不变,以下所示:

Position advanced to 4, limit unchanged

第二次写入

咱们只剩下一个字节可写了。limit在咱们调用flip()时被设置为 5,而且position不能超过limit。因此最后一次写入操做从缓冲区取出一个字节并将它写入输出通道。这使得position增长到 5,并保持limit不变,以下所示:

Position advanced to 5, limit unchanged

clear

最后一步是调用缓冲区的clear()方法。这个方法重设缓冲区以便接收更多的字节。Clear作两种很是重要的事情:

  1. 它将limit设置为与capacity相同。
  2. 它设置position为 0。

下图显示了在调用clear()后缓冲区的状态:

State of the buffer after clear() has been called

缓冲区如今能够接收新的数据了。

缓冲区的使用

下面的内部循环归纳了使用缓冲区将数据从输入通道拷贝到输出通道的过程。

while (true) {
     buffer.clear();
     int r = fcin.read(buffer);
 
     if (r==-1) {
       break;
     }
 
     buffer.flip();
     fcout.write(buffer);
}

read()write() 调用获得了极大的简化,由于许多工做细节都由缓冲区完成了。
clear()flip() 方法用于让缓冲区在读和写之间切换。

Selectors

主要用于异步I/O

Selector就是您注册对各类 I/O 事件的兴趣的地方,并且当那些事件发生时,就是这个对象告诉您所发生的事件。

咱们须要作的第一件事就是建立一个 Selector

Selector selector = Selector.open();

而后,咱们将对不一样的通道对象调用 register() 方法,以便注册咱们对这些对象中发生的 I/O 事件的兴趣。register() 的第一个参数老是这个 Selector。

打开一个 ServerSocketChannel

为了接收链接,咱们须要一个 ServerSocketChannel。事实上,咱们要监听的每个端口都须要有一个 ServerSocketChannel 。对于每个端口,咱们打开一个 ServerSocketChannel,以下所示:

ServerSocketChannel ssc = ServerSocketChannel.op
en();
ssc.configureBlocking( false );//非阻塞
 
ServerSocket ss = ssc.socket();
InetSocketAddress address = new InetSocketAddress(ports[i]);
ss.bind(address);//绑定端口

第一行建立一个新的 ServerSocketChannel ,最后三行将它绑定到给定的端口。第二行将 ServerSocketChannel 设置为 非阻塞的 。咱们必须对每个要使用的套接字通道调用这个方法,不然异步 I/O 就不能工做。

注册

将新打开的 ServerSocketChannels 注册到 Selector上

SelectionKey key = ssc.register( selector, SelectionKey.OP_ACCEPT );

内部循环

使用 Selector 的几乎每一个程序都像下面这样使用内部循环:

咱们调用 Selector 的 select() 方法。这个方法会阻塞,直到至少有一个已注册的事件发生
select() 方法将返回所发生的事件的数量

int num = selector.select();

调用 Selector 的 selectedKeys() 方法,它返回发生了事件的 SelectionKey 对象的一个集合

Set selectedKeys = selector.selectedKeys();
Iterator it = selectedKeys.iterator();
 
while (it.hasNext()) {
     SelectionKey key = (SelectionKey)it.next();
     // ... deal with I/O event ...
}

接受新的链接

经过channel()方法能够取得通道对象

ServerSocketChannel ssc = (ServerSocketChannel)key.channel();
SocketChannel sc = ssc.accept();

下一步是将新链接的 SocketChannel 配置为非阻塞的

sc.configureBlocking( false );
SelectionKey newKey = sc.register( selector, SelectionKey.OP_READ );

关于Java IO和NIO的知识暂且学习到这里,后面有时间会继续补充!

参考

NIO入门

相关文章
相关标签/搜索