Java NIO笔记

Java NIO由一下几个核心部分:

  • Channels
  • Buffers
  • Selectors 通道 ( Channels ) Java NIO的通道类似流,但又有些不同:
  • 既可以从通道中读取数据,又可写数据到通道
  • 通道可以异步地读写
  • 通道中的数据总是要先读到一个Buffer,或者要从一个Buffer写入

Channel的实现:

  • FileChannel:从文件中读写数据
  • SocketChannel:通过TCP读写网络中的数据
  • DatagramChannel:通过UDP读写网络中的数据
  • ServerSocketChannel:监听新进来的TCP连接,想web服务器那样,对每一个新进来的连接都会创建一个SocketChannel

下面是一个读取文件实例

RandomAccessFile aFile = new RandomAccessFile("LinuxPro.iml", "rw");
FileChannel fileChannel = aFile.getChannel();

ByteBuffer bf = ByteBuffer.allocate(48);
while (fileChannel.read(bf) != -1) {
    System.out.println("Read:" + bf);
    bf.flip();
    System.out.println("Read:" + bf);

    while (bf.hasRemaining()) {
        System.out.print((char) bf.get());
    }
    System.out.println();
    bf.clear();
}
aFile.close();

缓存区 ( Buffer )

使用Buffer读写数据一般遵循一下四个步骤:

  • 写入数据到Buffer
  • 调用flip()方法
  • 从Buffer中读取数据
  • 调用clear()方法或者campact方法

Buffer的 capacity, position 和 limit

limit:在写模式下,Buffer的limit表示你最多能往Buffer里写多少数据,写模式下,等于Buffer的capacity。 position:在写模式下,position表示当前的位置。初始值为0,最大可为capacity-1. capacity:一个内存块,Buffer的固定的大小值。

Buffer的类型

Java NIO有以下Buffer类型:

  • ByteBuffer
  • MappedByteBuffer
  • CharBuffer
  • DoubleBuffer
  • FloatBuffer
  • IntBuffer
  • LongBuffer
  • ShortBuffer

Buffer 的分配与读据

Buffer的分配

ByteBuffer buf = ByteBuffer.allocate(48);

写数据到Buffer有两种方式:

  • 从Channel写到Buffer
  • 通过Buffer的put()方法写到Biffer里

从Channel写到Buffer的例子

int bytesRead = inChannel.read(buf); //read into buffer.

通过put方法写Buffer的例子

buf.put(127);

从Buffer中读取数据有两种方式:

  • 从Buffer读取数据到Channel
  • 使用get()方法从Buffer中读取数据

从Buffer读取数据到Channel的例子 int byteWrtten = inChannel.write(buf); 使用get()方法从Buffer读取数据的例子

byte aByte = buf.get();

flip() 方法

flip 方法将 Buffer 从写模式切换到读模式。调用 flip() 方法会将 position 设回 0,并将 limit 设置成之前 position 的值。

rewind() 方法

Buffer.rewind() 将 position 设回 0,所以你可以重读 Buffer 中的所有数据。limit 保持不变,仍然表示能从 Buffer 中读取多少个元素(byte、char 等)。

clear() 与 compact()方法

一旦读完 Buffer 中的数据,需要让 Buffer 准备好再次被写入。可以通过 clear() 或 compact() 方法来完成。 如果调用的是 clear() 方法,position 将被设回 0,limit 被设置成 capacity 的值。 如果 Buffer 中有一些未读的数据,调用 clear() 方法,数据将 “被遗忘”,意味着不再有任何标记会告诉你哪些数据被读过,哪些还没有。 如果 Buffer 中仍有未读的数据,且后续还需要这些数据,但是此时想要先先写些数据,那么使用 compact() 方法。 compact() 方法将所有未读的数据拷贝到 Buffer 起始处。然后将 position 设到最后一个未读元素正后面。limit 属性依然像 clear() 方法一样,设置成 capacity。现在 Buffer 准备好写数据了,但是不会覆盖未读的数据。

equals() 与 compareTo() 方法

当满足下列条件时,表示两个 Buffer 相等equals():

  • 有相同的类型(byte、char、int 等)。
  • Buffer 中剩余的 byte、char 等的个数相等。
  • Buffer 中所有剩余的 byte、char 等都相同。

compareTo() 方法比较两个 Buffer 的剩余元素 (byte、char 等), 如果满足下列条件,则认为一个 Buffer“小于” 另一个 Buffer:

  • 第一个不相等的元素小于另一个 Buffer 中对应的元素 。
  • 所有元素都相等,但第一个 Buffer 比另一个先耗尽 (第一个 Buffer 的元素个数比另一个少)。

Scatter/Gather

Java NIO 开始支持 scatter/gather,scatter/gather 用于描述从 Channel(译者注:Channel 在中文经常翻译为通道)中读取或者写入到 Channel 的操作。 分散(scatter)从 Channel 中读取是指在读操作时将读取的数据写入多个 buffer 中。因此,Channel 将从 Channel 中读取的数据 “分散(scatter)” 到多个 Buffer 中。 聚集(gather)写入 Channel 是指在写操作时将多个 buffer 的数据写入同一个 Channel,因此,Channel 将多个 Buffer 中的数据 “聚集(gather)” 后发送到 Channel。 代码示例如下:

ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body   = ByteBuffer.allocate(1024);
//write data into buffers
ByteBuffer[] bufferArray = { header, body };
channel.write(bufferArray);

通道之间的数据传输

在 Java NIO 中,如果两个通道中有一个是 FileChannel,那你可以直接将数据从一个 channel(译者注:channel 中文常译作通道)传输到另外一个 channel。 transferFrom():FileChannel 的 transferFrom() 方法可以将数据从源通道传输到 FileChannel 中(译者注:这个方法在 JDK 文档中的解释为将字节从给定的可读取字节通道传输到此通道的文件中)。 transferTo():transferTo() 方法将数据从 FileChannel 传输到其他的 channel 中。 示例代码如下:

RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw");
FileChannel      fromChannel = fromFile.getChannel();
RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw");
FileChannel      toChannel = toFile.getChannel();
long position = 0;
long count = fromChannel.size();
fromChannel.transferTo(position, count, toChannel);

选择器

Selector(选择器)是 Java NIO 中能够检测一到多个 NIO 通道,并能够知晓通道是否为诸如读写事件做好准备的组件。这样,一个单独的线程可以管理多个 channel,从而管理多个网络连接。 完整的示例如下:

Selector selector = Selector.open();
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
while(true) {
  int readyChannels = selector.select();
  if(readyChannels == 0) continue;
  Set selectedKeys = selector.selectedKeys();
  Iterator keyIterator = selectedKeys.iterator();
  while(keyIterator.hasNext()) {
    SelectionKey key = keyIterator.next();
    if(key.isAcceptable()) {
        // a connection was accepted by a ServerSocketChannel.
    } else if (key.isConnectable()) {
        // a connection was established with a remote server.
    } else if (key.isReadable()) {
        // a channel is ready for reading
    } else if (key.isWritable()) {
        // a channel is ready for writing
    }
    keyIterator.remove();
  }
}

Pipe ( NIO 管道 )

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

创建管道

通过Pipe.open()方法打开管道。例如:

Pipe pipe = Pipe.open();

向管道写数据

要向管道写数据,需要访问 sink 通道。像这样:

Pipe.SinkChannel sinkChannel = pipe.sink();

通过调用 SinkChannel 的write()方法,将数据写入SinkChannel, 像这样:

String newData = "New String to write to file..." + System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());
buf.flip();
while(buf.hasRemaining()) {
    sinkChannel.write(buf);
}

从管道读取数据

从读取管道的数据,需要访问 source 通道,像这样:

Pipe.SourceChannel sourceChannel = pipe.source();

调用 source 通道的read()方法来读取数据,像这样:

ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = sourceChannel.read(buf);

Java NIO 与 IO

当学习了 Java NIO 和 IO 的 API 后,一个问题马上涌入脑海: 我应该何时使用 IO,何时使用 NIO 呢?在本文中,我会尽量清晰地解析 Java NIO 和 IO 的差异、它们的使用场景,以及它们如何影响您的代码设计。 Java NIO 和 IO 的主要区别

IO

NIO

面向流

面向缓冲

阻塞IO

非阻塞IO

选择器

作 者:ChanghuiN 原文链接:http://www.hchstudio.cn/article/2016/692b/ 版权声明:非特殊声明均为本站原创作品,转载时请注明作者和原文链接。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Linyb极客之路

Java高级特性——注解,这也许是最简单易懂的文章了

博主在初学注解的时候看到网上的介绍大部分都是直接介绍用法或者功能,没有实际的应用场景,篇幅又很长导致学习的时候难以理解其意图,而且学完就忘QAQ。本篇文章中我将...

11920
来自专栏微信公众号:Java团长

Java高级特性——注解,这也许是最简单易懂的文章了

博主在初学注解的时候看到网上的介绍大部分都是直接介绍用法或者功能,没有实际的应用场景,篇幅又很长导致学习的时候难以理解其意图,而且学完就忘QAQ。本篇文章中我将...

10230
来自专栏水击三千

Android Geocoder(位置解析)

Android中提供GPS定位服务,同时开发者可以对获得的位置信息进行解析,可以获得位置的详细信息。 1.gps定位 在Eclipse中建立android应用程...

303100
来自专栏jeremy的技术点滴

Java NIO中Buffer使用备忘

21550
来自专栏Java编程技术

FutureTask 原理

如上代码主线程会在futureTask.get()出阻塞直到task任务执行完毕,并且会返回结果。

10410
来自专栏菩提树下的杨过

JAVA CDI 学习(3) - @Produces及@Disposes

上一节学习了注入Bean的生命周期,今天再来看看另一个话题: Bean的生产(@Produces)及销毁(@Disposes),这有点象设计模式中的工厂模式。在...

22650
来自专栏DOTNET

asp.net web api 下载之断点续传

一、基本思想 利用 HTTP 请求的Range标头值,来向服务端传递请求数据的开始位置和结束位置。服务端获得这两个参数后,将指定范围内的数据传递给客户端。当客户...

481120
来自专栏xingoo, 一个梦想做发明家的程序员

【插件开发】—— 9 编辑器代码分块着色-高亮显示!

前文回顾: 1 插件学习篇 2 简单的建立插件工程以及模型文件分析 3 利用扩展点,开发透视图 4 SWT编程须知 5 SWT简单控件的使用与布局搭...

29460
来自专栏函数式编程语言及工具

ScalaPB(1): using protobuf in akka

31930
来自专栏移动开发之家

Android注解快速入门和实用解析

文章较长,欢迎收藏后浅斟慢酌。主要介绍和分析了 RUNTIME 和 CLASS 下两种注解的使用,也欢迎讨论留言。

7710

扫码关注云+社区

领取腾讯云代金券