NIO从零开始

Java NIO 简介

Java NIO(New IO)是从Java 1.4版本开始引入的 一个新的IO API,可以替代标准的Java IO API。 NIO与原来的IO有同样的作用和目的,但是使用 的方式完全不同,NIO支持面向缓冲区的、基于 通道的IO操作。NIO将以更加高效的方式进行文 件的读写操作。

NIO与IO区别

总结:IO是面向对流的,而NIO是面向缓存区。IO是单向流,NIO是双向传输。

通道(Channel)和缓冲区(Buffer

 Java NIO系统的核心在于:通道(Channel)和缓冲区 (Buffer)。通道表示打开到 IO 设备(例如:文件、 套接字)的连接。若需要使用 NIO 系统,需要获取 用于连接 IO 设备的通道以及用于容纳数据的缓冲 区。然后操作缓冲区,对数据进行处理。

通道(Channel):连接IO设置的渠道。

缓存区(Buffer):数据寄存的地方,通道用来运输缓存区中的数据的。

缓冲区(Buffer)

Buffer 就像一个数组,可以保存多个相同类型的数据。根 据数据类型不同(boolean 除外) ,

有以下 Buffer 常用子类:

 ByteBuffer

 CharBuffer

 ShortBuffer

 IntBuffer

 LongBuffer

 FloatBuffer

 DoubleBuffer

上述 Buffer 类 他们都采用相似的方法进行管理数据,只是各自 管理的数据类型不同而已。都是通过如下方法获取一个 Buffer 对象:
static XxxBuffer allocate(int capacity) : 创建一个容量为capacity 的 XxxBuffer 对象

缓冲区的基本属性

Buffer 中的重要概念:

(1)容量 (capacity) :表示 Buffer 最大数据容量,缓冲区容量不能为负,并且创 建后不能更改。

(2) 限制 (limit):第一个不应该读取或写入的数据的索引,即位于 limit 后的数据 不可读写。缓冲区的限制不能为负,并且不能大于其容量。

(3) 位置 (position):下一个要读取或写入的数据的索引。缓冲区的位置不能为 负,并且不能大于其限制

(4) 标记 (mark)与重置 (reset):标记是一个索引,通过 Buffer 中的 mark() 方法 指定 Buffer 中一个特定的 position,之后可以通过调用 reset() 方法恢复到这 个 position.
(5)标记、位置、限制、容量遵守以下不变式: 0 <= mark <= position <= limit <= capacity


 

Buffer 的常用方法

缓冲区的数据操作

 Buffer 所有子类提供了两个用于数据操作的方法:get() put() 方法
(1)获取 Buffer 中的数据

get() :  读取单个字节

get(byte[] dst):批量读取多个字节到 dst 中

get(int index):读取指定索引位置的字节(不会移动 position)
(2)放入数据到 Buffer 中

put(byte b): 将给定单个字节写入缓冲区的当前位置

put(byte[] src):将 src 中的字节写入缓冲区的当前位置

put(int index, byte b):将指定 字 节写入缓冲区的索引位置(不会移动 position) 

package com.ldd.nio;

import java.nio.ByteBuffer;

import org.junit.Test;

public class NioTest {

	
	@Test
	public void tesBuffer(){
		
		//创建一个字节缓冲区,capacity 为10
		ByteBuffer buffer = ByteBuffer.allocate(10);
		
		//输出刚创建的byteBuffer容量 (capacity)限制 (limit)位置 (position)
		System.out.println("-------------NEW--------------");
		System.out.println("capacity==="+buffer.capacity()); //10
		System.out.println("limit==="+buffer.limit());  //10
		System.out.println("position==="+buffer.position()); //0
		
		//put数据
		String str = "Hello Nio";
		buffer.put(str.getBytes());
		System.out.println("--------------Put-------------");
		System.out.println("capacity==="+buffer.capacity()); //10
		System.out.println("limit==="+buffer.limit());  //10
		System.out.println("position==="+buffer.position()); //9
		
		//切换到读数据
		buffer.flip();
		System.out.println("-------------Flip-------------");
		System.out.println("capacity==="+buffer.capacity()); //10
		System.out.println("limit==="+buffer.limit());  //9
		System.out.println("position==="+buffer.position()); //0
		
		
		//读取数据
		byte[] b = new byte[5];
		System.out.println("------------get----------");
		buffer.get(b); //读取5个字节大小 存储在b中
		System.out.println(new String(b,0,b.length));
		System.out.println("capacity==="+buffer.capacity()); //10
		System.out.println("limit==="+buffer.limit());  //9
		System.out.println("position==="+buffer.position()); //5
		
		//mark 记住position位置
		System.out.println("---------------mark--------------");
		byte[] bb = new byte[4];
		buffer.mark();
		buffer.get(bb);
		System.out.println(new String(b,0,b.length));
		System.out.println("capacity==="+buffer.capacity()); //10
		System.out.println("limit==="+buffer.limit());  //9
		System.out.println("position==="+buffer.position()); //9
		
		//reset 恢复position上级记录的位置
		buffer.reset();
		System.out.println();
		System.out.println("capacity==="+buffer.capacity()); //10
		System.out.println("limit==="+buffer.limit());  //9
		System.out.println("position==="+buffer.position()); //5
		
		
 		
		
	}
	
}

直接缓冲区与非直接缓冲区

1)非直接缓冲区通过allocate()方法分配缓冲区,将缓冲区建立在JVM的内存中

(2)直接缓冲区

                2-1:通过allocateDirect()方法分配直接缓冲区,将缓冲区建立在操作系统的物理内存中

                2-2:通过FileChannel 的 map() 方法 将文件区域直接映射到内存中来创建

(3) isDirect() 方法可以判断当前缓冲区是否是直接缓冲区。

非直接缓冲区

直接缓冲区

(1)直接字节缓冲区可以通过调用此类的 allocateDirect() 工厂方法来创建。此方法返回的缓冲区进行分配和取消 分配所需成本通常高于非直接缓冲区。直接缓冲区的内容可以驻留在常规的垃圾回收堆之外,因此,它们对 应用程序的内存需求量造成的影响可能并不明显。所以,建议将直接缓冲区主要分配给那些易受基础系统的 本机 I/O 操作影响的大型、持久的缓冲区。一般情况下,最好仅在直接缓冲区能在程序性能方面带来明显好 处时分配它们。

(2)直接字节缓冲区还可以通过FileChannel 的 map() 方法 将文件区域直接映射到内存中来创建。方法返回MappedByteBuffer 。Java 平台的实现有助于通过 JNI 从本机代码创建直接字节缓冲区。如果以上这些缓冲区 中的某个缓冲区实例指的是不可访问的内存区域,则试图访问该区域不会更改该缓冲区的内容,并且将会在 访问期间或稍后的某个时间导致抛出不确定的异常。

//直接缓冲区
	@Test
	public void testBuffer02(){
		
		//创建一个10长度的直接缓冲区
		ByteBuffer buffer = ByteBuffer.allocateDirect(10);
		//判断是否是直接缓冲区
		System.out.println(buffer.isDirect());
	}

 

通道(Channel)

通道(Channel):由 java.nio.channels 包定义 的。Channel 表示 IO 源与目标打开的连接。 Channel 类似于传统的“流”。只不过 Channel 本身不能直接访问数据,Channel 只能与 Buffer 进行交互。

Java 为 Channel 接口提供的最主要实现类如下:

(1)FileChannel:用于读取、写入、映射和操作本地文件的通道。

(2)DatagramChannel:通过 UDP 读写网络中的数据通道。

(3)SocketChannel:通过 TCP 读写网络中的数据。

(4)ServerSocketChannel:可以监听新进来的 TCP 连接,对每一个新进来的连接都会创建一个 SocketChannel。

getChannel() 方法。支持通道的类如下:
(1) FileInputStream 

(2)FileOutputStream 

(3)RandomAccessFile 

(4)DatagramSocket 

(5)Socket 

(6)ServerSocket

注意:在JDK1.7以后,获取通道的其他方式是使用 Files 类的静态方法 newByteChannel() 获取字节通道。或者通过通道的静态方法 open() 打开并返回指定通道。

package com.ldd.nio;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

import org.junit.Test;

public class TestChannel {

	// 通道与通道之间的传输( transferFrom、transferTo 是直接缓冲区)
	@Test
	public void testThree02() throws Exception {

		FileInputStream fIn = new FileInputStream("C:\\nio\\zfy.jpg");
		FileOutputStream fon = new FileOutputStream("C:\\nio\\zfy5.jpg");

		FileChannel inChannel = fIn.getChannel();
		FileChannel ouChannel = fon.getChannel();

		// 将文件复制到哪里去 transferTo
		inChannel.transferTo(0, inChannel.size(), ouChannel);
		// 文件从哪里复制transferFrom
		// transferTo transferFrom 效果是一样的
		// ouChannel.transferFrom(inChannel, 0, inChannel.size());

		ouChannel.close();
		inChannel.close();
	}

	// 通道与通道之间的传输(transferFrom、transferTo 直接缓冲区)
	@Test
	public void testThree() throws Exception {
		FileChannel inChannel = FileChannel.open(Paths.get("C:\\nio\\zfy.jpg"), StandardOpenOption.READ);
		FileChannel ouChannel = FileChannel.open(Paths.get("C:\\nio\\zfy4.jpg"), StandardOpenOption.READ,
				StandardOpenOption.WRITE, StandardOpenOption.CREATE);

		// 将文件复制到哪里去 transferTo
		inChannel.transferTo(0, inChannel.size(), ouChannel);
		// 文件从哪里复制transferFrom
		// transferTo transferFrom 效果是一样的
		// ouChannel.transferFrom(inChannel, 0, inChannel.size());

		ouChannel.close();
		inChannel.close();
	}

	// 直接缓冲区复制文件
	@Test
	public void testTwo() throws Exception {
		// 通过JDK1.7 FileChannel的静态方法获取
		// StandardOpenOption.READ 设置只读模式
		// StandardOpenOption.CREATE 若要输出的文件不存在 创建 存在覆盖
		FileChannel inChannel = FileChannel.open(Paths.get("C:\\nio\\zfy.jpg"), StandardOpenOption.READ);
		FileChannel ouChannel = FileChannel.open(Paths.get("C:\\nio\\zfy3.jpg"), StandardOpenOption.READ,
				StandardOpenOption.WRITE, StandardOpenOption.CREATE);

		// 通过通道将文件读取到物理内存映射文件中
		MappedByteBuffer inBuffer = inChannel.map(MapMode.READ_ONLY, 0, inChannel.size());
		// 通过通道将文件写入到物理内存映射文件中
		MappedByteBuffer ouBuffer = ouChannel.map(MapMode.READ_WRITE, 0, inChannel.size());

		// 直接读取直接缓冲区数据
		byte[] dst = new byte[inBuffer.limit()];
		inBuffer.get(dst);
		// 直接写入直接缓冲区数据
		ouBuffer.put(dst);

		ouChannel.close();
		inChannel.close();
	}

	// 利用通道完成文件的复制(非直接缓冲区)
	@Test
	public void testOne() {

		FileInputStream fIn = null;
		FileOutputStream fon = null;
		FileChannel inChannel = null;
		FileChannel ouChannel = null;
		try {
			fIn = new FileInputStream("C:\\nio\\zfy.jpg");
			fon = new FileOutputStream("C:\\nio\\zfy2.jpg");

			// 1获取通道
			inChannel = fIn.getChannel();
			ouChannel = fon.getChannel();

			// 2建立缓冲区
			ByteBuffer buffer = ByteBuffer.allocate(1024);

			// 3将读取通道中的数据存储到缓冲区中
			while (inChannel.read(buffer) != -1) {
				// 将缓冲区设置为读取模式 这样position位置 就从0开始读取了
				buffer.flip();
				// 3将缓冲区中的数据写入onChannel通道
				ouChannel.write(buffer);
				// 清空缓冲区
				buffer.clear();
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (ouChannel != null) {
				try {
					ouChannel.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}

			if (inChannel != null) {
				try {
					inChannel.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}

			if (fon != null) {
				try {
					fon.close();
				} catch (IOException e) {

					e.printStackTrace();
				}
			}

			if (fIn != null) {
				try {
					fIn.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}

		}
	}

}

 

分散(Scatter)聚集(Gather)

(1)分散读取:从管道(Channel)中读取的数据“分散"到多个Buffer中。

注意:按照缓冲区的顺序,从 Channel 中读取的数据依次将Buffer 填满。

(2)聚集写入:是指将多个Bufer中的数据“聚集”到Channel。

注意:按照缓冲区的顺序,写入position和limit之间的数据到Channel.

    //分散(Scatter) 聚集(Gather)
	//分散 就是通道里面存在多个缓存区 数据依次存入缓存区中
	//聚集 将通道中多个缓冲区依次写出到存储区(内存或硬盘 就是写操作)
	//分散 聚集无法就是操作buffer数组
	@Test
	public void onScatterGather() throws Exception{
		
		//使用RandomAccessFile  获取通道 Channel  rw读写
		RandomAccessFile inFile = new RandomAccessFile("C:\\nio\\scatter.txt", "rw");
		//读取通道
		FileChannel channel = inFile.getChannel();
		
		//创建缓冲区
		ByteBuffer[] buffers = new ByteBuffer[3];
		for(int i=0;i<buffers.length;i++){
			ByteBuffer allocate = ByteBuffer.allocate(100);
			buffers[i] = allocate;
		}
		//读入数据
		channel.read(buffers);
		
		//将通过切换flip()这样就可以从通道中往出写数据了
		for(int i=0;i<buffers.length;i++){
			buffers[i].flip();
		}
		
		//写通道
		RandomAccessFile outFile = new RandomAccessFile("C:\\nio\\Gather.txt", "rw");
		FileChannel outChannel = outFile.getChannel();
		outChannel.write(buffers);
	}
	

阻塞式网络通信

传统的 IO 流都是阻塞式的。也就是说,当一个线程调用 read() 或 write() 时,该线程被阻塞,直到有一些数据被读取或写入,该线程在此期间不 能执行其他任务。

Java NIO 是非阻塞模式的。当线程从某通道进行读写数据时,若没有数 据可用时,该线程可以进行其他任务。

    /**
      * 阻塞式网络通信
      */ 
     
	//socketChannel客户端
	@Test
	public void socketClient() throws Exception{
		
		//1 获取客户端发送通道
		SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8686));
		
		//读取本地文件
		FileChannel fileChannel = FileChannel.open(Paths.get("C:\\nio\\zfy.jpg"), StandardOpenOption.READ);
		 
		//定义缓存区
		ByteBuffer allocate = ByteBuffer.allocate(1024);
		
		while(fileChannel.read(allocate) != -1){
			allocate.flip();
			socketChannel.write(allocate);
			allocate.clear();
		}
		fileChannel.close();
		socketChannel.close();
	}
	
	
	//服务器
	@Test
	public void socketService() throws Exception{
		
		//定义服务器接收
		ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
		
	    //绑定端口号
		serverSocketChannel.bind(new InetSocketAddress(8686));
		
		//获取客户端连接通道
		SocketChannel socketChannel = serverSocketChannel.accept();
	
	    //获取输出文件到本地通道
		FileChannel fileChannel = FileChannel.open(Paths.get("C:\\nio\\zfysocket.jpg"), StandardOpenOption.WRITE,StandardOpenOption.CREATE);
	    
		//定义保存缓存区
		ByteBuffer allocate = ByteBuffer.allocate(1024);
	    
		while(socketChannel.read(allocate) != -1){
			allocate.flip();
			fileChannel.write(allocate);
			allocate.clear();
		}
		
		fileChannel.close();
		socketChannel.close();
		serverSocketChannel.close();
	}


 

/**
	 * 非阻塞式
	 * @throws IOException
	 */
	@Test
	public void client() throws IOException{
		//1. 获取通道
		SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8686));
		//2. 切换非阻塞模式
		sChannel.configureBlocking(false);
		//3. 分配指定大小的缓冲区
		ByteBuffer buf = ByteBuffer.allocate(1024);
		//4. 发送数据给服务端
		Scanner scan = new Scanner(System.in);
		while(scan.hasNext()){
			String str = scan.next();
			buf.put(str.getBytes());
			buf.flip();
			sChannel.write(buf);
			buf.clear();
		}
		//5. 关闭通道
		sChannel.close();
	}
	
	
	//服务端
	@Test
	public void server() throws IOException{
		//1. 获取通道
		ServerSocketChannel ssChannel = ServerSocketChannel.open();
		//2. 切换非阻塞模式
		ssChannel.configureBlocking(false);
		//3. 绑定连接
		ssChannel.bind(new InetSocketAddress(8686));
		//4. 获取选择器
		Selector selector = Selector.open();
		/**
		 * 选择器(Selector) 是 SelectableChannle 对象的多路复用器,
		 * Selector 可 以同时监控多个 SelectableChannel 的 IO 状况,
		 * 也就是说,利用 Selector 可使一个单独的线程管理多个 Channel。
		 * Selector 是非阻塞 IO 的核心。
		 * 读 : SelectionKey.OP_READ  
		 * 写 : SelectionKey.OP_WRITE  
                 连接 : SelectionKey.OP_CONNECT
                 接收 : SelectionKey.OP_ACCEPT 
		 */
		//5. 将通道注册到选择器上, 并且指定“监听接收事件”
		ssChannel.register(selector, SelectionKey.OP_ACCEPT);
		//6. 轮询式的获取选择器上已经“准备就绪”的事件
		while(selector.select() > 0){
			//7. 获取当前选择器中所有注册的“选择键(已就绪的监听事件)”
			Iterator<SelectionKey> it = selector.selectedKeys().iterator();
			while(it.hasNext()){
				//8. 获取准备“就绪”的是事件
				SelectionKey sk = it.next();
				//9. 判断具体是什么事件准备就绪
				if(sk.isAcceptable()){
					//10. 若“接收就绪”,获取客户端连接
					SocketChannel sChannel = ssChannel.accept();
					//11. 切换非阻塞模式
					sChannel.configureBlocking(false);
					//12. 将该通道注册到选择器上
					sChannel.register(selector, SelectionKey.OP_READ);
				}else if(sk.isReadable()){
					//13. 获取当前选择器上“读就绪”状态的通道
					SocketChannel sChannel = (SocketChannel) sk.channel();
					//14. 读取数据
					ByteBuffer buf = ByteBuffer.allocate(1024);
					int len = 0;
					while((len = sChannel.read(buf)) > 0 ){
						buf.flip();
						System.out.println(new String(buf.array(), 0, len));
						buf.clear();
					}
				}
				//15. 取消选择键 SelectionKey
				it.remove();
			}
		}
	}

管道(Pipe)

Java NIO 管道是2个线程之间的单向数据连接。 Pipe有一个source通道和一个sink通道。数据会 被写到sink通道,从source通道读取。

	/**
     *  管道demo
     */
    @Test
	public void pipeTest() throws Exception{
		//1 获取管道
		Pipe open = Pipe.open();
		//定义缓冲区
		ByteBuffer allocate = ByteBuffer.allocate(1024);
		
		Pipe.SinkChannel sinkChannel = open.sink();
		
	    allocate.put("管道pipeDemo".getBytes());
	    allocate.flip();
	    //定义sink发送数据
	    sinkChannel.write(allocate);
	    
	    
	    //读取sink中的数据
	    Pipe.SourceChannel sourceChannel = open.source();
	    allocate.flip();
	    int read = sourceChannel.read(allocate);
	    System.out.println(new String(allocate.array(),0,read));
	    
	    sourceChannel.close();
	    sinkChannel.close();
	}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值