热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

NIO基础(第九天)

什么是NIOJavaNIO(NewIO)是一个可以替代标准JavaIOAPI的IOAPI(从Java1.4开始),JavaNIO提供了与标准IO不同的

什么是NIO

Java NIO(New IO)是一个可以替代标准Java IO API的IO API(从Java 1.4开始),Java NIO提供了与标准IO不同的IO工作方式。

Java NIO: Channels and Buffers(通道和缓冲区)
标准的IO基于字节流和字符流进行操作的,而NIO是基于通道(Channel)和缓冲区(Buffer)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。

Java NIO: Non-blocking IO(非阻塞IO)
Java NIO可以让你非阻塞的使用IO,例如:当线程从通道读取数据到缓冲区时,线程还是可以进行其他事情。当数据被写入到缓冲区时,线程可以继续处理它。从缓冲区写入通道也类似。

Java NIO: Selectors(选择器)
Java NIO引入了选择器的概念,选择器用于监听多个通道的事件(比如:连接打开,数据到达)。因此,单个的线程可以监听多个数据通道。

注意:传统IO是单向,NIO是双向的。


Buffer的数据存取

一个用于特定基本数据类行的容器。由java.nio包定义的,所有缓冲区都是抽象类Buffer的子类。

Java NIO中的Buffer主要用于与NIO通道进行交互,数据是从通道读入到缓冲区,从缓冲区写入通道中的。Buffer就像一个数组,可以保存多个相同类型的数据。根据类型不同(boolean除外),有以下Buffer常用子类:

ByteBuffer,CharBuffer,ShortBuffer,IntBuffer,LongBuffer,FloatBuffer,DoubleBuffer。


Buffer的概述


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

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

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

  4. 标记(mark)与重置(reset):标记是一个索引,通过Buffer中的mark()方法指定Buffer中一个特定的position,之后可以通过调用reset()方法恢复到这个position。

/*** (缓冲区)buffer 用于NIO存储数据 支持多种不同的数据类型* 1.byteBuffer* 2.charBuffer* 3.shortBuffer* 4.IntBuffer* 5.LongBuffer * 6.FloatBuffer* 7.DubooBuffer* 上述缓冲区管理的方式 几乎* 通过allocate() 获取缓冲区* 二、缓冲区核心的方法 put 存入数据到缓冲区 get 获取缓冲区数据 flip 开启读模式* 三、缓冲区四个核心属性* capacity:缓冲区最大容量,一旦声明不能改变。 limit:界面(缓冲区可以操作的数据大小) limit后面的数据不能读写。* position:缓冲区正在操作的位置*/
public class TestNIO {public static void main(String[] args) {// 1.指定缓冲区大小1024ByteBuffer buf = ByteBuffer.allocate(1024);System.out.println("--------------------");System.out.println(buf.position());System.out.println(buf.limit());System.out.println(buf.capacity());// 2.向缓冲区存放5个数据buf.put("abcd1".getBytes());System.out.println("--------------------");System.out.println(buf.position());System.out.println(buf.limit());System.out.println(buf.capacity());// 3.开启读模式buf.flip();System.out.println("----------开启读模式...----------");System.out.println(buf.position());System.out.println(buf.limit());System.out.println(buf.capacity());byte[] bytes = new byte[buf.limit()];buf.get(bytes);System.out.println(new String(bytes, 0, bytes.length));System.out.println("----------重复读模式...----------");// 4.开启重复读模式buf.rewind();System.out.println(buf.position());System.out.println(buf.limit());System.out.println(buf.capacity());byte[] bytes2 = new byte[buf.limit()];buf.get(bytes2);System.out.println(new String(bytes2, 0, bytes2.length));// 5.clean 清空缓冲区 数据依然存在,只不过数据被遗忘System.out.println("----------清空缓冲区...----------");buf.clear();System.out.println(buf.position());System.out.println(buf.limit());System.out.println(buf.capacity());System.out.println((char)buf.get());}}

make与rest用法

标记(mark)与重置(reset):标记是一个索引,通过Buffer中的mark()方法指定Buffer中一个特定的position,之后可以通过调用reset()方法恢复到这个position。

public class Test002 {public static void main(String[] args) {ByteBuffer buf = ByteBuffer.allocate(1024);String str = "abcd1";buf.put(str.getBytes());// 开启读取模式buf.flip();byte[] dst = new byte[buf.limit()];buf.get(dst, 0, 2);buf.mark();System.out.println(new String(dst, 0, 2));System.out.println(buf.position());buf.get(dst, 2, 2);System.out.println(new String(dst, 2, 2));System.out.println(buf.position());buf.reset();System.out.println("重置恢复到mark位置..");System.out.println(buf.position());}}

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

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

直接缓冲区:通过 allocateDirect() 方法分配直接缓冲区,将缓冲区建立在物理内存中。可以提高效率。


  1. 字节缓冲区要么是直接的,要么是非直接的。如果为直接字节缓冲区,则 Java虚拟机会尽最大努力直接在此缓冲区上执行本机 I/O 操作。也就是说,在每次调用基础操作系统的一个本机 I/O 操作之前(或之后),虚拟机都会尽量避免将缓冲区的内容复制到中间缓冲区中(或从中间缓冲区中复制内容)。

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

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

// 使用直接缓冲区完成文件的复制(内存映射文件)
public void test2() throws IOException {long start = System.currentTimeMillis();FileChannel inChannel = FileChannel.open(Paths.get("f://1.mp4"), StandardOpenOption.READ);FileChannel outChannel = FileChannel.open(Paths.get("f://2.mp4"), StandardOpenOption.WRITE,StandardOpenOption.READ, StandardOpenOption.CREATE);// 内存映射文件MappedByteBuffer inMappedByteBuf = inChannel.map(MapMode.READ_ONLY, 0, inChannel.size());MappedByteBuffer outMappedByteBuffer = outChannel.map(MapMode.READ_WRITE, 0, inChannel.size());// 直接对缓冲区进行数据的读写操作byte[] dsf = new byte[inMappedByteBuf.limit()];inMappedByteBuf.get(dsf);outMappedByteBuffer.put(dsf);inChannel.close();outChannel.close();long end = System.currentTimeMillis();System.out.println(end - start);
}// 1.利用通道完成文件的复制(非直接缓冲区)
public void test1() throws IOException { // 4400long start = System.currentTimeMillis();FileInputStream fis = new FileInputStream("f://1.mp4");FileOutputStream fos = new FileOutputStream("f://2.mp4");// ①获取通道FileChannel inChannel = fis.getChannel();FileChannel outChannel = fos.getChannel();// ②分配指定大小的缓冲区ByteBuffer buf = ByteBuffer.allocate(1024);while (inChannel.read(buf) != -1) {buf.flip();// 切换为读取数据// ③将缓冲区中的数据写入通道中outChannel.write(buf);buf.clear();}outChannel.close();inChannel.close();fos.close();fis.close();long end = System.currentTimeMillis();System.out.println(end - start);
}

通道(Channel)的原理获取

通道表示打开到 IO 设备(例如:文件、套接字)的连接。若需要使用 NIO 系统,需要获取用于连接 IO 设备的通道以及用于容纳数据的缓冲区。然后操作缓冲区,对数据进行处理。Channel 负责传输, Buffer 负责存储。通道是由 java.nio.channels 包定义的。 Channel 表示 IO 源与目标打开的连接。Channel 类似于传统的“流”。只不过 Channel本身不能直接访问数据, Channel 只能与Buffer 进行交互。

java.nio.channels.Channel 接口:|--FileChannel|--SocketChannel|--ServerSocketChannel|--DatagramChannel获取通道1. Java 针对支持通道的类提供了 getChannel() 方法本地 IO:FileInputStream/FileOutputStreamRandomAccessFile网络IO:SocketServerSocketDatagramSocket2. 在 JDK 1.7 中的 NIO.2 针对各个通道提供了静态方法 open()3. 在 JDK 1.7 中的 NIO.2 的 Files 工具类的 newByteChannel()

@Test
// 使用直接缓冲区完成文件的复制(內存映射文件)
public void test2() throws IOException {FileChannel inChannel = FileChannel.open(Paths.get("1.png"), StandardOpenOption.READ);FileChannel outChannel = FileChannel.open(Paths.get("2.png"), StandardOpenOption.READ, StandardOpenOption.WRITE,StandardOpenOption.CREATE);// 映射文件MappedByteBuffer inMapperBuff = inChannel.map(MapMode.READ_ONLY, 0, inChannel.size());MappedByteBuffer outMapperBuff = outChannel.map(MapMode.READ_WRITE, 0, inChannel.size());// 直接对缓冲区进行数据读写操作byte[] dst = new byte[inMapperBuff.limit()];inMapperBuff.get(dst);outMapperBuff.put(dst);outChannel.close();inChannel.close();
}@Test
// 1.利用通道完成文件复制(非直接缓冲区)
public void test1() throws IOException {FileInputStream fis = new FileInputStream("1.png");FileOutputStream fos = new FileOutputStream("2.png");// ①获取到通道FileChannel inChannel = fis.getChannel();FileChannel outChannel = fos.getChannel();// ②分配指定大小的缓冲区ByteBuffer buf = ByteBuffer.allocate(1024);while (inChannel.read(buf) != -1) {buf.flip();// 切换到读取模式outChannel.write(buf);buf.clear();// 清空缓冲区}// 关闭连接outChannel.close();inChannel.close();fos.close();fis.close();
}

直接缓冲区与非直接缓冲耗时计算

@Test
// 使用直接缓冲区完成文件的复制(內存映射文件) //428、357
public void test2() throws IOException {long startTime = System.currentTimeMillis();FileChannel inChannel = FileChannel.open(Paths.get("f://1.mp4"), StandardOpenOption.READ);FileChannel outChannel = FileChannel.open(Paths.get("f://2.mp4"), StandardOpenOption.READ, StandardOpenOption.WRITE,StandardOpenOption.CREATE);// 映射文件MappedByteBuffer inMapperBuff = inChannel.map(MapMode.READ_ONLY, 0, inChannel.size());MappedByteBuffer outMapperBuff = outChannel.map(MapMode.READ_WRITE, 0, inChannel.size());// 直接对缓冲区进行数据读写操作byte[] dst = new byte[inMapperBuff.limit()];inMapperBuff.get(dst);outMapperBuff.put(dst);outChannel.close();inChannel.close();long endTime = System.currentTimeMillis();System.out.println("内存映射文件耗时:"+(endTime-startTime));
}@Test
// 1.利用通道完成文件复制(非直接缓冲区)
public void test1() throws IOException { //11953 、3207、3337long startTime = System.currentTimeMillis();FileInputStream fis = new FileInputStream("f://1.mp4");FileOutputStream fos = new FileOutputStream("f://2.mp4");// ①获取到通道FileChannel inChannel = fis.getChannel();FileChannel outChannel = fos.getChannel();// ②分配指定大小的缓冲区ByteBuffer buf = ByteBuffer.allocate(1024);while (inChannel.read(buf) != -1) {buf.flip();// 切换到读取模式outChannel.write(buf);buf.clear();// 清空缓冲区}// 关闭连接outChannel.close();inChannel.close();fos.close();fis.close();long endTime = System.currentTimeMillis();System.out.println("非缓冲区:"+(endTime-startTime));
}

分散读取与聚集写入

分散读取(scattering Reads):将通道中的数据分散到多个缓冲区中。

聚集写入(gathering Writes):将多个缓冲区的数据聚集到通道中。

RandomAccessFile raf1 = new RandomAccessFile("test.txt", "rw");
// 1.获取通道
FileChannel channel = raf1.getChannel();
// 2.分配指定大小的指定缓冲区
ByteBuffer buf1 = ByteBuffer.allocate(100);
ByteBuffer buf2 = ByteBuffer.allocate(1024);
// 3.分散读取
ByteBuffer[] bufs = { buf1, buf2 };
channel.read(bufs);
for (ByteBuffer byteBuffer : bufs) {// 切换为读取模式byteBuffer.flip();
}
System.out.println(new String(bufs[0].array(), 0, bufs[0].limit()));
System.out.println("------------------分算读取线分割--------------------");
System.out.println(new String(bufs[1].array(), 0, bufs[1].limit()));
// 聚集写入
RandomAccessFile raf2 = new RandomAccessFile("2.txt", "rw");
FileChannel channel2 = raf2.getChannel();
channel2.write(bufs);

推荐阅读
  • Java中包装类的设计原因以及操作方法
    本文主要介绍了Java中设计包装类的原因以及操作方法。在Java中,除了对象类型,还有八大基本类型,为了将基本类型转换成对象,Java引入了包装类。文章通过介绍包装类的定义和实现,解答了为什么需要包装类的问题,并提供了简单易用的操作方法。通过本文的学习,读者可以更好地理解和应用Java中的包装类。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • VScode格式化文档换行或不换行的设置方法
    本文介绍了在VScode中设置格式化文档换行或不换行的方法,包括使用插件和修改settings.json文件的内容。详细步骤为:找到settings.json文件,将其中的代码替换为指定的代码。 ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • PHP中的单例模式与静态变量的区别及使用方法
    本文介绍了PHP中的单例模式与静态变量的区别及使用方法。在PHP中,静态变量的存活周期仅仅是每次PHP的会话周期,与Java、C++不同。静态变量在PHP中的作用域仅限于当前文件内,在函数或类中可以传递变量。本文还通过示例代码解释了静态变量在函数和类中的使用方法,并说明了静态变量的生命周期与结构体的生命周期相关联。同时,本文还介绍了静态变量在类中的使用方法,并通过示例代码展示了如何在类中使用静态变量。 ... [详细]
  • 本文介绍了一个在线急等问题解决方法,即如何统计数据库中某个字段下的所有数据,并将结果显示在文本框里。作者提到了自己是一个菜鸟,希望能够得到帮助。作者使用的是ACCESS数据库,并且给出了一个例子,希望得到的结果是560。作者还提到自己已经尝试了使用"select sum(字段2) from 表名"的语句,得到的结果是650,但不知道如何得到560。希望能够得到解决方案。 ... [详细]
  • 前景:当UI一个查询条件为多项选择,或录入多个条件的时候,比如查询所有名称里面包含以下动态条件,需要模糊查询里面每一项时比如是这样一个数组条件:newstring[]{兴业银行, ... [详细]
  • C++字符字符串处理及字符集编码方案
    本文介绍了C++中字符字符串处理的问题,并详细解释了字符集编码方案,包括UNICODE、Windows apps采用的UTF-16编码、ASCII、SBCS和DBCS编码方案。同时说明了ANSI C标准和Windows中的字符/字符串数据类型实现。文章还提到了在编译时需要定义UNICODE宏以支持unicode编码,否则将使用windows code page编译。最后,给出了相关的头文件和数据类型定义。 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • Linux重启网络命令实例及关机和重启示例教程
    本文介绍了Linux系统中重启网络命令的实例,以及使用不同方式关机和重启系统的示例教程。包括使用图形界面和控制台访问系统的方法,以及使用shutdown命令进行系统关机和重启的句法和用法。 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • 在说Hibernate映射前,我们先来了解下对象关系映射ORM。ORM的实现思想就是将关系数据库中表的数据映射成对象,以对象的形式展现。这样开发人员就可以把对数据库的操作转化为对 ... [详细]
  • 本文介绍了游标的使用方法,并以一个水果供应商数据库为例进行了说明。首先创建了一个名为fruits的表,包含了水果的id、供应商id、名称和价格等字段。然后使用游标查询了水果的名称和价格,并将结果输出。最后对游标进行了关闭操作。通过本文可以了解到游标在数据库操作中的应用。 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
author-avatar
记忆里的Angle
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有