Java NIO

本文深入探讨了Java NIO中的Buffer概念,包括Capacity、Limit、Position和Mark等关键属性的作用,通过具体示例展示了如何使用flip()、clear()等方法进行缓冲区状态的切换,并比较了传统IO与NIO复制文件的效率。此外,还介绍了Direct Buffer和MappedByteBuffer的使用场景及其优势。

 

 

Capacity

A buffer's capacity is the number of elements it contains. The capacity of a buffer is never negative and never changes. 

 

Limit

A buffer's limit is the index of the first element that should not be read or written. A buffer's limit is never negative and is never greater than its capacity. 

limit 变量限制了当前buffer中可操作的容量大小。 向buffer写入数据时limit =capacity (除非使用limit(n)限制了limit大小),表明可写入的容量为limit ;调用flip()时Limit=Position,表面当前buffer可读取的容量是limit。 

 

Position 

A buffer's position is the index of the next element to be read or written. A buffer's position is never negative and is never greater than its limit. 

在从通道读取时,您将所读取的数据放到底层的数组中。 position 变量跟踪已经写了多少数据。更准确地说,它指定了下一个字节将放到数组的哪一个元素中。因此,如果您从通道中读三个字节到缓冲区中,那么缓冲区的 position 将会设置为3,指向数组中第四个元素。

同样,在写入通道时,您是从缓冲区中获取数据。 position 值跟踪从缓冲区中获取了多少数据。更准确地说,它指定下一个字节来自数组的哪一个元素。因此如果从缓冲区写了5个字节到通道中,那么缓冲区的 position 将被设置为5,指向数组的第六个元素。

 

Mark

A buffer's mark is the index to which its position will be reset when the reset method is invoked. The mark is not always defined, but when it is defined it is never negative and is never greater than the position.

标记读写的位置

 

0 <= mark <= position <= limit <= capacity 

remaining= limit - position 

 

默认值:

mark =-1 (未定义)

position =0

limit = capacity = n

 

重要方法:

flip()  由写buffer模式 改为从buffer读出模式

public final Buffer flip() {
	limit = position;
	position = 0;
	mark = -1;
	return this;
}
 

 clear() 重新回到写buffer模式

    public final Buffer clear() {
        position = 0;
        limit = capacity;
        mark = -1;
        return this;
    }

 

 

 mark() 标记当前读或写得位置

    public final Buffer mark() {
        mark = position;
        return this;
    }

 

reset() 回到标记的位置

    public final Buffer reset() {
        int m = mark;
        if (m < 0)
            throw new InvalidMarkException();
        position = m;
        return this;
    }

 

discarkMark() 清除标记

    final void discardMark() {                          // package-private
        mark = -1;
    }

 

 rewind() 位置归0,清除标记, 从头开始读或写

    public final Buffer rewind() {
        position = 0;
        mark = -1;
        return this;
    }

 

 

以下对比两种不同IO copy文件的方式:

	@Test
	public void copyByIO() throws Exception{
		FileInputStream fin = new FileInputStream("E:\\TechTools\\windows\\DEEP_GHOST_WIN7_SP1_X64_V2016_01.iso");
		FileOutputStream fout = new FileOutputStream("D:\\Data\\Log\\future\\DEEP_GHOST_WIN7_SP1_X64_V2016_01.iso");
		byte[] buffer = new byte[1024*1024];
		while(fin.read(buffer, 0, 1024*1024)!=-1){
			fout.write(buffer);
		}
		fin.close();
		fout.close();
	}


	@Test
	public void copyByNIO() throws Exception{
		FileInputStream fin = new FileInputStream("E:\\TechTools\\windows\\DEEP_GHOST_WIN7_SP1_X64_V2016_01.iso");
		FileOutputStream fout = new FileOutputStream("D:\\Data\\Log\\future\\DEEP_GHOST_WIN7_SP1_X64_V2016_01.iso");
		try {
			FileChannel chin = fin.getChannel();
			FileChannel chout = fout.getChannel();
			ByteBuffer buffer =ByteBuffer.allocate(1024*1024);
			int flag = 0;
			while(flag!=-1){
				buffer.flip();
				chout.write(buffer);
				
				buffer.clear();
				flag = chin.read(buffer);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}finally{
			fin.close();
			fout.close();
		}
	}

  测试结果: NIO比IO略快。

 

Direct Buffer

与ByteBuffer.allocate(1024*1024)相似的另一个方法是ByteBuffer.allocateDirect(1024*1024)。它直接在OS内核分配内存,不占用JVM空间,就好比是“内核缓冲区”的内容直接写入了Channel,减少了数据拷贝,因此速度更快。著名的Netty就是使用DirectBuffer。

 

 

MappedByteBuffer 

将文件的全部或部分直接映射到内存,可以实现对文件的快速随机读取,对MappedByteBuffer的修改操作也可以直接反应到文件

	@Test
	public void testMappedByteBuffer() throws Exception{
		RandomAccessFile file = new RandomAccessFile("D:\\Data\\Log\\future\\1.txt", "rw");
		FileChannel chin = file.getChannel();
		MappedByteBuffer buffer = chin.map(MapMode.READ_WRITE, 0, chin.size());
		buffer.put(0, (byte)1);
		file.close();
	}
 

 

 

 

参考:

https://www.ibm.com/developerworks/cn/education/java/j-nio/index.html

<Direct buffer 与Heap Buffer的区别>http://blog.youkuaiyun.com/shibushiyouwenti/article/details/7396016

 

11-13
Java NIO(New I/O)是Java 1.4引入的新的I/O API,用于替代标准的Java I/O API。它提供了非阻塞I/O操作,能显著提高程序的性能和可扩展性,尤其适用于处理大量并发连接的场景。 ### 核心组件 - **Channel(通道)**:Channel是对传统I/O中流的模拟,用于在缓冲区和实体(如文件、套接字)之间传输数据。常见的Channel实现有FileChannel、SocketChannel、ServerSocketChannel和DatagramChannel等。例如,FileChannel用于文件读写,SocketChannel用于TCP网络通信。 - **Buffer(缓冲区)**:Buffer是一个用于存储特定基本类型数据的容器。所有的缓冲区都是Buffer抽象类的子类,如ByteBuffer、CharBuffer、IntBuffer等。使用时,数据先被写入Buffer,再从Buffer读取到Channel,反之亦然。 - **Selector(选择器)**:Selector是Java NIO实现非阻塞I/O的关键。它允许一个线程处理多个Channel的I/O事件。通过将多个Channel注册到一个Selector上,Selector可以不断轮询这些Channel,当某个Channel有可用的I/O操作时,就会被Selector选中,从而实现单线程处理多个连接的目的。 - **SelectionKey(选择键)**:SelectionKey用于维护Selector和SelectableChannel的关系,每个Channel注册到Selector时都会产生一个SelectionKey,它聚合了Channel和Selector,有点类似EventKey。通过SelectionKey可以获取对应的Channel和Selector,还可以设置和查询感兴趣的I/O事件类型,如读、写、连接和接受连接事件等 [^1]。 ### 使用指南 #### 1. 使用FileChannel进行文件读写 ```java import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; public class FileChannelExample { public static void main(String[] args) { try (FileInputStream fis = new FileInputStream("input.txt"); FileOutputStream fos = new FileOutputStream("output.txt"); FileChannel inChannel = fis.getChannel(); FileChannel outChannel = fos.getChannel()) { ByteBuffer buffer = ByteBuffer.allocate(1024); while (inChannel.read(buffer) != -1) { buffer.flip(); // 切换为读模式 outChannel.write(buffer); buffer.clear(); // 清空缓冲区,准备下一次写入 } } catch (IOException e) { e.printStackTrace(); } } } ``` #### 2. 使用Selector实现非阻塞网络编程 ```java 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.Iterator; import java.util.Set; public class NioServerExample { public static void main(String[] args) { try (ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); Selector selector = Selector.open()) { serverSocketChannel.socket().bind(new InetSocketAddress(8080)); serverSocketChannel.configureBlocking(false); serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); while (true) { int readyChannels = selector.select(); if (readyChannels == 0) continue; Set<SelectionKey> selectedKeys = selector.selectedKeys(); Iterator<SelectionKey> keyIterator = selectedKeys.iterator(); while (keyIterator.hasNext()) { SelectionKey key = keyIterator.next(); if (key.isAcceptable()) { ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel(); SocketChannel socketChannel = serverChannel.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 bytesRead = socketChannel.read(buffer); if (bytesRead > 0) { buffer.flip(); byte[] data = new byte[buffer.remaining()]; buffer.get(data); System.out.println(new String(data)); } } keyIterator.remove(); } } } catch (IOException e) { e.printStackTrace(); } } } ``` ### 原理 Java NIO的非阻塞I/O原理基于操作系统的I/O多路复用机制。在传统的阻塞I/O模型中,一个线程只能处理一个连接,当线程在等待某个连接的数据时会被阻塞,无法处理其他连接。而在Java NIO中,Selector利用操作系统提供的I/O多路复用功能,如Linux的select、poll和epoll,通过一个线程监控多个Channel的I/O状态。当某个Channel有数据可读或可写时,Selector会感知到并通知应用程序进行相应的处理,从而实现单线程处理多个连接,提高了系统的并发处理能力。 ### 应用场景 - **网络编程**:在构建高性能的网络服务器时,如Web服务器、聊天服务器、游戏服务器等,Java NIO的非阻塞I/O特性可以显著减少线程数量,降低系统资源消耗,提高服务器的并发处理能力。 - **文件处理**:对于大文件的读写操作,使用FileChannel和ByteBuffer可以提高文件读写的效率,尤其是在需要随机访问文件内容时。 - **实时数据处理**:在处理实时数据流时,如视频流、音频流等,Java NIO可以高效地处理数据的传输和处理,确保数据的实时性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值