提到IO,这是Java提供的一套类库,用于支持应用程序与内存、文件、网络间进行数据交互,实现数据写入与输出。JDK自从1.4版本后,提供了另一套类库NIO,我们平时习惯称呼为NEW IO或NON-blocking IO。
创新互联建站专注于灵武网站建设服务及定制,我们拥有丰富的企业做网站经验。 热诚为您提供灵武营销型网站建设,灵武网站制作、灵武网页设计、灵武网站官网定制、小程序开发服务,打造灵武网络公司原创品牌,更为您提供灵武网站排名全网营销落地服务。
那么这套新的IO库与之前的有何区别?为什么需要提供这样一套IO库呢?
Java NIO相比与传统的IO,除了提供标准IO的加强功能之外,最为核心的是对基于Socket的网络编程提供了一套非阻塞编程模式。
IO |
NIO |
面向流 |
面向缓冲 |
阻塞 |
非阻塞 |
无 |
选择器 |
Java IO Java的IO很好的诠释了Stream这个概念,该单词本身的含义表示‘河流’,承载数据的流,平时我们说的面向流的操作主要是在流的端点,实现对数据读与写。通过Stream相关的API可以看到, 不管是输入还是输出流,我们能做的仅仅是将数据读取或写入到流中。
Java NIO NIO是基于缓冲区来操作数据,主要是基于通道Channel从缓冲Buffer中进行数据读取或写入。其中Buffer的灵活性决定了NIO的可操作空间,同样基于Buffer API可以看到, 其提供了对Buffer的基本读写功能外,还有提供了各种其他API来操作Buffer,相比Stream对数据的操作更加的灵活。
Java IO 上面说到IO的操作都是基于流的,往流中写入数据时依赖于OutputStream#write,从流中读取数据时通过InputStream#read,这些操作都是阻塞的。
Java NIO 支持非阻塞模式,但并非NIO就是非阻塞的,比如基于FileChannel操作文件时,仍然是阻塞的。我们说的阻塞或非阻塞都是基于操作系统层面的read/write方法导致的,NIO的非阻塞 基于操作系统层面提供的多路复用IO模型实现,所以NIO的实现是依赖于操作系统的支持。
在NIO中,三个核心的对象Buffer、Channel、Selector
Buffer
我们经常说的面向缓冲区编程主要对该对象的操作,Buffer简单的看就是一个内存块,其内部封装了一个数组,同时该对象提供了大量API可以灵活对其操作,比如缓冲数据读取与写入、缓冲复制等。
其内部结构如下:
其内部除了存储数据的数组外,还维护了capacity、limit、position几个属性,用于标记数组容量、存储占用空间、下标索引。Buffer存在读写两种状态,根据上图可以看到其具体含义。
读模式
写模式
用于将Buffer由写状态切换为读状态,limit = position; position = 0;
用于将Buffer由读状态切换为写状态,compact:positinotallow=limit,limit=capacity; clear:positinotallow=0,limit=capacity。
操作Buffer时,用于临时存储position(mark=position),当有需要时,可以通过rest方法将临时值取出并赋值到position(positinotallow=mark) 重新从标记位置继续操作Buffer。
Channel
直译为通道,表示源端与目标端的连接通道,主要负责将数据读写到Buffer。
常用的Channel包括FileChannel、DatagramChannel、ServerSocketChannel和SocketChannel。
Selector
选择器是NIO技术中的核心组件,可以将通道注册进选择器中,其主要作用就是使用一个线程来对多个通道中的已就绪通道进行选择, 然后可以对选择的通道进行数据处理,属于一对多的关系。这种机制在NIO技术中心称为“IO多路复用”。其优势是可以在一个线程中 对多个连接实现监听,从而节省系统资源与CPU开销。
其中包括三个核心类:
我们可以将Channel注册到Selector上并定义感兴趣的事件,当Channel就绪时,可以监听这些事件:
传统IO复制文件时需要依赖于InputStream、OutputStream来完成,基于NIO可以通过FileChannel:
// 文件复制
sourceChannel.transferTo(0, sourceChannel.size(), targetChannel);
// 其中获取FileChannel的方法有以下三种:
FileChannel channel = new FileInputStream(file).getChannel();
FileChannel channel = new RandomAccessFile(file, "rw").getChannel();
FileChannel channel = FileChannel.open(file.toPath());
Server:
@Slf4j
public class Server {
private Selector selector;
private DatagramChannel datagramChannel;
public Server(int port) {
try {
this.selector = Selector.open();
this.datagramChannel = DatagramChannel.open();
this.datagramChannel.configureBlocking(false);
this.datagramChannel.bind(new InetSocketAddress(port));
this.datagramChannel.register(this.selector, SelectionKey.OP_READ);
log.info("++++++ DUP Server启动成功 ++++++");
} catch (IOException e) {
log.error("Server创建失败:{}", e.getMessage());
}
}
public void start() throws IOException {
while (true){
int select = selector.select();
if(select >0 ){
Iterator iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()){
SelectionKey key = iterator.next();
iterator.remove();
if(key.isReadable()){
DatagramChannel channel = (DatagramChannel) key.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
channel.receive(byteBuffer);
byteBuffer.flip();
CharBuffer charBuffer = Charset.defaultCharset ().decode ( byteBuffer ) ;
log.info("Server接收消息:{}", charBuffer);
}
}
}
}
}
}
Client:
@Slf4j
public class Client {
private DatagramChannel datagramChannel;
public Client(int port) {
try {
this.datagramChannel = DatagramChannel.open();
this.datagramChannel.configureBlocking(true);
this.datagramChannel.connect(new InetSocketAddress("127.0.0.1", port));
} catch (IOException e) {
log.error("Client创建失败:{}", e.getMessage());
}
}
public void invoke(String message) throws IOException {
log.info("Client发送消息:{}", message);
datagramChannel.write(Charset.defaultCharset().encode(message));
}
}
Tests:
public class UDPTest {
int port = 8095;
@Test
public void server() throws IOException {
Server server = new Server(port);
server.start();
}
@Test
public void client() throws IOException {
Client client = new Client(port);
client.invoke(message);
while (true){}
}
}
Server:
@Slf4j
public class Server {
private ServerSocketChannel serverSocketChannel;
private Selector selector;
public Server(int port){
try {
this.selector = Selector.open();
this.serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
serverSocketChannel.bind(new InetSocketAddress(port));
log.info("++++++ NIO Server启动成功 ++++++");
} catch (IOException e) {
log.error("创建ServerSocketChannel出错:{}", e.getMessage());
}
}
public void start() throws IOException {
while (true){
selector.select(); // 阻塞
Iterator keyIterator = selector.selectedKeys().iterator();
while (keyIterator.hasNext()){
SelectionKey selectionKey = keyIterator.next();
keyIterator.remove(); //
if(!selectionKey.isValid()){
continue;
}
if(selectionKey.isAcceptable()){
ServerSocketChannel ssc = (ServerSocketChannel) selectionKey.channel();
SocketChannel socketChannel = ssc.accept(); // 可以是阻塞或非阻塞,获取的Channel一定是阻塞的
socketChannel.configureBlocking(false); // 这个有用?
socketChannel.register(selector, SelectionKey.OP_READ);
}else if(selectionKey.isReadable()){
SocketChannel channel = (SocketChannel) selectionKey.channel();
ByteBuffer buffer = ByteBuffer.allocate(256);
int writeBytes = channel.read(buffer); //
if(writeBytes > 0){
buffer.flip();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
log.info(">>> Server接收消息:{}", new String(bytes));
}
// 回复
channel.write(Charset.defaultCharset().encode("我是Server的回复内容"));
}
}
}
}
}
Client:
@Slf4j
public class Client {
private SocketChannel socketChannel;
public Client(int port){
try {
this.socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("127.0.0.1",port));
} catch (IOException e) {
log.error("创建SocketChannel出错:{}", e.getMessage());
}
}
public void invoke(String message) throws IOException {
log.info(">>> Client发送消息:{}", message);
this.socketChannel.write(Charset.defaultCharset().encode(message));
}
}
NIO整体处理流程如下:
首先我们要知道,程序在读取系统文件时,是没办法直接读取磁盘内容,基于操作系统安全考虑,需要通过调用操作系统提供的系统API从内核缓冲区将文件数据拷到用户缓冲区后 才能读取到文件信息。
在操作系统层面,如果为了完成网络文件的传输,一般需要这样做:
while( in.read(...)!=-1 ){
out.write(...)
}
拿到源文件的输入流;拿到目标文件的输出流;从输入流读取数据;将数据写入到输出流;
整个过程经历了4次文件拷贝:
经历了4次CPU切换:
在高并发网络通信环境中,通过传统的方式由于多次的CPU切换与数据拷贝会消耗系统资源,因此为了提高网络间文件传输的性能,就需要减少‘用户态与内核态的上下文切换’和‘内存拷贝’的次数。
零拷贝的“零”是指用户态和内核态间copy数据的次数为零
零拷贝依附于操作系统底层,基于虚拟内存实现,将文件地址与虚拟地址件建立映射关系,
零拷贝技术可以减少数据拷贝和共享总线操作的次数,消除传输数据在存储器之间不必要的中间拷贝次数,从而有效地提高数据传输效率;零拷贝技术减少了用户进程地址空间和内核地址空间之间因为上下文切换而带来的开销
RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");
FileChannel fileChannel = randomAccessFile.getChannel();
MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, fileChannel.size());
NIO的出现得益于操作系统的变革,由于网路编程对性能与资源使用上的要求更高,传统的IO模型只能通过线程来提升系统吞吐率;为了满足现代网络通信的需求,在高级编程语言中的优化 行为逐步迁移到操作系统底层,这样通过底层逻辑优化,不仅提供系统性能,最主要减少了系统资源的浪费。
文章名称:一篇文章带你了解JavaNIO
URL标题:http://www.shufengxianlan.com/qtweb/news23/168023.html
网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联