NIO簡介
什麼是Java NIO,nio在java1.4時新增,叫做new I/O,就是新的I/O,既是在基於1.0出現的I/O Stream操作之上的新改變,
包括,新的 I/O通信模型,如Buffer,Channels,多路複用(Selector);基於Perl樣式正則表達式的模式匹配工具。
java.nio.Buffer
一個特點原始數據類型(並不包括如String等類)的集合,提供了方便的put,get,數據批量移動等操作,就像我們用數組來緩存每次讀取的數據一樣,但提供了更加方便的操作,還有一個特殊的基於直接內存來分配的ByteBuffer.allocateDirect()的操作,基於直接內存來操作,雖然在gc中並不回收這部分內存,但大數據的操作效率更高。
Buffer內部維護0<=mark<=position<=limit<=capacity的指針,來操作緩存數據,對外提供各種類型的put,get,批量移動;對緩存容器進行創建,翻轉,釋放,壓縮,標記
緩衝區的使用實例
public static void main(String[] args) {
//1.基於allocate方法調用得到的heap(JVM管理的堆)上的緩存
ByteBuffer byteBuffer = ByteBuffer.allocate(8);
byteBuffer.put((byte) 'a');
byteBuffer.put((byte) 'b');
byteBuffer.put((byte) 'c');
//翻轉函數,設置limit = position,position = 0,
byteBuffer.flip();
//hasRemaining = limit - position>0
while (byteBuffer.hasRemaining()) {
//position = position+1*(1byte)
System.out.println((char) byteBuffer.get());
}
//直接返回當前這個Buffer底層的數組,和position,limit等下標操縱無關
byte[] bytes = byteBuffer.array();
for (byte aByte : bytes) {
System.out.println(aByte);
}
//int類型的Buffer,同理其他的Buffer也是如此
IntBuffer intBuffer = IntBuffer.allocate(8);
intBuffer.put(1);
intBuffer.put(2);
intBuffer.put(3);
intBuffer.flip();
while (intBuffer.hasRemaining()) {
System.out.println(intBuffer.get());
}
//2.基於Wrap()方法得到heap(JVM管理的堆)上的緩存
//基礎類型的Wrap(),直接創建一個數組長度的緩衝區,position=0,limit =capacity = length;
LongBuffer longBuffer = LongBuffer.wrap(new long[]{1L, 2L, 3L});
//因爲offset=position =0,所以不用再次flip
while (longBuffer.hasRemaining()) {
System.out.println("long:" + longBuffer.get());
}
//已經到結尾了,所以要重置一下指針,內部的數據並未發生改變
//position = 0;
//limit = capacity;
//mark = -1;
longBuffer.clear();
longBuffer.put(4L);
longBuffer.flip();
while (longBuffer.hasRemaining()) {
System.out.println("long:" + longBuffer.get());
}
// 這是爲了方便處理字符串處理而實現的特例,包裝CharSequence,如String,StringBuffer,StringBuilder
CharSequence charBuffer = CharBuffer.wrap("hello world");
while (((CharBuffer) charBuffer).hasRemaining()) {
System.out.println("char:" + ((CharBuffer) charBuffer).get());
}
//批量移動,把float[]的數批量put到floatBuffer中,然後再get出floatBuffer的數據,放在tmpBuffer中
FloatBuffer floatBuffer = FloatBuffer.allocate(8);
floatBuffer.put(new float[]{0.4f, 0.2f, 0.3f});
float[] tmpBuffer = new float[4];
floatBuffer.flip();
floatBuffer.get(tmpBuffer, 0, floatBuffer.remaining());
for (float v : tmpBuffer) {
System.out.println("float:" + v);
}
DoubleBuffer doubleBuffer = DoubleBuffer.allocate(8);
doubleBuffer.put(new double[]{0.5d, 0.6d, 0.7d});
//mark一下position,mark = position
doubleBuffer.mark();
doubleBuffer.put(new double[]{0.8d, 0.9d, 0.11d});
//position = mark;
doubleBuffer.reset();
//position = mark,limit = capacity,所以打印出來了最後兩個沒有實際賦值的0.0
while (doubleBuffer.hasRemaining()) {
System.out.println("double:" + doubleBuffer.get());
}
ShortBuffer shortBuffer = ShortBuffer.allocate(8);
shortBuffer.put(new short[]{1, 2, 3, 4, 5, 6});
System.out.println("shortBuffer.position:" + shortBuffer.position());
//手動設置position
shortBuffer.position(3);
//剩餘position個數據,limit=capacity
shortBuffer.compact();
while (shortBuffer.hasRemaining()) {
System.out.println("short:" + shortBuffer.get());
}
//直接內存緩衝區,1char = 2byte,1short=2byte,1int = 4byte,1long = 8byte,1float = 4byte,1double = 8byte
//不會被gc的區域
ByteBuffer directByteBuffer = ByteBuffer.allocateDirect(8);
directByteBuffer.putInt(10).putInt(11);
directByteBuffer.flip();
//1.直接類型映射
/*while (directByteBuffer.hasRemaining()) {
System.out.println("directByteBuffer:" + directByteBuffer.getInt());
}*/
//2.buffer類型轉換
IntBuffer intBuffer1 = directByteBuffer.asIntBuffer();
while(intBuffer1.hasRemaining()){
System.out.println("directByteBuffer:" + intBuffer1.get());
}
}
java.nio.channels.Channel
什麼是channel,channel是(A nexus for I/O operations.),一個i/o操作的連接。
FileChannel文件拷貝實例
/**
* 利用transfer直接拷貝
*/
private static void copyFile2(String source, String target) throws IOException {
File fileRead = new File(source);
File fileWrite = new File(target);
RandomAccessFile randomAccessFileRead = new RandomAccessFile(fileRead, "r");
RandomAccessFile randomAccessFileWrite = new RandomAccessFile(fileWrite, "rw");
FileChannel fileChannelRead = randomAccessFileRead.getChannel();
FileChannel fileChannelWrite = randomAccessFileWrite.getChannel();
fileChannelRead.transferTo(0, fileChannelRead.size(), fileChannelWrite);
fileChannelRead.close();
fileChannelWrite.close();
}
/**
* 手工拷貝
* @param source
* @param target
* @throws IOException
*/
private static void copyFile1(String source, String target) throws IOException {
File fileRead = new File(source);
File fileWrite = new File(target);
RandomAccessFile randomAccessFileRead = new RandomAccessFile(fileRead, "r");
RandomAccessFile randomAccessFileWrite = new RandomAccessFile(fileWrite, "rw");
//16k
ByteBuffer buffer = ByteBuffer.allocateDirect(1 << 14);
FileChannel fileChannelRead = randomAccessFileRead.getChannel();
FileChannel fileChannelWrite = randomAccessFileWrite.getChannel();
while (fileChannelRead.read(buffer) != 0) {
buffer.flip();
fileChannelWrite.write(buffer);
}
fileChannelRead.close();
fileChannelWrite.close();
}
不帶Selector的NIO網絡通信實例
server,服務端監聽端口8888,等待服務端連接,連接完成後,直接在當前線程(main線程)中,接受來自客戶端的文件,並寫出到文件中。
private static void server(String filename) throws IOException, InterruptedException {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.socket().bind(new InetSocketAddress(8888));
while (true) {
SocketChannel socketChannel = serverSocketChannel.accept();
if (socketChannel == null) {
System.out.println("等待客戶端連接.");
TimeUnit.SECONDS.sleep(2);
continue;
}
//16k
ByteBuffer buffer = ByteBuffer.allocate(1 << 14);
RandomAccessFile randomAccessFile = new RandomAccessFile(filename, "rw");
while (socketChannel.read(buffer) != 0) {
buffer.flip();
randomAccessFile.getChannel().write(buffer);
}
if (socketChannel.isOpen()) {
socketChannel.close();
}
break;
}
serverSocketChannel.close();
}
client
客戶端連接127.0.0.1端口爲8888的服務,然後發送數據,等待服務端接受。
private static void client(String filename) throws IOException {
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8888));
RandomAccessFile randomAccessFile = new RandomAccessFile(filename, "r");
//16k
ByteBuffer buffer = ByteBuffer.allocateDirect(1 << 14);
while (randomAccessFile.getChannel().read(buffer) != 0) {
buffer.flip();
socketChannel.write(buffer);
}
randomAccessFile.close();
socketChannel.close();
}
java.nio.channels.Selector(對象的多路複用器)
多個channel可以同時註冊到同一個Selector中,Selector通過唯一的一個SelectionKey與一個通道建立聯繫,而每個SelectionKey都設置了一個關注事件類型,包括, OP_ACCEPT、OP_READ、OP_WRITE、OP_CONNECT四種事件類型。
使用Selector選擇器的服務端實例(客戶端可以使用上面的客戶端)
private static void server(String filename) throws IOException, InterruptedException {
Selector selector = Selector.open();
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.socket().bind(new InetSocketAddress(8888));
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
//阻塞等待連接
int complement = selector.select();
if (complement == 0) {
continue;
}
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
if (selectionKey.isAcceptable()) {
ServerSocketChannel serverSocketChannel1 = (ServerSocketChannel) selectionKey.channel();
System.out.println("接受連接。");
SocketChannel socketChannel = serverSocketChannel1.accept();
if(socketChannel==null){
continue;
}
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
} else if (selectionKey.isReadable()) {
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
//16k
ByteBuffer byteBuffer = ByteBuffer.allocate(1 << 14);
FileChannel fileChannel = new RandomAccessFile(filename, "rw").getChannel();
while (socketChannel.read(byteBuffer) > 0) {
byteBuffer.flip();
fileChannel.write(byteBuffer);
byteBuffer.clear();
}
fileChannel.close();
socketChannel.close();
}
iterator.remove();
}
}
}
這一篇寫了對Nio的理解,主要是Buffer、Channel、Selector的類,下一篇講解在Java7中Nio2。