1.什么是nio
nio是一个非阻塞的IO。之前学的IO是面向流的一种技术。而nio是面向缓冲区的一种技术。采用通道传输。nio可以高效的进行文件的读写操作(之所以速度的提高来自于操作系统所执行的IO方式:缓冲区和通道)。
2.缓冲区
缓冲区的API除了没有boolean对应的Buffer,其他基本类型都有,如IntBuffer,ByteBuffer,FloatBuffer等。
缓冲区的原理:
缓冲区在java nio中负责数据的存取。缓冲区就是数组,用于存取不同类型的数据。
缓冲区的属性:
初始化图:(分配空间为7的容量)
capacity:容量,表示缓冲区中最大存储数据的容量。一旦声明不能改变。
limit:界限,表示缓冲区中可以操作数据的大小。(limit后数据不能读写)
position:位置,表示缓冲区中正在操作数据的位置。
public class TestBuffer {
private void printProperties(int position,int limit,int capacity){
System.out.println("capacity:"+capacity);
System.out.println("limit:"+limit);
System.out.println("position:"+position);
}
@Test
public void test1(){
String str ="abcde";
//分配一个指定大小的缓冲区
ByteBuffer buf =ByteBuffer.allocate(1024);
System.out.println("----------初始化------------");
printProperties(buf.position(),buf.limit(),buf.capacity());
}
}
结果:
put方法:存入元素到缓冲区
System.out.println("-----------put------------");
buf.put(str.getBytes());
printProperties(buf.position(),buf.limit(),buf.capacity());
结果:
flip方法:用来切换到读模式
buf.flip();
printProperties(buf.position(),buf.limit(),buf.capacity());
System.out.println("----------get-------------");
byte[] dest =new byte[buf.limit()];
buf.get(dest);
printProperties(buf.position(),buf.limit(),buf.capacity());
结果:
rewind方法:重复读,将位置设置为零并丢弃标记。
System.out.println("----------rewind-------------");
buf.rewind();
printProperties(buf.position(),buf.limit(),buf.capacity());
结果:
clear方法:清空缓冲区,将位置设置为零,限制设置为该容量,并且丢弃标记。
System.out.println("----------clear-------------");
buf.clear();
printProperties(buf.position(),buf.limit(),buf.capacity());
结果:
mark和reset方法:
mark属性:标记,表示记录当前position的位置。可以通过reset()恢复到mark的位置。reset 恢复到mark标记的位置。
@Test
public void test2(){
String str ="efghijk";
ByteBuffer buf =ByteBuffer.allocate(1024);
printProperties(buf.position(), buf.limit(), buf.capacity());
buf.put(str.getBytes());
buf.flip();
byte[] dest =new byte[buf.limit()];
buf.get(dest,0,3);
System.out.println(new String(dest,0,2));
System.out.println(buf.position());
//mark
buf.mark();
buf.get(dest,2,2);
System.out.println(new String(dest,2,2));
System.out.println(buf.position());
//reset 恢复到mark标记的位置
buf.reset();
System.out.println(buf.position());
}
结果:
直接缓冲区与间接缓冲区的区别:
间接缓冲区:通过allocate方法分配缓冲区,将缓冲区建立在jvm内存中
直接缓冲区:通过allocateDirect方法分配直接缓冲区,将缓冲区建立在操作系统的物理内存中。
3.通道
通道在java nio中负责缓冲区中数据的传输。
主要实现类:
①FileChannel:用于操作本地的通道。
②SocketChannel:用于操作网络TCP协议的通道。
③ServerSocketChannel:用于操作网络TCP协议的通道。
④DatagramChannel:用于操作网络UDP协议的通道。
获取通道:
本地IO方式:FileInputStream/FileOutputStream、RandomAccessFile
网络IO方式:Socket、ServerSocket、DatagramSocket
示例:
方式一:使用文件流来获取通道的方式来复制文件
@Test
public void testChannel() throws Exception{
FileInputStream fis =new FileInputStream("C:\\Users\\Administrator\\Desktop\\触发器.txt");
FileOutputStream fos =new FileOutputStream("C:\\Users\\Administrator\\Desktop\\触发器1.txt");
FileChannel fic =fis.getChannel();
FileChannel foc =fos.getChannel();
ByteBuffer buf =ByteBuffer.allocate(1024);
while(fic.read(buf) !=-1){
buf.flip();
foc.write(buf);
buf.clear();
}
foc.close();
fic.close();
fos.close();
fis.close();
}
结果:
方式二:将文件区域直接映射到内存中来创建
@Test
public void testChannelByOpen() throws Exception{
FileChannel inChannel =FileChannel.open(Paths.get("C:\\Users\\Administrator\\Desktop\\触发器.txt"), StandardOpenOption.READ);
FileChannel outChannel =FileChannel.open(Paths.get("C:\\Users\\Administrator\\Desktop\\触发器2.txt"),StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE);
//直接字节缓冲区,其内容是文件的内存映射区域:将此通道的文件区域直接映射到内存中。
MappedByteBuffer inMappedBuf =inChannel.map(MapMode.READ_ONLY, 0, inChannel.size());
MappedByteBuffer outMapperBuf =outChannel.map(MapMode.READ_WRITE, 0, inChannel.size());
//直接对缓冲区进行读写操作
byte[] dest =new byte[inMappedBuf.limit()];
inMappedBuf.get(dest);
outMapperBuf.put(dest);
outChannel.close();
inChannel.close();
}
结果:
4.字符编码与解码
示例:
@Test
public void testCharset() throws IOException{
Charset cs1 =Charset.forName("UTF-8");
//获取编码器
CharsetEncoder ce =cs1.newEncoder();
//获取解码器
CharsetDecoder cd =cs1.newDecoder();
CharBuffer cBuf =CharBuffer.allocate(1024);
cBuf.put("张无忌");
cBuf.flip();
//编码
ByteBuffer eBuf =ce.encode(cBuf);
while(eBuf.hasRemaining()){
System.out.println(eBuf.get());
}
//解码
eBuf.flip();
CharBuffer result =cd.decode(eBuf);
System.out.println(result.toString());
}
结果:
5.阻塞式网络NIO
客户端发送本地文件数据:
@Test
public void initClient() throws IOException{
//获取网络NIO的通道
SocketChannel socketChannel =SocketChannel.open(new InetSocketAddress("localhost", 8888));
//获取本地文件对应的文件通道
FileChannel inputChannel =FileChannel.open(Paths.get("C:\\Users\\Administrator\\Desktop\\1.jpg"), StandardOpenOption.READ);
//分配1024大小的缓冲区
ByteBuffer buf =ByteBuffer.allocate(1024);
//循环读取本地文件通道中的缓冲区中的数据,写入到网络通道缓冲区中
while(inputChannel.read(buf) !=-1){
buf.flip();
socketChannel.write(buf);
buf.clear();
}
socketChannel.shutdownOutput();
//发送成功提示
int len =0;
while((len =socketChannel.read(buf)) !=-1){
buf.flip();
System.out.println(new String(buf.array(),0,len));
buf.clear();
}
inputChannel.close();
socketChannel.close();
}
服务端接收数据:
@Test
public void initServer() throws IOException{
ServerSocketChannel ssChannel =ServerSocketChannel.open();
FileChannel outChannel =FileChannel.open(Paths.get("C:\\Users\\Administrator\\Desktop\\2.jpg"), StandardOpenOption.WRITE,StandardOpenOption.CREATE);
ssChannel.bind(new InetSocketAddress(8888));
SocketChannel socketChannel =ssChannel.accept();
ByteBuffer buf =ByteBuffer.allocate(1024);
while(socketChannel.read(buf) !=-1){
buf.flip();
outChannel.write(buf);
buf.clear();
}
buf.put("服务端接收客户端的数据成功".getBytes());
buf.flip();
socketChannel.write(buf);
socketChannel.close();
outChannel.close();
ssChannel.close();
}
结果:
6.非阻塞网络NIO
客户端发送数据:
@Test
public void initClient() throws IOException{
SocketChannel socketChannel =SocketChannel.open(new InetSocketAddress("localhost", 8989));
socketChannel.configureBlocking(false);
ByteBuffer buf =ByteBuffer.allocate(1024);
Scanner scanner =new Scanner(System.in);
while(scanner.hasNext()){
String content =scanner.next();
buf.put((new Date()+" : "+content).getBytes());
buf.flip();
socketChannel.write(buf);
buf.clear();
}
socketChannel.close();
}
服务端接收数据:
@Test
public void initServer() throws IOException{
ServerSocketChannel serverSocketChannel =ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.bind(new InetSocketAddress(8989));
Selector selector =Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while(selector.select() >0){
Iterator<SelectionKey> it =selector.selectedKeys().iterator();
while(it.hasNext()){
SelectionKey key =it.next();
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 buf =ByteBuffer.allocate(1024);
int len =0;
while((len =socketChannel.read(buf))>0){
buf.flip();
System.out.println(new String(buf.array(),0,len));
buf.clear();
}
}
it.remove();
}
}
}
结果:
7.使用UDP协议的网络NIO
服务端:
@Test
public void server() throws IOException{
DatagramChannel dc = DatagramChannel.open();
dc.configureBlocking(false);
dc.bind(new InetSocketAddress(9898));
Selector selector = Selector.open();
dc.register(selector, SelectionKey.OP_READ);
while(selector.select() > 0){
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
while(it.hasNext()){
SelectionKey sk = it.next();
if(sk.isReadable()){
ByteBuffer buf = ByteBuffer.allocate(1024);
dc.receive(buf);
buf.flip();
System.out.println(new String(buf.array(), 0, buf.limit()));
buf.clear();
}
}
it.remove();
}
}
客户端:
@Test
public void client() throws IOException{
DatagramChannel dc = DatagramChannel.open();
dc.configureBlocking(false);
ByteBuffer buf = ByteBuffer.allocate(1024);
Scanner scan = new Scanner(System.in);
while(scan.hasNext()){
String str = scan.next();
buf.put((new Date().toString() + " : " + str).getBytes());
buf.flip();
dc.send(buf, new InetSocketAddress("localhost", 9898));
buf.clear();
}
dc.close();
}
结果: