java NIO
简介
NIO是java 1.4版本引进的一个新的IO API, NIO 支持面向缓冲区的,通过通道的IO操作。
IO | NIO |
---|---|
面向流 | 面向缓冲区 |
阻塞IO | 非阻塞IO |
(无) | 选择器(Selectors) |
缓冲区:
在java NIO 中负责数据的存取,缓冲区就是数组.用于存储不同类型的数据.根据数据类型不同(boolean 除外),提供了相应类型的缓冲区
- ByteBuffer
- CharBuffer
- ShortBuffer
- IntBuffer
- LongBuffer
- FloatBuffer
- DoubleBuffer
package cn.ckh2019.test;
import org.junit.Test;
import java.nio.ByteBuffer;
/**
* @author Chen Kaihong
* 2019-06-22 14:44
*
* 缓存区存取数据的两个核心方法
* put() : 存入数据到缓冲区中
* get() : 获取缓冲区的数据
*
* 缓冲区中的四个核心属性
* capacity : 容量,表示缓冲区存储数据的最大容量.一旦声明不能改变
* limit : 界限,表示缓冲区中可以操作数据的大小.(limit 后的数据不能进行读写)
* position : 位置,表示缓冲区中正在操作数据的位置 (规律 : position <= limit <= capacity )
* mark : 标记,表示记录当前position的位置.可以通过reset()恢复到这个位置
*
*/
public class TestBuffer {
@Test
public void test1(){
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
//利用put()存入数据到缓冲区中
byteBuffer.put("ckh".getBytes());
// 切换到读取数据的模式 此时 position会变为 0 ,limit变为缓冲区数据的长度,capacity不变
byteBuffer.flip();
byteBuffer.mark();
//利用get() 读取缓冲区的数据
byte[] dst = new byte[byteBuffer.limit()];
byteBuffer.get(dst);
byteBuffer.reset();
// rewind() 可重复读数据(position回到0)
byteBuffer.rewind();
//判断缓冲区是否还有剩余的数据
if (byteBuffer.hasRemaining()){
//获取缓冲区中可以操作的剩余数量
System.out.println(byteBuffer.remaining());
}
//clear() 清空缓冲区,各指针回到最初状态,缓冲区的数据依然存在,但是数据处于被遗忘状态
byteBuffer.clear();
}
}
直接缓冲区和非直接缓冲区
- 非直接缓冲区: 通过allocate() 方法分配缓冲区,将缓冲区建立在JVM的内存中
- 直接缓冲区 : 通过allocatedDirect() 方法分配直接缓冲区,将缓冲区建立在操作系统的物理内存中.可以提高效率.
@Test
public void test(){
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
//isDirect() 判断是否是直接缓冲区
System.out.println(buffer.isDirect());
}
通道
用于源节点与目标节点的连接,在java NIO 中负责缓冲区数据的传输 。Channel本身不存储数据,因此需要配合缓冲区进行传输 。
通道的一些主要实现类
java.nio.channels.Channel接口
- FileChannel : 用于读取、写入、映射和操作文件的通道 。
- SocketChannel : 通过TCP读写网络中数据的通道 。
- ServerSocketChannel : 可以监听新进来的TCP连接,对每一个新进来的连接都会创建一个SocketChannel 。
- DatagramChannel : 通过UDP读写网络中数据的通道 。
获取通道的方式
- java针对支持通道的类提供了getChannel() 方法
- 本地IO:
- FileInputStream/FileOutputStream
- RandomAccessFile
- 网络IO
- Socket
- ServerSocket
- DatagramSocket
- 本地IO:
- 在JDK1.7中的NIO.2 针对各个通道提供了静态方法open()
- 在JDK1.7中的NIO.2的Files工具类的newByteChannel()
/**
* 利用通道完成文件的复制(非直接缓冲区)
*/
@Test
public void test2(){
FileInputStream fis = null;
FileOutputStream fos = null;
FileChannel inChannel = null;
FileChannel outChannel = null;
try {
fis = new FileInputStream("D:/Apicture/abc.jpg");
fos = new FileOutputStream("1.jpg");
//获取通道
inChannel = fis.getChannel();
outChannel = fos.getChannel();
//分配指定大小的缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
//将通道中的数据读到缓冲区
while (inChannel.read(buffer) != -1){
buffer.flip();
//将缓冲区的数据写入通道中
outChannel.write(buffer);
buffer.clear();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if (outChannel != null){
try {
outChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (inChannel != null){
try {
inChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fos != null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fis != null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 使用直接缓冲区完成文件复制(内存映射文件)
*/
@Test
public void test3() throws IOException {
FileChannel inChannel = FileChannel.open(Paths.get("D:/Apicture/abc.jpg"), StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(Paths.get("1.jpg"),StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE);
/*
StandardOpenOption :
READ 以读取方式打开文件
WRITE 已写入方式打开文件
CREATE 如果文件不存在,创建
CREATE_NEW 如果文件不存在,创建;若存在,异常。
APPEND 在文件的尾部追加
DELETE_ON_CLOSE 当流关闭的时候删除文件
TRUNCATE_EXISTING 把文件设置为0字节
SPARSE 文件不够时创建新的文件
SYNC 同步文件的内容和元数据信息随着底层存储设备
DSYNC 同步文件的内容随着底层存储设备
*/
//内存映射文件
MappedByteBuffer inMappedBuffer = inChannel.map(FileChannel.MapMode.READ_ONLY,0,inChannel.size());
MappedByteBuffer outMappedBuffer = outChannel.map(FileChannel.MapMode.READ_WRITE,0,inChannel.size());
//直接对缓冲区进行数据的读写操作
byte[] dst = new byte[inMappedBuffer.limit()];
inMappedBuffer.get(dst);
outMappedBuffer.put(dst);
inChannel.close();
outChannel.close();
}
下面的代码测试直接缓冲区和非直接缓冲区的速度
@Test
public void test4() throws Exception {
FileInputStream fis = new FileInputStream("D:/Apicture/gakki.mp4");
FileOutputStream fos = new FileOutputStream("D:/Apicture/gakki1.mp4");
FileChannel inChannel = fis.getChannel();
FileChannel outChannel = fos.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
long start = System.currentTimeMillis();
while (inChannel.read(buffer) != -1){
buffer.flip();
outChannel.write(buffer);
buffer.clear();
}
System.out.println(System.currentTimeMillis()-start);
/*
测试三次 1908 855 752
*/
outChannel.close();
inChannel.close();
fos.close();
fis.close();
}
@Test
public void test5() throws IOException {
FileChannel inChannel = FileChannel.open(Paths.get("D:/Apicture/gakki.mp4"),StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(Paths.get("D:/Apicture/gakki1.mp4"),StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE);
MappedByteBuffer inMappedBuffer = inChannel.map(FileChannel.MapMode.READ_ONLY,0,inChannel.size());
MappedByteBuffer outMappedBuffer = outChannel.map(FileChannel.MapMode.READ_WRITE,0,inChannel.size());
long start = System.currentTimeMillis();
byte[] dst = new byte[inMappedBuffer.limit()];
inMappedBuffer.get(dst);
outMappedBuffer.put(dst);
System.out.println(System.currentTimeMillis()-start);
/*
测试三次 118 140 127
*/
outChannel.close();
inChannel.close();
}
分散与聚集
分散读取 : 将通道中的数据分散到多个缓冲区中
聚集写入 : 将多个缓冲区的数据聚集到通道中
@Test
public void test6() throws Exception {
RandomAccessFile raf = new RandomAccessFile("d:/Apicture/1.txt","rw");
FileChannel channel = raf.getChannel();
ByteBuffer buffer1 = ByteBuffer.allocate(100);
ByteBuffer buffer2 = ByteBuffer.allocate(512);
//分散读取
ByteBuffer[] buffers = {buffer1,buffer2};
channel.read(buffers);
buffer1.flip();
buffer2.flip();
System.out.println(new String(buffer1.array(),0,buffer1.limit()));
System.out.println(new String(buffer2.array(),0,buffer1.limit()));
//聚集写入
RandomAccessFile raf2 = new RandomAccessFile("D:/Apicture/11.txt","rw");
FileChannel channel1 = raf2.getChannel();
channel1.write(buffers);
channel.close();
channel1.close();
raf.close();
raf2.close();
}
字符集
@Test
public void test7() throws CharacterCodingException {
/*
//这段代码可查看所由支持的编码格式
Map<String,Charset> map = Charset.availableCharsets();
Set<Map.Entry<String,Charset>> set = map.entrySet();
for (Map.Entry<String,Charset> entry : set){
System.out.println(entry.getKey()+"---"+entry.getValue());
}*/
Charset charset = Charset.forName("GBK");
//获取编码器
CharsetEncoder ce = charset.newEncoder();
//获取解码器
CharsetDecoder cd = charset.newDecoder();
CharBuffer charBuffer = CharBuffer.allocate(1024);
charBuffer.put("ckh2019");
charBuffer.flip();
//编码
ByteBuffer byteBuffer = ce.encode(charBuffer);
System.out.println(byteBuffer.array().length);
for (int i = 0;i < 7;i ++){
System.out.println(byteBuffer.get());
}
//解码
byteBuffer.flip();
CharBuffer charBuffer1 = cd.decode(byteBuffer);
System.out.println(charBuffer1.toString());
}
NIO 的非阻塞网络通信
用NIO实现阻塞式网络通信
package cn.ckh2019.test;
import org.junit.Test;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
/**
* @author Chen Kaihong
* 2019-06-25 21:19
*/
public class TestBlockingNIO {
/**
* 客户端
* @throws IOException
*/
@Test
public void client() throws IOException {
//获取通道
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",9000));
FileChannel fileChannel = FileChannel.open(Paths.get("D:/Apicture/1.txt"));
//分配指定大小缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
//读取本地文件,并发送到服务端
while (fileChannel.read(buffer) != -1){
buffer.flip();
socketChannel.write(buffer);
buffer.clear();
}
socketChannel.shutdownOutput();
//接收服务端的反馈
int len = 0;
while ((len = socketChannel.read(buffer)) != -1){
buffer.flip();
System.out.println(new String(buffer.array(),0,len));
buffer.clear();
}
socketChannel.close();
fileChannel.close();
}
/**
* 服务端
*/
@Test
public void server() throws IOException {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
FileChannel fileChannel = FileChannel.open(Paths.get("D:/Apicture/2.txt"), StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE);
//绑定连接
serverSocketChannel.bind(new InetSocketAddress(9000));
//获取客户端连接的通道
SocketChannel socketChannel = serverSocketChannel.accept();
//分配指定大小的缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
//接收客户端的数据保存到本地
while (socketChannel.read(buffer) != -1){
buffer.flip();
fileChannel.write(buffer);
buffer.clear();
}
//发送反馈给客户端
buffer.put("OK!!!".getBytes());
buffer.flip();
socketChannel.write(buffer);
fileChannel.close();
socketChannel.close();
}
}
用NIO 完成非阻塞的网络通信
package cn.ckh2019.test;
import org.junit.Test;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Date;
import java.util.Set;
/**
* @author Chen Kaihong
* 2019-06-26 9:44
*/
public class TestNonBlockingNIO {
/**
* 客户端
*/
@Test
public void client() throws IOException {
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",9000));
//切换非阻塞模式
socketChannel.configureBlocking(false);
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put(new Date().toString().getBytes());
buffer.flip();
socketChannel.write(buffer);
buffer.clear();
socketChannel.close();
}
/**
* 服务端
*/
@Test
public void server() throws IOException {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//切换非阻塞模式
serverSocketChannel.configureBlocking(false);
serverSocketChannel.bind(new InetSocketAddress(9000));
//获取选择器
Selector selector = Selector.open();
//将通道注册到选择器,并且指定"监听接收事件"
/*
SelectionKey的四个常量
读 : SelectionKey.OP_READ
写 : SelectionKey.OP_WRITE
连接 : SelectionKey.OP_CONNECT
接收 : SelcetionKey.OP_ACCEPT
如果要接听多个状态 可以用位或运算符 : SelcetionKey.OP_ACCEPT|SelectionKey.OP_CONNECT
*/
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//轮循式获取选择器上已经准备就绪的事件
while (selector.select()>0){
//获取当前选择器中所有注册的选择键(已就绪的监听事件)
Set<SelectionKey> set = selector.selectedKeys();
for (SelectionKey key : set){
//判断具体是什么事件准备就绪
if (key.isAcceptable()){
//如果是接收就绪.获取客户端连接
SocketChannel socketChannel = serverSocketChannel.accept();
//切换到非阻塞模式
socketChannel.configureBlocking(false);
//将该通道注册到选择器上
socketChannel.register(selector,SelectionKey.OP_READ);
} else if (key.isReadable()) {
//获取当前选择器上 "读就绪" 的通道
SocketChannel socketChannel = (SocketChannel) key.channel();
//读取数据
ByteBuffer buffer = ByteBuffer.allocate(1024);
int len = 0;
while ((len = socketChannel.read(buffer)) > 0){
buffer.flip();
System.out.println(new String(buffer.array(),0,len));
buffer.clear();
}
}
}
//取消选择键
set.removeAll(set);
}
}
}
利用UDP协议完成NIO的非阻塞网络通信
package cn.ckh2019.test;
import org.junit.Test;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.Date;
import java.util.Iterator;
import java.util.Scanner;
/**
* @author Chen Kaihong
* 2019-06-26 10:22
*/
public class UDPNonBlockingNIO {
/**
* 发送端
*/
@Test
public void send() throws IOException {
DatagramChannel datagramChannel = DatagramChannel.open();
datagramChannel.configureBlocking(false);
ByteBuffer buffer = ByteBuffer.allocate(1024);
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()){
String str = scanner.next();
buffer.put(str.getBytes());
buffer.flip();
datagramChannel.send(buffer,new InetSocketAddress("127.0.0.1",9000));
buffer.clear();
}
datagramChannel.send(buffer,new InetSocketAddress("127.0.0.1",9000));
datagramChannel.close();
}
/**
* 接收端
*/
@Test
public void receive() throws IOException {
DatagramChannel datagramChannel = DatagramChannel.open();
datagramChannel.configureBlocking(false);
datagramChannel.bind(new InetSocketAddress(9000));
Selector selector = Selector.open();
datagramChannel.register(selector, SelectionKey.OP_READ);
while (selector.select() > 0) {
Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
while (keys.hasNext()) {
SelectionKey key = keys.next();
if (key.isReadable()) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
datagramChannel.receive(buffer);
buffer.flip();
System.out.println(new String(buffer.array(),0,buffer.limit()));
buffer.clear();
}
}
keys.remove();
}
}
}
管道
java NIO 是两个线程之间的单向数据连接。Pipe有一个 source通道和一个sink通道.数据会被写到sink通道,从source通道读取。
@Test
public void test() throws IOException {
//获取管道
Pipe pipe = Pipe.open();
//将缓冲区的数据写入管道
ByteBuffer buffer = ByteBuffer.allocate(1024);
Pipe.SinkChannel sinkChannel = pipe.sink();
buffer.put(new Date().toString().getBytes());
buffer.flip();
sinkChannel.write(buffer);
//读取缓冲区中的数据
Pipe.SourceChannel sourceChannel = pipe.source();
buffer.flip();
int len = sourceChannel.read(buffer);
System.out.println(new String(buffer.array(),0,len));
sourceChannel.close();
sinkChannel.close();
}