NIO创建的目的是为了让java程序员实现高速IO而无需编写自定义的本机代码。NIO将最耗时的IO操作(即填充和提取缓冲区)转移回操作系统,因而可以极大的提高速度
原来的IO库(java.io.*)与NIO最重要的区别是数据打包与传输的方式。原来的IO以流的方式处理数据,而NIO以块的方式处理数据。
面向流的IO系统一次一个字节的处理数据。一个输入流产生一个字节的数据,一个输出流消费一个字节的数据。为流式数据创建过滤器非常容易。链接几个过滤器,以便每个过滤器只负责单个复杂处理机制的一部分,这样也相对简单。不利的一面是,面向流的IO通常相当慢。
一个面向块的IO系统以块的形式处理数据。每一个操作都在一步中产生或消费一个数据块。按块处理数据比按字节处理数据要快的多。但是面向块的IO缺少一些面向流的IO所具有的优雅性和简单性。
缓冲区是包在一个对象内的基本数据元素数组。 Buffer类相比一个简单数组的优点是它将关于数据的数据内容和信息包含在一个单一的对象中。Buffer类以及它专有的子类定义了一个用于处理数据缓冲区的API
所有的缓冲区都有四个属性来提供关于其所有包含的数据元素的信息。他们是:
上述的各种类型的缓冲区,都提供了读和写的方法 get和put方法,也提供了一些批量的put和get方法。而且缓冲区可以通过allocation创建,此方法通过wrapping讲一个现有(数据类型)数组包装到缓冲区中来为缓冲区内容分配空间,或者通过创建现有字节缓冲区的视图来创建。
分配一个容量为100个char变量的CharBuffer
CharBuffer charBuffer = CharBuffer.allocate(100)
提供数组用做缓冲区的备份存储器,调用wrap()函数
char[] myArray = new char[100]
CharBuffer charBuffer = CharBuffer.wrap(myArray)
通过allocate()或wrap()函数创建的缓冲区通常都是简介的。间接的缓冲区使用备份数组。hasArray()告诉这个缓冲区是否有一个可存取的备份数组。如果返回true,array()函数会返回这个缓冲区对象所使用的数组存储空间的引用。
public static void main(String[] args) {
IntBuffer intBuffer = IntBuffer.allocate(10);
int[] arr = new int[]{3,5,1};
intBuffer = intBuffer.wrap(arr);
intBuffer.put(0,7);
for (int i = 0; i < intBuffer.limit(); i++) {
System.out.print(intBuffer.get() + "\t");
}
System.out.println("");
for(int i : arr){
System.out.print(i + "\t");
}
}
输出:
7 5 1
7 5 1
Duplicate()函数创建了一个与原始缓冲区相似的新缓冲区。两个缓冲区共享数据元素,拥有相同的容量,但每个缓冲区拥有各自的位置,上界和标记属性。对一个缓冲区内的数据元素所做的改变会反映在另一个缓冲区上。这一副本缓冲区具有与原始缓冲区同样的数据视图。如果原始的缓冲区为只读,或者为直接缓冲区,新的缓冲区将继承这些属性。
通道是java.nio的第二个创新。Channer用于在字节缓冲区和位于通道另一侧的实体(通常是一个文件或套接字)之间有效的传输数据。
先填充缓冲区,接着将缓冲写到通道中。
不同的操作系统上通道实现会有根本性的差异,所以通道API仅仅描述了可以做什么。通道实现经常使用操作系统的本地代码。
对所有通道来说只有两种共同的操作:检查一个通道是否打开(IsOpen)和关闭一个打开的通道(close)
通道是访问IO服务的导管。IO可以分为广义的两大类别:File IO和Stream IO。相应的通道有文件通道和套接字通道。FIleChannel、SocketChannel、ServerSocketChannel、DatagramChannel
文件通道总是阻塞式的。FileChannel不能直接创建,只能通过在一个打开的file对象(RandomAccessFile、FIleInputStream或者FIleOutputStream)上调用getChannel()方法获取。调用getChannel()方法会返回一个连接到相同文件的FIleChannel对象且该FileChannel对象具有与file对象相同的访问权限。
FIleChannel对象是线程安全的(thread-safe)。多个进程可以在同一个实例上并发调用方法而不会引起任何问题。影响通道位置或者影响文件大小的操作都是单线程的。
Demo
public static void fileChannelDemo(){
try{
ByteBuffer buffer = ByteBuffer.allocate(1024);
//通过文件流获取到channel
FileChannel inFc = new FileInputStream("/Users/aa/Desktop/a.txt").getChannel();
FileChannel outFc = new FileOutputStream("/Users/aa/Desktop/a.txt",true).getChannel();
buffer.clear();
int len = inFc.read(buffer);
System.out.println(new String(buffer.array(),0,len));
ByteBuffer buf2 = ByteBuffer.wrap("jack".getBytes());
outFc.write(buf2);
outFc.close();
inFc.close();
}catch (Exception e){
e.printStackTrace();
}
}
public static void main(String[] args) {
fileChannelDemo();
}
Demo
求和
Server端
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
public class NioServerChannel {
private ByteBuffer buffer = ByteBuffer.allocate(1024);
private IntBuffer intBuffer = buffer.asIntBuffer();
private SocketChannel clientChannel = null;
private ServerSocketChannel serverChannel = null;
public void openChannel() throws Exception{
serverChannel = ServerSocketChannel.open();
serverChannel.socket().bind(new InetSocketAddress(8888));
}
public void waitReqConn() throws Exception{
while (true){
clientChannel = serverChannel.accept();
if(clientChannel != null){
System.out.println("new Connection");
}
processReq();
clientChannel.close();
}
}
public void processReq() throws Exception{
buffer.clear();
clientChannel.read(buffer);
int result = intBuffer.get(0) + intBuffer.get(1);
buffer.flip();
buffer.clear();
intBuffer.put(0,result);
clientChannel.write(buffer);
}
public void start(){
try{
openChannel();
waitReqConn();
clientChannel.close();
}catch (Exception e){
e.printStackTrace();
}
}
public static void main(String[] args) {
new NioServerChannel().start();
}
}
client
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.nio.channels.SocketChannel;
public class NioClientChannel {
SocketChannel socketChannel = null;
ByteBuffer buffer = ByteBuffer.allocate(8);
IntBuffer intBuffer = buffer.asIntBuffer();
public SocketChannel connect() throws Exception{
return SocketChannel.open(new InetSocketAddress("127.0.0.1",8888));
}
public void sendRequest(int a,int b) throws IOException {
buffer.clear();
intBuffer.put(0,a);
intBuffer.put(1,b);
socketChannel.write(buffer);
}
public int receiveResult() throws Exception{
buffer.clear();
socketChannel.read(buffer);
return intBuffer.get(0);
}
public int getSum(int a,int b){
int result = 0;
try {
socketChannel = connect();
sendRequest(a, b);
result = receiveResult();
}catch (Exception e){
}
return result;
}
public static void main(String[] args) {
System.out.println(new NioClientChannel().getSum(2147483647,20));
}
}
管理着一个被注册的通道集合的信息和他们的就绪状态。通道是和选择器一起被注册的,并且使用选择器来更新通道的就绪状态。
可选择通道(SelectableChannel)
FileChannel对象不是可选择的,因为他们没有继承SelectableChannel,所有socket通道都是可选择的,包括从管道对象中获得的通道。SelectableChannel可以被注册到Selector对象上,一个通道可以被注册到多个选择器上,但对每个选择器而言,只能被注册一次。
选择键(SelectionKey)
选择键封装了通道和选择器的注册关系。选择键被SelectableChannel.register返回并提供一个表示这种注册关系的标记。通道在被注册到一个选择器上之前,必须先设置为非阻塞模式(configureBlocking(false))
调用可选择通道的register()方法会将它注册到一个选择器上。如果试图注册一个处于阻塞状态的通道,register将抛出未检查的illegalBlockingModeException异常。此外,通道一旦被注册,就不能回到阻塞状态,如果试图这么做的话,将在调用configureBlocking方法时抛出illegalBlockingModeException异常。并且,试图注册一个已经关闭的SelectableChannel实例的话,将会抛出ClosedChannelException异常
在SelectionKeys中,用静态常量定义了四中IO操作:OP_READ 1、OP_WRITE 4、OP_CONNECT 8、OP_ACCEPT 16,这四个值任何2、3、4个相加结果都不相同。
当通道关闭时,所有相关的键会自动取消。当选择器关闭时,所有注册到该选择器的通道都将被注销,并且相关的键将立即被无效化(取消)。一旦键被无效化,调用他的与选择器相关的方法就会抛出CanceledKeyException
keys()获取已注册键的集合。
已选择的键的集合selectedKeys()是已注册的键的集合的子集,这个集合的每个成员都是相关的通道被选择器判断为已经准备好的。
已取消的键的集合是已注册的键的集合的子集,包含了cancel()方法被调用过的键(这个键已经被无效化),但他们还没有被注销。这个集合是选择器对象的私有成员,因而无法直接访问。