NIO——笔记一(相关概念以及代码)

笔记:
JVM 自身在 I/O方面效率欠佳。操作系统与 Java 基于流的 I/O 模型有些不匹配。操作系统要移动的是大块数据(缓冲区),这往往是在硬件直接存储器存取(DMA)的协助下完成的。而 JVM 的 I/O 类喜欢操作小块数据——单个字节、几行文本。结果操作系统送来了整个缓冲区数据,java.io 的流数据类再花大量时间把它们拆成小块,往往拷贝一个小块就要往返于几层对象。操作系统喜欢整卡车地运来数据,java.io 类则喜欢一铲子一铲子地加工数据。有了 NIO,就可以轻松地把一卡车数据备份到你能直接使用的地方(ByteBuffer)
在这里插入图片描述
采用分页计数的操作系统执行 I/O的全过程可总结为以下几步:
① 确定请求的数据分布在文件系统的哪些页(磁盘扇区组)。磁盘上的文件内容和元素据可能跨越多个文件系统页,而且这些页可能也不连续
② 在内核空间分配足够数量的内存页,以容纳得到确定的文件系统页
③ 在内存页与磁盘上的文件系统页之间建立映射
④ 为每一个内存页产生页错误
⑤ 虚拟内存系统俘获页错误,安排页面调入,从磁盘读取页内容,使页有效
⑥ 一旦页面调入操作完成,文件系统即对原始数据进行解析,获取文件内容或属性信息
需要注意的是,这些文件系统数据也会同其他内存页一样得到高速缓存。对于随后发生的I/O 请求,文件数据的部分或全部可能仍旧位于物理内存当中,无需再从磁盘读取即可重复使用。

内存映射文件
传统的文件I/O 是通过用户进程发布 read() 和 write() 系统调用来传输数据的。为了在内核空间的文件系统页与用户空间的内存区之间移动数据,一次以上的拷贝操作几乎总是免不了的。这是因为,在文件系统页与用户换从之间往往没有一一对应关系。但是,还有一种大多数操作系统都支持的特殊类型的 I/O 操作,允许用户进程最大限度地利用面向页的系统 I/O 特性,并完全摒弃缓冲区拷贝,这就是内存映射 I/O,如图:
在这里插入图片描述
内存映射 I/O 使用文件系统建立从用户空间直接到文件系统页的虚拟内存映射。这样做有几个好处:
① 用户进程把文件数据当作内存,所以无需发布 read() 或 write() 系统调用
② 当用户进程碰触到映射内存空间,页错误会自动产生,从而将文件数据从磁盘读进内存。如果用户修改了映射内存空间,相关页会自动标记为脏,随后刷新到磁盘,文件得到更新。
③ 操作系统的虚拟内存子系统会对页进行智能高速缓存,自动根据系统负载进行内存管理
④ 数据总是按页对齐的,无需执行缓冲区拷贝
⑤ 大型文件使用映射,无需耗费大量内存,即可进行数据拷贝
虚拟内存和磁盘 I/O 是紧密相连的,从很多方面看来,它们只是同一件事务的两面,在处理大量数据时,尤其要记得这一点。如果数据缓冲区是按页对齐的,且大小是内建页大小的倍数,那么,对于大多数操作系统而言,其处理效率会大幅度提升。

文件锁定

文件锁定机制允许一个进程阻止其他进程存取某个文件,或限制其存取方式。通常的用途是控制共享信息的更新方式,或用于事务隔离。在控制多个实体并行访问共同资源方面,文件锁定是必不可少的。数据库等复杂应用严重依赖于文件锁定。

“文件锁定” 从字面上看有锁定整个文件的意思(通常的确是这样),但锁定往往可以发生在更为细微的层面,锁定区域往往可以细致到单个字节。锁定与特定文件相关,开始于文件的某个特定字节地址,包含特定数量的连续字节。这对于协调多个进程互补影响地访问不同区域,是至关重要的。

文件锁定有两种方式:共享和独占。多个共享锁可同时对同一文件文件区域发生作用;独占锁则不同,它要求相关区域不能有其他锁定在其作用。

共享锁和独占锁的经典应用,是控制最初用于读取的共享文件的更新。某个进程要读取文件,会先取得该文件或该文件部分区域的共享锁。第二个希望读取文件区域的进程也会请求共享锁。两个进程可以并行读取,互不影响。但是,假如有第三个进程要更新该文件,它会请求独占锁。该进程会处于阻滞状态,直到既有锁定(共享的、独占的)全部解除。一旦给予独占锁,其它锁的读取进程就会处于阻滞状态,直到独占锁解除。这样,更新进程可以更改文件,而其他读取进程不会因为文件的更改得到前后不一致的结果

文件锁有建议使用和强制使用之分。建议型文件锁会向提出请求的进程提供当前锁定信息,但操作系统并不要求一定这样做,而是由相关进程进行协调并关注锁定信息。多数 Unix 和 类 Unix 操作系统使用建议型锁,有些也使用强制型锁或兼而有之。

强制型锁由操作系统或文件系统强制实施,不管进程对锁的存在知道与否,都会阻止其对文件锁定区域的访问,微软的操作系统往往使用的是强制型锁。假定所有文件锁均为建议型,并在访问共同资源的各个应用程序间使用一定的文件锁定,是明智之举,也是唯一可型的跨平台策略。依赖于强制文件锁定的应用程序,从根本上讲就是不可移植的

缓冲区( Buffers )

新的 Buffer 类是常规 Java 类和通道之间的纽带。原始数据元素组成的固定长度数组,封装在 包含状态信息的对象中,存入缓冲区。缓冲区提供了一个会合点:通道既可提取放在缓冲区中的数
据(写),也可向缓冲区存入数据供读取(读)。此外,还有一种特殊类型的缓冲区,用于内存映 射文件

通道( Channels )

NIO 新引入的最重要的抽象是通道的概念。Channel 对象模拟了通信连接,管道既可以是单向 的(进或出),也可以是双向的(进和出)。可以把通道想象成连接缓冲区和 I/O 服务的捷径。
某些情况下,软件包中的旧类可利用通道。为了能够向与文件或套接字关联的通道进行存取, 适当的地方都增加了新方法。
多数通道可工作在非块模式下,这意味着更好的可伸缩性,尤其是与选择器一同使用的时候。

文件锁定和内存映射文件( File locking and memory-mapped files )

新的 FileChannel 对象包含在 java.nio.channels 软件包内,提供许多面向文件的新特 性,其中最有趣的两个是文件锁定和内存映射文件。
在多个进程协同工作的情况下,要协调各个进程对共享数据的访问,文件锁定是必不可少的工 具。
将文件映射到内存,这样在您看来,磁盘上的文件数据就像是在内存中一样。这利用了操作系 统的虚拟内存功能,无需在内存中实际保留一份文件的拷贝,就可实现文件内容的动态高速缓存

套接字( Sockets )

套接字通道类为使用网络套接字实现交互提供了新方法。套接字通道可工作于非块模式,并可 与选择器一同使用。因此,多个套接字可实现多路传输,管理效率也比 java.net 提供的传统套 接字更高。
三个新套接字通道,即 ServerSocketChannel、SocketChannel 和 DatagramChannel,

选择器( Selectors )

选择器可实现就绪性选择。Selector 类提供了确定一或多个通道当前状态的机制。使用选择 器,借助单一线程,就可对数量庞大的活动 I/O 通道实施监控和维护

字符集( Character sets )

java.nio.charsets 提供了新类用于处理字符与字节流之间的映射关系。您可以对字符转换映射方式进行选择,也可以自己创建映射

@Test 
	public void NIOTest_copy_1() throws Exception {
		long startTime = System.currentTimeMillis();
		FileChannel in = new FileInputStream("F:\\temporary\\read.txt").getChannel();
		FileChannel out = new FileOutputStream("F:\\temporary\\write.txt").getChannel();
		
		ByteBuffer buffer = ByteBuffer.allocate(1024);
		while(in.read(buffer) != -1) {
			buffer.flip(); //切换到读取数据模式
			out.write(buffer);
			buffer.clear();
		}
		in.close();
		out.close();
		 long end = System.currentTimeMillis();
	        System.out.println("nioCopyTest2耗费时间:"+(end-startTime));
	}
	

	@Test 
	public void NIOTest_copy_2() throws Exception {
		long startTime = System.currentTimeMillis();
		FileChannel inChannel = FileChannel.open(Paths.get("F:\\temporary\\read.txt"), StandardOpenOption.READ);		   
        FileChannel outChennel = 
        		FileChannel.open(Paths.get("F:\\temporary\\write1.txt"),StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE_NEW);

        //内存映射文件(什么模式 从哪开始 到哪结束)
        MappedByteBuffer inMappeBuf = inChannel.map(FileChannel.MapMode.READ_ONLY,0,inChannel.size());
        MappedByteBuffer outMappeBuf =  outChennel.map(FileChannel.MapMode.READ_WRITE,0,inChannel.size());


        byte[] dst = new byte[inMappeBuf.limit()];
        inMappeBuf.get(dst);
        outMappeBuf.put(dst);
 
        inChannel.close();
        outChennel.close();
        long end = System.currentTimeMillis();
        System.out.println("nioCopyTest2耗费时间:"+(end-startTime));
       
	}
package Test;

import static org.junit.jupiter.api.Assertions.*;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.channels.Channel;
import java.nio.channels.CompletionHandler;
import java.nio.channels.DatagramChannel;
import java.nio.channels.FileChannel;
import java.nio.channels.Pipe;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.concurrent.Future;

import org.junit.jupiter.api.Test;

class NIOTest {

	/**
	 *Position 缓冲区正在操作的位置默认从0开始
	 *limit 写数据时,limit表示可对Buffer最多写入多少个数据。 读数据时,limit表示Buffer里有多少可读数据(not null的数据),
	 *capacity 缓冲区最大容量 一旦声明不能改变
	   *  核心方法:
	 *put() 往buff存放数据
	 *get() 获取数据 
	 */
	@Test
	void test() {
		ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
		byteBuffer.put("我会嘤嘤嘤...".getBytes()); // 向 byteBuffer 中存放数据
		 
		byteBuffer.flip(); // 读取 buteBuffer 里面的内容
		byte[] bytes = new byte[byteBuffer.limit()];
		byteBuffer.get(bytes);
		System.out.println("读取内容为: "+new String(bytes,0,bytes.length));
		
		byte[] bytes2 = new byte[byteBuffer.limit()]; // 重复读取
		byteBuffer.rewind(); 
		byteBuffer.get(bytes2);
		System.out.println("读取内容为: "+new String(bytes2,0,bytes2.length));
	}

	/**
	 * @throws Exception 
	 */
	@Test 
	public void NIOTest_1() throws Exception {
		// 读取文件
		// 第一步 获取通道
		FileInputStream fin = new FileInputStream("F:\\temporary\\read.txt");
		FileChannel fc = fin.getChannel();
		
		// 第二步创建缓冲区
		ByteBuffer buffer = ByteBuffer.allocate(1024);
		
		// 第三步 将数据读到缓冲区
		// 注意: 这里不需要告诉通道要读多少数据到缓冲区中。每一个缓冲区都有复杂的内部统计机制,它会跟踪已经读了多少数据
		// 以及还有多少空间可以容纳更多的数据。我们将在缓冲区内部中介绍更多关于缓冲区统计机制的内容
		fc.read(buffer);
		
		// 查看刚刚读入缓冲区的内容  
		buffer.rewind();
		byte[] bytes = new byte[buffer.limit()];
		buffer.get(bytes);
		System.out.println(new String(bytes,0,bytes.length));

	}
	
	@Test 
	public void NIOTest_copy_1() throws Exception {
		long startTime = System.currentTimeMillis();
		FileChannel in = new FileInputStream("F:\\temporary\\read.txt").getChannel();
		FileChannel out = new FileOutputStream("F:\\temporary\\write.txt").getChannel();
		
		ByteBuffer buffer = ByteBuffer.allocate(1024);
		while(in.read(buffer) != -1) {
			buffer.flip(); //切换到读取数据模式
			out.write(buffer);
			buffer.clear();
		}
		in.close();
		out.close();
		 long end = System.currentTimeMillis();
	        System.out.println("nioCopyTest2耗费时间:"+(end-startTime));
	}
	

	@Test 
	public void NIOTest_copy_2() throws Exception {
		long startTime = System.currentTimeMillis();
		FileChannel inChannel = FileChannel.open(Paths.get("F:\\temporary\\read.txt"), StandardOpenOption.READ);		   
        FileChannel outChennel = 
        		FileChannel.open(Paths.get("F:\\temporary\\write1.txt"),StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE_NEW);

        //内存映射文件(什么模式 从哪开始 到哪结束)
        MappedByteBuffer inMappeBuf = inChannel.map(FileChannel.MapMode.READ_ONLY,0,inChannel.size());
        MappedByteBuffer outMappeBuf =  outChennel.map(FileChannel.MapMode.READ_WRITE,0,inChannel.size());


        byte[] dst = new byte[inMappeBuf.limit()];
        inMappeBuf.get(dst);
        outMappeBuf.put(dst);
 
        inChannel.close();
        outChennel.close();
        long end = System.currentTimeMillis();
        System.out.println("nioCopyTest2耗费时间:"+(end-startTime));
       
	}
	
	@Test
	public void NIOTest_copy_3() throws Exception {
		 long startTime = System.currentTimeMillis();
		 
	        FileChannel inChannel = FileChannel.open(Paths.get("F:\\temporary\\read.txt"), StandardOpenOption.READ);
	 
	        FileChannel outChennel = FileChannel.open(Paths.get("F:\\temporary\\write4.txt"),StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE_NEW);
	 
	        outChennel.transferFrom(inChannel,0,inChannel.size());
	 
	        long end = System.currentTimeMillis();
	        System.out.println("nioCopyTest3耗费时间:"+(end-startTime));

	}
	
	/**
	 * 代码来源: 并发编程网  http://ifeve.com/channels/
	 * @throws Exception
	 */
	@Test
	public void NIO_Test_1() throws Exception {
		RandomAccessFile aFile = new RandomAccessFile("F:\\b\\write4.txt", "rw");
		FileChannel inChannel = aFile.getChannel();

		ByteBuffer buf = ByteBuffer.allocate(48);

		int bytesRead = inChannel.read(buf);
		
		while (bytesRead != -1) {

		System.out.println("Read " + bytesRead);
		buf.flip();

		while(buf.hasRemaining()){
		System.out.print( new String());
		}

		buf.clear();
		bytesRead = inChannel.read(buf);
		}
		aFile.close();
	}
	
	
	
	

	/**
	 * 
	 * 使用 FileChannel 读取 buffer 的示例
	 * @throws IOException 
	 */
	@Test
	public void test_channel() throws Exception {
		RandomAccessFile accessFile = new RandomAccessFile("G:\\temp\\temFile\\read.txt", "rw");
		FileChannel channel = accessFile.getChannel();
				
		ByteBuffer buffer = ByteBuffer.allocate(128);
		byte[] bytes = null;
		int length = -1;
		while ((length = channel.read(buffer)) != -1) {
			buffer.flip(); //切换到读数据
			bytes = buffer.array();
			System.out.println(new String(bytes,0,length,"UTF-8"));
			buffer.clear(); // 要让 buffer 准备好再次被写入,可以通过 clear() 或 compact() 方法来完成
		}	
		channel.close();
		accessFile.close();
	}
	
	/**
	 * 1. flip() 方法 将 buffer 从写模式切换到读模式
	 * 2. 从 buffer 读取数据到 Channel 方法: channel.write(buffer);
	 * 3. rewind()将 position 设为0,所以你可以重读 Buffer 中的所有数据,limit 保持不变
	 * 	  仍表示能从 buffer 中读取多少个元素
	 * 4. clear() 与 compact() 方法
	 *   读完 Buffer 中的数据,需要让 Buffer 准备好再次被写入。可以通过 clear() 或 compact() 方法完成
	 * 5. mark() 与 reset() 方法
	 *   通过 mark()方法可以标记 Buffer 中的一个特定 position.之后 可以通过 Buffer.reset 方法恢复这个
	 *   position 
	 * 6. equals() 与 compareTo() 方法
	 * 	 equals() 满足下面条件时,表示两个 Buffer 相等
	 *      ① 有相同的类型
	 *      ② Buffer 中剩余的 byte char 等的个数
	 *      ③ Buffer 中剩余的 byte char 等都相同
	 *      如你所见,equals 只是比较 Buffer 的一部分,不是每一个在它里面的元素都比较。实际上,它只比较
	 *      Buffer 中剩余元素
	 * 7. compareTo() 方法
	 * 	   用于比较两个 Buffer 的剩余元素(byte、char)等,如果满足下列条件,则认为一个 Buffer “小于”另一个
	 * 	  Buffer:
	 *  	① 第一个不相等的元素小于另一个 Buffer 中对应的元素
	 *  	② 所有元素都相等,但第一个 Buffer 比另一个先耗尽(第一个 Buffer 的元素比另一个少)
	 * 
	 * @throws Exception
	 */
	public void test_buffer() throws Exception{
		
	}

	
	/**
	 * 分散(scater): 从 Channel 中读取是指在读取操作时将读取的数据写入多个 buffer 中。因此, Channel
	 * 将 Channel 中读取的数据分散(scatter) 到多个 Buffer 中
	 * 聚集(gather): 写入 Channel 是指在写操作时将 多个 buffer 的数据写入同一个 Channel,因此,Channel 
	 * 将多个 Buffer 中的数据"聚集(gather)" 后发送到 Channel
	 * scatter/gather 经常用于需要将传输的数据分开处理的场合,例如传输一个由消息头和消息体组成的消息,你
	 * 可能会将消息和消息头分散到不同的 buffer 中,这样你可以方便的处理消息头和消息体
	 * @throws Exception
	 */
	@Test
	public void test_ScaterAndGather() throws Exception{
		RandomAccessFile accessFile = new RandomAccessFile("G:\\temp\\temFile\\read.txt", "rw");
		FileChannel channel = accessFile.getChannel();
		
		ByteBuffer header = ByteBuffer.allocate(16);
		ByteBuffer body = ByteBuffer.allocate(64);		 
		ByteBuffer [] byteBuffers = {header , body};
		
		channel.read(byteBuffers);
		channel.write(byteBuffers);
		
		channel.close();
		accessFile.close();
	}
	
	/**
	 * 通道之间的数据传输
	 * 在 java NIO 中,如果两个通道中一个是 FileChannel ,那你可以将数据从一个 channel 传输到另一个 channel
	 * 
	 * @throws Exception
	 */
	@Test
	public void test_transferXXX() throws Exception{
		RandomAccessFile readFile = new RandomAccessFile("G:\\temp\\temFile\\read.txt", "rw");
		FileChannel readChannel = readFile.getChannel();
		RandomAccessFile writeFile = new RandomAccessFile("G:\\temp\\temFile\\write.txt", "rw");
		FileChannel writeChannel = writeFile.getChannel();
		
		long position = 0;
		long count = writeChannel.size();
		
//		ByteBuffer buffer = ByteBuffer.allocate(1024);
//		int length = -1;
//		while ((length = readChannel.read(buffer)) != -1) {
//			buffer.flip();
//			
//			buffer.clear();
//		}
		
		writeChannel.transferFrom(readChannel, count, position);
		
		readChannel.close();
		writeChannel.close();
		readFile.close();
		writeFile.close();
	}
	
	/**
	 * Selector(选择器) 是 java NIO 中能够检测一到多个 NIO 通道,并且能够知晓是否为 诸如读写事件做好准备的组件。
	 * 这样,一个单独的线程可以管理多个 channel ,从而管理多个网络连接
	 * 为什么使用 Selector?(仅用单个线程来处理多个 Channels 的好处)
	 * 只需要更少的线程来处理通道,事实上,可以只用一个线程处理所有的通道。对于操作系统来说,线程之间上下文切换开销
	 * 很大,而且每个线程都要占用系统资源(如内存)。因此,使用的线程越少越好。
	 * 但是,需要记住,现代的操作系统和 CPU 在多任务方面表现越来越好,所以多线程的开销随着时间的推移,变得越来越小了。
	 * 实际上,一个 CPU 有多个内核,不使用多任务可能是在浪费 CPU 的能力。不管怎么说,关于那种设计讨论应该放在另一篇不同
	 * 的文章中。这里只需要知道使用 Selector 能够处理多个通道就足够了
	 * @throws Exception
	 * 位置: http://ifeve.com/selectors/
	 * Java NIO 系列教程(六)
	 */
	@Test
	public void test_Selector() throws Exception{
	
		Channel channel = null;
		// Selector 的创建
		Selector selector = Selector.open();
		// 向 Selector 注册通道
		// 与 Selector 一起使用时, Channel 必须处于非阻塞模式下。这意味着不能将 FileChannel 与 Selector 一起
		// 使用,因为 FileChannel 不能切换到 非阻塞模式,而 套接字通道都可以
		// 注意 register() 方法的第二个参数。这是一个 "interest集合" , 意思是在通过 Selector 监听 Channel 时
		// 对什么事件感兴趣。可以监听四种不同类型的事件
		// 1. Connect 
		// 2. Accept 
		// 3. Read 
		// 4. Write
		// 通道触发了一个事件意思是该事件已经就绪。所以,某个 channel 成功连接到另一个服务器称为"连接就绪". 一个 
		// Server socket channel 准备好接收新进入的连接称为"接收就绪"。一个有数据可读的通道可以说是“读就绪”。
		// 等待写数据的通道可以说是 “写就绪”
		// 这四种 事件可以用 SelectionKey 的四个常量表示:
		// 1. SelectionKey.OP_CONNECT
		// 2. SelectionKey.OP_ACCEPT
		// 3. SelectionKey.OP_READ
		// 4. SelectionKey.OP_WRITE
		// 如果你对不止一种事件感兴趣,那么可以用"位或"操作符将常量连接起来,如下:
		// 		int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;

//		channel.configureBlocking(false);
//		SelectionKey key = channel.register(selector,SelectionKey.OP_READ);
	}
	
	/**
	 * channel.size() 返回该实例所关联的大小
	 * channel.truncate(1024); 这个例子截取文件的前 1024 个字节,指定长度后面的部分将被删除
	 * channel.force(true); 将通道里尚未写入磁盘的数据强制写到磁盘上 
	 * @throws Exception
	 */
	@Test
	public void test_FileChannel_1() throws Exception{
		RandomAccessFile writeFile = new RandomAccessFile("G:\\temp\\temFile\\write.txt", "rw");
		FileChannel writeChannel = writeFile.getChannel();
		String newData = "我会嘤嘤嘤123!!";
		
		ByteBuffer buffer = ByteBuffer.allocate(1024);
		buffer.clear();
		buffer.put(newData.getBytes());
		
		buffer.flip();
		while (buffer.hasRemaining()) {
			writeChannel.write(buffer);	
		}
		
		writeChannel.close();
		writeFile.close();
	}
	
	/**
	 * 主要方法:
	 * 1. position 方法(无参获取当前的位置,有参设置其位置)
	 * 	  有时候可能需要在 FileChannel 的某个特定位置进行数据的 读/写操作。可以通过 position() 方法获取 FileChannel 的当前位置
	 * 2. size 方法
	 *          返回该实例所关联文件的大小
	 * 3. truncate 方法, 
	 *          截取文件时,文件将截取指定部分,指定长度后面部分将被删除
	 * 4. force 方法
	 *          将通道里尚未写入磁盘的数据强制写到磁盘上。处于性能方面的考虑,操作系统会将数据缓存在内存中,所以无法保证写入到 FileChannel 里的
	 *          数据一定会即使写到磁盘上。要保证这一点,需要调节 force() 方法         
	 * @throws Exception
	 */
	@Test 
	public void test_FileChannel_2() throws Exception{
		// 打开 channel 
		RandomAccessFile aFile = new RandomAccessFile("F:\\temporary\\write.txt", "rw");
		FileChannel channel = aFile.getChannel();
		
		// 从 channel 中读取数据
		ByteBuffer buffer = ByteBuffer.allocate(64);
		byte [] bytes = null ;
		channel.read(buffer);
		bytes = buffer.array();		
		System.out.println("第一次: "+new String( bytes,"utf-8"));
		
		// 向 fileChannel 中写数据	
		buffer.clear();
		String newData = "新增数据, 我会嘤嘤嘤......"; 
		buffer.put(newData.getBytes());
		buffer.flip();    // 没有这行代码,不会将数据写入磁盘
		while(buffer.hasRemaining()) {
			channel.write(buffer);
		}
		buffer.clear();
		
//		channel.force(true);
		// 再次查看内容
		buffer.flip();
		channel.read(buffer);
		bytes = buffer.array();		
		System.out.println("第二次: "+new String( bytes,"utf-8"));
				
		channel.close();
		aFile.close();
	}
	
	/**
	 * NIO 中的 SocketChannel 是一个连接到 TCP 网络套接字的通道,可以通过一下两种方式创建 SocketChannel:
	 * 1. 打开一个 SocketChannel 并连接到互联网上的某台服务器
	 * 2. 一个新的连接到达 ServerSocketChannel 时,会创建一个 SocketChannel 
	   *     打开 SocketChannel :
	 * 		SocketChannel socketChannel = SocketChannel.open();
	 * 		socketChannel.connect(new InetSocketAddress("http://jenkov.com),80);
	 * 从 SocketChannel 读取数据
	 * 要从 SocketChannel 中读取数据,调用一个 read() 的方法之一,如下例子:
	 * 	 ByteBuffer buffer = ByteBuffer.allocate(48);
	 * 	 int bytesRead = socketChannel.read(buf);
	 * 首先,分配一个 Buffer .从 SocketChannel 读取到的数据将会放到这个 Buffer 中,read() 方法返回的 int 
	 * 值表示读了多少字节进 Buffer 里,如果返回的是  -1,表示已经读到了流的末尾(连接关闭了)
	 * 
	 * @throws Exception
	 */
	@Test 
	public void test_socketChannel() throws Exception{
		
	}
	
	/**
	 * NIO 中的 ServerSocketChannel 是一个可以监听新进来的 TCP 连接的通道,就像标准 IO 中的 ServerSocket 一样。
	 * ServerSocketChannel 类在 java.nio.channels 包中
	 * 1. 打开 ServerSocketChannel 是通过调用  ServerSocketChannel.open()
	 * 2.关闭 ServerSocketChannel 是通过调用  serverSocketChannel.close()
	 * 3.监听新进来的连接 是通过 serverSocketChannel.accept(),当 accep() 方法返回的时候,它返回一个包含新进来的连接的 
	 * SocketChannel。因此 accep() 方法会一直阻塞到有新连接到达
	 * 4.可以通过 serverSocketChannel.configureBlocking(false);将其设置为 非阻塞模式
	 *  
	 * @throws Exception
	 */
	@Test
	public void test_serverSocketChanneel() throws Exception{
		ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
		serverSocketChannel.socket().bind(new InetSocketAddress(9999));
		serverSocketChannel.configureBlocking(false); // 将 serverSocketChannel 设置为非阻塞模式
		int state = 0;
		while(true) {
			SocketChannel socketChannel = serverSocketChannel.accept();
			// 其它 逻辑
			state ++;
			if(state > 1000) {
				serverSocketChannel.close();
				return ;
			}
		}
	}


	/**
	 * DatagramChannel 是一个能收发 UDP 包的通道,因为 UDP 是无连接的网络协议,所以不能像其它通道那样读取和写入。它发送和接受的是
	 *      数据包
	 * @throws Exception
	 */
	@Test 
	public void test_datagramChannel() throws Exception{
		// 打开 DatagramChannel 
		DatagramChannel channel = DatagramChannel.open();
		channel.socket().bind(new InetSocketAddress(9999));
		
		// 接收数据
		ByteBuffer buffer = ByteBuffer.allocate(64);
		buffer.clear();
		channel.receive(buffer);
		
		// 发送数据
		String data = "从 Datagram 发送数据";
		buffer.clear();
		buffer.put(data.getBytes());
		buffer.flip();
		
		// 连接特定的地址
		int bytesSent = channel.send(buffer, new InetSocketAddress("jenkov.com",80));
		int bytesRead = channel.read(buffer);
		int bytesWrite = channel.write(buffer);
	}
	
	/**
	 * NIO 管道是 2 个线程之间的单向数据连接。Pipe 有一个 source 通道和一个 sink 通道。数据会被写入 sink 通道,从
	 * source 通道中读取 
	 * @throws Exception 
	 */
	@Test
	public void test_Pipe() throws Exception {
		// 创建管道
		Pipe pipe = Pipe.open();
		
		// 向管道写数据
		Pipe.SinkChannel sinkChannel = pipe.sink();
		String data = "测试数据....";
		ByteBuffer buffer = ByteBuffer.allocate(64);
		buffer.clear();
		buffer.flip();		
		while(buffer.hasRemaining()) {
			sinkChannel.write(buffer);
			break;
		}
		
		// 从管道中读取数据
		Pipe.SourceChannel sourceChannel = pipe.source();
		ByteBuffer buffer2 = ByteBuffer.allocate(64);
		int bytesRead = sourceChannel.read(buffer2);
		
		System.out.println(new String(buffer2.array(),"utf-8"));
	}
	
	/**
	 * Java NIO 和 IO 的主要区别
	 * IO 						NIO	
	 *    面向流						面向缓冲
	 *    阻塞IO						非阻塞 IO
	 *    无							选择器
	 * @throws Exception
	 */
	@Test
	public void test_NIOAndIO() throws Exception{
		
	}
	
	
	/**
	 * Path 接口是 java nio2的一部分,全称是 java.nio.file.Path. java 中的 Path 表示文件系统的路径,可以指向文件或文件夹,
	 * 也有相对路径和绝对路径之分。绝对路径表示从文件系统的根路径到文件或是文件夹的路径。相对路径表示从特定路径下访问指定文件或是文件夹的路径。
	 * 在很多方面,  java.nio.file.Path 接口和 java.io.File 有相似性,但也有一些细微的差别。在很多情况下,可以用 Path 代替。
	 * @throws Exception
	 */
	@Test
	public void test_NIOPath() throws Exception{
		// 绝对路径
		Path path = Paths.get("F:\\temporary\\write.txt"); 
		
		// 相对路径
		// 相对路径指一个已确定的路径开始到某一文件或文件夹的路径.将确定路径和相对路径
		// 拼接起来就是对应的绝对路径地址
		Path projects = Paths.get("F:\\temporary","write.txt");
		
		// .(点) 表示当前路径
		Path currentDir = Paths.get(".");
		System.out.println(currentDir.toAbsolutePath());
		
		// Path.normalize()
		// Path 的 normalize() 方法可以标准化路径。标准化的含义是路径中的 . 和 ..(点) 都被去掉,指向真正的路径目录地址。下面 
		// Path.normalize() 示例
	}
	
	/**
	 * Files 类( java.nio.file.File)提供了操作文件相关方法
	 * @throws Exception
	 */
	@Test
	public void test_NIOFiles() throws Exception{
		
	}
	
	/**
	 * AsynchronousFileChannel 被添加到 Java NIO 中。使用 AsynchronousFileChannel 可以实现异步地
	 * 读取和写入文件数据,它提供了两种读取数据的方式,都是 调用它本身的 read() 方法。
	 * @throws Exception
	 */
	@Test
	public void test_AsynchronousFileChannel() throws Exception{
		Path path = Paths.get("F:\\temporary\\write.txt");
		AsynchronousFileChannel channel = 
				AsynchronousFileChannel.open(path, StandardOpenOption.READ);
		ByteBuffer buffer = ByteBuffer.allocate(128);
		// 1. 使用 Futrue 读取数据
		Future<Integer> operation1 = channel.read(buffer, 0);
		while(!operation1.isDone());
		
		buffer.flip();
		byte[] data = new byte[buffer.limit()];
		buffer.get(data);
		System.out.println(new String(data));
		buffer.clear();
		
		
		// 使用 CompletionHandler 读取数据
		// 一旦读取操作完成, CompletionHandler 的 complete() 方法将会被调用。它的第一个参数是 Integer
		// 类型,表示读取的字节数。第二个参数 attachment 是 ByteBuffer类型的,用来存储读取的数据,它 其实就是由 read()
		// 方法的第三个参数。
		int position = 0;
		channel.read(buffer, position,buffer,new CompletionHandler<Integer,ByteBuffer>(){

			@Override
			public void completed(Integer result, ByteBuffer attachment) {
				System.out.println("result = "+result);
				
				attachment.flip();
				byte[] data = new byte[attachment.limit()];
				attachment.get(data);
				System.out.println(new String(data));
				attachment.clear();
			}

			@Override
			public void failed(Throwable exc, ByteBuffer attachment) {
				// TODO Auto-generated method stub
				
			}
			
		});
		
		
	}
}

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值