NIO

2019-10-15 10:40:16 浏览数 (1)

详细知识参考我有道云笔记

代码语言:javascript复制
package com.shi.nio;

import java.nio.ByteBuffer;

/**
 * 
 * @author shiye
 *	一、缓冲区(Buffer):在 Java NIO 中负责数据的存取。缓冲区就是数组。用于存储不同数据类型的数据
 * 
 *	 根据数据类型不同(boolean 除外),提供了相应类型的缓冲区:
 * ByteBuffer
 * CharBuffer
 * ShortBuffer
 * IntBuffer
 * LongBuffer
 * FloatBuffer
 * DoubleBuffer
 * 
 * ·上述缓冲区的管理方式几乎一致,通过 allocate() 获取缓冲区
 * 
 *	 二、缓冲区存取数据的两个核心方法:
 * put() : 存入数据到缓冲区中
 * get() : 获取缓冲区中的数据
 * 
 * 	三、缓冲区中的四个核心属性:
 * capacity : 容量,表示缓冲区中最大存储数据的容量。一旦声明不能改变。
 * limit : 界限,表示缓冲区中可以操作数据的大小。(limit 后数据不能进行读写)
 * position : 位置,表示缓冲区中正在操作数据的位置。
 * 
 * mark : 标记,表示记录当前 position 的位置。可以通过 reset() 恢复到 mark 的位置
 * 
 * 0 <= mark <= position <= limit <= capacity
 * 
 * 	四、直接缓冲区与非直接缓冲区:
 * 	非直接缓冲区:通过 allocate() 方法分配缓冲区,将缓冲区建立在 JVM 的内存中
 * 	直接缓冲区:通过 allocateDirect() 方法分配直接缓冲区,将缓冲区建立在物理内存中。可以提高效率
 */
public class TestBuffer {

	public static void main(String[] args) {
		String str = "shiye";
		//1 分配一个指定大小的缓冲区
		ByteBuffer buffer = ByteBuffer.allocate(1024); 
//		ByteBuffer.allocateDirect(10);//直接内存缓冲区
//		buffer.isDirect();//是否是直接内存缓冲区
		
		System.out.println("-------------------1 allocate(初始创建)-----------");
		System.out.println("容量:" buffer.capacity());
		System.out.println("界限:" buffer.limit());
		System.out.println("当前位置:" buffer.position());
		
		buffer.put(str.getBytes());
		
		System.out.println("-------------------2 put(写数据)-----------");
		System.out.println("容量:" buffer.capacity());
		System.out.println("界限:" buffer.limit());
		System.out.println("当前位置:" buffer.position());
		
		buffer.flip();
		System.out.println("-------------------3 flip(切换到读取数据模式)-----------");
		System.out.println("容量:" buffer.capacity());
		System.out.println("界限:" buffer.limit());
		System.out.println("当前位置:" buffer.position());
		
		
		System.out.println("-------------------4 get(读取数据)-----------");
		
		byte[] dst = new byte[10];
		buffer.get(dst, 0, 2);//从下标0,读取2个数据 道dst中
		System.out.println("dst中读到的数据位:" new String(dst));
		
		System.out.println("容量:" buffer.capacity());
		System.out.println("界限:" buffer.limit());
		System.out.println("当前位置:" buffer.position());
		
		buffer.rewind();
		System.out.println("-------------------5 rewind(重新读数据)-----------");
		System.out.println("容量:" buffer.capacity());
		System.out.println("界限:" buffer.limit());
		System.out.println("当前位置:" buffer.position());
		
		System.out.println("buffer中读到的数据位:" (char)buffer.get());//rewind 测试
		
		buffer.clear();
		System.out.println("-------------------6 clear(清除位置信息,数据还存在)-----------");
		System.out.println("容量:" buffer.capacity());
		System.out.println("界限:" buffer.limit());
		System.out.println("当前位置:" buffer.position());
		
		System.out.println("buffer中读到的数据位:" (char)buffer.get());//clear 测试
		
		
		System.out.println("-------------------7 mark(标记当前位置)-----------");
		System.out.println("当前位置:" buffer.position());
		buffer.mark();//mark 标记当前位置
		System.out.println("buffer中读到的数据位:" (char)buffer.get());//mark 测试
		System.out.println("当前位置:" buffer.position());
		
		buffer.reset();//返回到mark标记好的位置 重新读取数据
		System.out.println("reset当前位置:" buffer.position());//reset 测试
		
		
		if(buffer.hasRemaining()) {
			System.out.println("当前缓冲区中剩余可以操作的的数据的数量" buffer.remaining());
		}
	}

	/**
	运行结果:
	-------------------1 allocate(初始创建)-----------
	容量:1024
	界限:1024
	当前位置:0
	-------------------2 put(写数据)-----------
	容量:1024
	界限:1024
	当前位置:5
	-------------------3 flip(切换到读取数据模式)-----------
	容量:1024
	界限:5
	当前位置:0
	-------------------4 get(读取数据)-----------
	dst中读到的数据位:sh
	容量:1024
	界限:5
	当前位置:2
	-------------------5 rewind(重新读数据)-----------
	容量:1024
	界限:5
	当前位置:0
	buffer中读到的数据位:s
	-------------------6 clear(清除位置信息,数据还存在)-----------
	容量:1024
	界限:1024
	当前位置:0
	buffer中读到的数据位:s
	-------------------7 mark(标记当前位置)-----------
	当前位置:1
	buffer中读到的数据位:h
	当前位置:2
	reset当前位置:1
	当前缓冲区中剩余可以操作的的数据的数量1023
	 */
}
代码语言:javascript复制
package com.shi.nio;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.LinkedList;
import java.util.List;

/**
 * 
 * @author shiye 一、通道(Channel):用于源节点与目标节点的连接。在 Java NIO 中负责缓冲区中数据的传输。Channel
 *         本身不存储数据,因此需要配合缓冲区进行传输。
 * 
 *         二、通道的主要实现类 java.nio.channels.Channel 接口: |--FileChannel
 *         |--SocketChannel |--ServerSocketChannel |--DatagramChannel
 * 
 *         三、获取通道 1. Java 针对支持通道的类提供了 getChannel() 方法 本地 IO:
 *         FileInputStream/FileOutputStream RandomAccessFile
 * 
 *         网络IO: Socket ServerSocket DatagramSocket
 * 
 *         2. 在 JDK 1.7 中的 NIO.2 针对各个通道提供了静态方法 open() 3. 在 JDK 1.7 中的 NIO.2 的
 *         Files 工具类的 newByteChannel()
 * 
 *         四、通道之间的数据传输 transferFrom() transferTo()
 * 
 *         五、分散(Scatter)与聚集(Gather) 分散读取(Scattering Reads):将通道中的数据分散到多个缓冲区中
 *         聚集写入(Gathering Writes):将多个缓冲区中的数据聚集到通道中
 * 
 *         六、字符集:Charset 编码:字符串 -> 字节数组 解码:字节数组 -> 字符串
 */
public class TestChannel {

	/**
	 * 
	 * @param args
	 * @throws IOException
	 */
	public static void main(String[] args) throws IOException {
		test4();
		
	}
	
	/**
	 * 	编码和解码
	 * @throws CharacterCodingException 
	 */
	public static void test4() throws CharacterCodingException {
		
		//获取UTF-8的字符集
		Charset charset = Charset.forName("UTF-8");
		
		//获取编码器
		CharsetEncoder utf8_encoder = charset.newEncoder();
		
		//获取解码器
		CharsetDecoder utf8_decoder = charset.newDecoder();
		
		CharBuffer charBuffer = CharBuffer.allocate(1024);
		charBuffer.put("还是施爷足智多谋!");
		charBuffer.flip();
		
		//1 编码 CharBuffer -> ByteBuffer 字符流->字节流
		ByteBuffer byteBuffer = utf8_encoder.encode(charBuffer);
		for(int i = 0;i<byteBuffer.limit();i  ) {
			System.out.println(byteBuffer.get());
		}
		
		
		//2 解码 ByteBuffer-> CharBuffer  字节流->字符流
		byteBuffer.flip();
		CharBuffer charBuffer2 = utf8_decoder.decode(byteBuffer);
		System.out.println(charBuffer2.toString());
		
		
		//所有支持的字符集
//		SortedMap<String, Charset> charsets = Charset.availableCharsets();
//		charsets.forEach((key,value)->{
//			System.out.println( key  "   " value);
//		});
	}
	
	

	/**
	 * 	分散(Scatter)与聚集(Gather) 分散读取(Scattering Reads):将通道中的数据分散到多个缓冲区中 聚集写入(Gathering
	 * Writes):将多个缓冲区中的数据聚集到通道中
	 * 
	 * @throws IOException
	 * 
	 * 	注意文件不能超过内存的大小
	 * Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at java.nio.HeapByteBuffer.<init>(Unknown Source)
	 * 
	 * 	结果:
	 * 	总耗时:7065
	 */
	public static void test3() throws IOException {
		long time1 = System.currentTimeMillis();
		

		// 1 读取文件 获取通道
		RandomAccessFile raf1 = new RandomAccessFile("d:/nio.zip", "rw");
		FileChannel inChannel = raf1.getChannel();

		// 2 计算总共需要多少个缓冲区
		int size = 1024 * 1024 * 100;// 100M大小
		int index = (int) ((raf1.length() - 1) / size   1);// 文件超出 Integer.MAX_VALUE 需要分开缓存
		List<ByteBuffer> bufs = new LinkedList<ByteBuffer>();
		for (int i = 0; i < index; i  ) {
			if (i != index - 1) {
				ByteBuffer team = ByteBuffer.allocate(size);
				bufs.add(team);
			} else {
//				System.out.println((raf1.length() - (size * (index - 1))) / 1024 / 1024   "M");
				ByteBuffer team = ByteBuffer.allocate((int) (raf1.length() - (size * (index - 1))));
				bufs.add(team);
			}

		}
		
		//3 分散读取
		ByteBuffer[] byteArray = bufs.toArray(new ByteBuffer[index]);
		inChannel.read(byteArray);
		
		for(ByteBuffer byteBuffer:byteArray) {
			byteBuffer.flip();//变成读模式
		}
		
		//4 聚焦写入
		RandomAccessFile raf2 = new RandomAccessFile("d:/test.zip","rw");
		FileChannel outChanel = raf2.getChannel();
		
		outChanel.write(byteArray);
		
		//5 关闭文件
		inChannel.close();
		outChanel.close();
		
		long time2 = System.currentTimeMillis();
		System.out.println("总耗时:"   (time2 - time1));
	}

	/**
	 * transferFrom() transferTo()
	 * 
	 * @throws IOException 注意文件只能有1.98G 太大文件会丢失 结果: 总耗时:2349
	 */
	public static void test2() throws IOException {
		long time1 = System.currentTimeMillis();

		FileChannel inChannel = FileChannel.open(Paths.get("d:/"   "big.zip"), StandardOpenOption.READ);
		FileChannel outChannel = FileChannel.open(Paths.get("d:/"   "2.zip"), StandardOpenOption.READ,
				StandardOpenOption.WRITE, StandardOpenOption.CREATE);

		inChannel.transferTo(0, inChannel.size(), outChannel);

		long time2 = System.currentTimeMillis();
		System.out.println("总耗时:"   (time2 - time1));

		inChannel.close();
		outChannel.close();
	}

	/**
	 * 使用直接缓冲区完成文件的复制(内存映射文件)
	 * 
	 * @throws IOException
	 * 
	 *                     结果: 读取文件耗时:1264 写文件耗时:14845
	 */
	public static void test1() throws IOException {
		long time1 = System.currentTimeMillis();
		// 1 读文件内存
		FileChannel inChannel = FileChannel.open(Paths.get("d:/"   "nio.zip"), StandardOpenOption.READ);
		MappedByteBuffer inMappedBuffer = inChannel.map(MapMode.READ_ONLY, 0, inChannel.size());// inChannel.size()
																								// 不能大于Integer.MAX_VALUE
		byte[] dst = new byte[inMappedBuffer.limit()];
		inMappedBuffer.get(dst);

		long time2 = System.currentTimeMillis();
		System.out.println("读取文件耗时:"   (time2 - time1));

		// 2 写文件到磁盘
		FileChannel outChannel = FileChannel.open(Paths.get("d:/"   "2.zip"), StandardOpenOption.READ,
				StandardOpenOption.WRITE, StandardOpenOption.CREATE);
		MappedByteBuffer outMappedBuffer = outChannel.map(MapMode.READ_WRITE, 0, inChannel.size());
		outMappedBuffer.put(dst);// 直接对缓冲区进行数据的读写

		long time3 = System.currentTimeMillis();
		System.out.println("写文件耗时:"   (time3 - time2));
		inChannel.close();
		outChannel.close();
	}

}
代码语言:javascript复制
package com.shi.nio;

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.time.LocalDateTime;
import java.util.Scanner;
import java.util.Set;

import org.junit.Test;

/*
 *  一。 使用NIO完成网络通讯的三个核心
 *  1.通道 (Channel):负责连接
 *  	java.nio.channels.Channel接口
 *  		|--SocketChannel
 *  		|--ServerSocketChannel
 *  		|--DatagramChannel
 *  
 *  		|--Pipe.SinkChannel
 *  		|--Pipe.SourceChannel
 *  
 *  2.缓冲区(buffer):负责数据的存储
 *  
 *  3.选择器(Selector):是SelectableChannel 的多路复用器.用于监控SeclectableChannel 的IO状况
 *  
 *  
 */
public class TestNonBlockingNIO {
	
	//客户端
	@Test
	public void client() throws IOException {
		//1.获取通道
		SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",9898));
		
		//2.切换非阻塞模式
		sChannel.configureBlocking(false);
		
		//3.分配指定大小的缓冲区
		ByteBuffer byteBuffer = ByteBuffer.allocate(1*1024);
		
		//4.发送给服务器端数据
		Scanner scan = new Scanner(System.in);
		while(scan.hasNext()) {
			byteBuffer.put((LocalDateTime.now().toString() "t" scan.next()).getBytes());
			byteBuffer.flip();//切换到读模式
			sChannel.write(byteBuffer);
			byteBuffer.clear();
		}
		
		
		//5.关闭通道
		sChannel.close();
	}
	
	//服务器端
	@Test
	public void server() throws Exception {
		//1.获取通道
		ServerSocketChannel ssChannel = ServerSocketChannel.open();
		
		//2.切换非阻塞模式
		ssChannel.configureBlocking(false);
		
		//3.绑定连接
		ssChannel.bind(new InetSocketAddress(9898));
		
		//4.获取选择器
		Selector selector = Selector.open();
		
		//5.将通道注册到选择器上,并且指定“监听接收事件”
		ssChannel.register(selector, SelectionKey.OP_ACCEPT);
		
		
		//6.轮询式的获取选择器上已经“准备就绪”的事件
		while(selector.select()>0) {
			
			//7.获取当前选择器中所有注册的“选择键(已经就绪的监听事件)”
			Set<SelectionKey> selectedKeys = selector.selectedKeys();
			
			//8.获取准备“就绪”的事件
			selectedKeys.forEach(selectionKey->{
				
				if(selectionKey.isAcceptable()) {
					try {
						//9."接收就绪"状态事件
						SocketChannel sChannel = ssChannel.accept();
						
						//10.切换非阻塞模式
						sChannel.configureBlocking(false);
						
						//11.将通道注册到选择器上
						sChannel.register(selector, SelectionKey.OP_READ);
						
					} catch (IOException e) {
						e.printStackTrace();
					}
				}else if(selectionKey.isReadable()) {
					//获取选择器上“读就绪”状态的通道
					SocketChannel sChannel = (SocketChannel) selectionKey.channel();
					
					//读取数据
					ByteBuffer buf = ByteBuffer.allocate(1*1024);
					
					int len = 0;
					try {
						while((len = sChannel.read(buf)) > 0) {
							buf.flip();//变成只读模式
							System.out.println(new String(buf.array(),0,len));
							buf.clear();
						}
					} catch (IOException e) {
						e.printStackTrace();
					}
					
					try {
						sChannel.close();
					} catch (IOException e) {
						e.printStackTrace();
					}
					
				}else if(selectionKey.isConnectable()) {
					System.out.println("连接状态的通道自己处理....");
				}else if(selectionKey.isValid()) {
					System.out.println("验证状态的通道自己处理....");
				}else if(selectionKey.isWritable()) {
					System.out.println("写状态的通道自己处理....");
				}
			});
			
			ssChannel.close();
		}
	}
}

0 人点赞