java.io 核心概念是流,即面向流的编程,在java中一个流只能是输入流或者输出流,不能同时具有两个概念。

java.nio核心是 selector、Channel、Buffer ,是面向缓冲区(buffer)或者面向块block。

一、Buffer 

Buffer本身是一个内存块,底层是数组,数据的读写都是通过Buffer类实现的。即同一个Buffer即可以写数据也可以读数据,通过intBuffer.flip()方法进行Buffer位置状态的翻转。JAVA中的8中基本类型都有各自对应的Buffer。

缓冲区buffer主要是和通道数据交互,即从通道中读入数据到缓冲区,和从缓冲区中把数据写入到通道中,通过这样完成对数据的传输。 它通过几个变量来保存这个数据的当前位置状态。

Buffer中的四个核心变量

  • 容量(Capacity):缓冲区能够容纳的数据元素的最大数量。这一个容量在缓冲区创建时被设定,并且永远不能改变。
  • 界限(Limit):指定还有多少数据需要取出(在从缓冲区写入通道时),或者还有多少空间可以放入数据(在从通道读入缓冲区时)。
  • 位置(Position):指定了下一个将要被写入或者读取的元素索引,它的值由get()/put()方法自动更新,在新创建一个Buffer对象时,position被初始化为0。
  • 标记(Mark):下一个要被读或写的元素的索引。位置会自动由相应的 get( )和 put( )函数更新

get()方法从缓冲区中读取数据写入到输出通道,这会导致position的增加而limit保持不变,但position不会超过limit的值。

flip()方法 把limit设置为当前的position值 并且把position设置为 0

clear()方法将Buffer恢复到初始化状态

public class BufferTest {
public static void main(String[] args) throws IOException {
ByteBufferTest();
}
private static void ByteBufferTest(){
//分配新的byte缓冲区,参数为缓冲区容量
//新缓冲区的当前位置将为零,其界限(限制位置)将为其容量,它将具有一个底层实现数组,其数组偏移量将为零。
ByteBuffer byteBuffer=ByteBuffer.allocate(10);
output("初始化缓冲区:",byteBuffer);
for(int i=0;i<byteBuffer.capacity()-1;i++){
byteBuffer.put(Byte.parseByte(new SecureRandom().nextInt(20)+""));
}
output("写入缓冲区9个byte:",byteBuffer);
byteBuffer.flip();
output("使用flip重置元素位置:",byteBuffer);
while (byteBuffer.hasRemaining()){
System.out.print(byteBuffer.get()+"|");
}
System.out.print("\n");
output("使用get读取元素:",byteBuffer);
byteBuffer.clear();
output("恢复初始化态clear:",byteBuffer); }
private static void output(String step, Buffer buffer) {
System.out.println(step + " : ");
System.out.print("capacity: " + buffer.capacity() + ", ");
System.out.print("position: " + buffer.position() + ", ");
System.out.println("limit: " + buffer.limit());
System.out.println("mark: " + buffer.mark());
System.out.println();
}
}
初始化缓冲区: :
capacity: 10, position: 0, limit: 10
mark: java.nio.HeapByteBuffer[pos=0 lim=10 cap=10] 写入缓冲区9个byte: :
capacity: 10, position: 9, limit: 10
mark: java.nio.HeapByteBuffer[pos=9 lim=10 cap=10] 使用flip重置元素位置: :
capacity: 10, position: 0, limit: 9
mark: java.nio.HeapByteBuffer[pos=0 lim=9 cap=10] 读取元素:1|读取元素:16|读取元素:12|读取元素:0|读取元素:17|读取元素:5|读取元素:4|读取元素:13|读取元素:18|
使用get读取元素后: :
capacity: 10, position: 9, limit: 9
mark: java.nio.HeapByteBuffer[pos=9 lim=9 cap=10] 恢复初始化态clear: :
capacity: 10, position: 0, limit: 10
mark: java.nio.HeapByteBuffer[pos=0 lim=10 cap=10]

ByteBuffer.wrap( array ):将一个现有的数组,包装为缓冲区对象

buffer.slice():创建子缓冲区,子缓冲区与原缓冲区是数据共享的

buffer.position( 3 );
buffer.limit( 7 );
ByteBuffer slice = buffer.slice();

只读缓冲区:ByteBuffer readonly = buffer.asReadOnlyBuffer();

只读缓冲区非常简单,可以读取它们,但是不能向它们写入数据。可以通过调用缓冲区的asReadOnlyBuffer()方法,将任何常规缓冲区转 换为只读缓冲区,这个方法返回一个与原缓冲区完全相同的缓冲区,并与原缓冲区共享数据,只不过它是只读的。如果原缓冲区的内容发生了变化,只读缓冲区的内容也随之发生变化。

如果尝试修改只读缓冲区的内容,则会报ReadOnlyBufferException异常。只读缓冲区对于保护数据很有用。创建一个只读的缓冲区可以保证该缓冲区不会被修改。只可以把常规缓冲区转换为只读缓冲区,而不能将只读的缓冲区转换为可写的缓冲区。

二、直接缓冲区 DirectByteBuffer

直接在堆外分配一个内存(即,native memory)来存储数据,程序通过JNI直接将数据读/写到堆外内存中。因为数据直接写入到了堆外内存中,所以这种方式就不会再在JVM管控的堆内再分配内存来存储数据了,也就不存在堆内内存和堆外内存数据拷贝的操作了。这样在进行I/O操作时,只需要将这个堆外内存地址传给JNI的I/O的函数就好了。底层的数据其实是维护在操作系统的内存中,而不是jvm里,DirectByteBuffer里维护了一个引用address指向了数据,从而操作数据。实现zero copy(零拷贝)。

间接内存HeapByteBuffer:对于HeapByteBuffer,数据的分配存储都在jvm堆上,当需要和io设备打交道的时候,会将jvm堆上所维护的byte[]拷贝至堆外内存,然后堆外内存直接和io设备交互。外设之所以要把jvm堆里的数据copy出来再操作,不是因为操作系统不能直接操作jvm内存,而是因为jvm在进行gc(垃圾回收)时,会对数据进行移动,一旦出现这种问题,外设就会出现数据错乱的情况。

直接缓冲区的创建:ByteBuffer buffer = ByteBuffer.allocateDirect( 1024 );

DirectByteBuffer的初始化:

 DirectByteBuffer(int cap) {                   // package-private
super(-1, 0, cap, cap);
boolean pa = VM.isDirectMemoryPageAligned();
int ps = Bits.pageSize();
long size = Math.max(1L, (long)cap + (pa ? ps : 0));
// 保留总分配内存(按页分配)的大小和实际内存的大小
Bits.reserveMemory(size, cap);
long base = 0;
try {
// 通过unsafe.allocateMemory分配堆外内存,并返回堆外内存的基地址
base = unsafe.allocateMemory(size);
} catch (OutOfMemoryError x) {
Bits.unreserveMemory(size, cap);
throw x;
}
unsafe.setMemory(base, size, (byte) 0);
if (pa && (base % ps != 0)) {
// Round up to page boundary
address = base + ps - (base & (ps - 1));
} else {
address = base;
}
// 构建Cleaner对象用于跟踪DirectByteBuffer对象的垃圾回收,以实现当DirectByteBuffer被垃圾回收时,堆外内存也会被释放
cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
att = null;
}
 // Used only by direct buffers
// NOTE: hoisted here for speed in JNI GetDirectBufferAddress
//address就是堆外内存创建好后返回给JVM的地址,JVM内存需要维护的只是DirectByteBuffer对象,而具体数据的管理是由操作系统来管理的
long address;

什么情况下使用堆外内存

  • 堆外内存适用于生命周期中等或较长的对象。( 如果是生命周期较短的对象,在YGC的时候就被回收了,就不存在大内存且生命周期较长的对象在FGC对应用造成的性能影响 )。
  • 直接的文件拷贝操作,或者I/O操作。直接使用堆外内存就能少去内存从用户内存拷贝到系统内存的操作,因为I/O操作是系统内核内存和设备间的通信,而不是通过程序直接和外设通信的。
  • 同时,还可以使用 池+堆外内存 的组合方式,来对生命周期较短,但涉及到I/O操作的对象进行堆外内存的再使用。( Netty中就使用了该方式 )

两种方式的效率比较:

private static void directByteBufferTest()throws IOException{
long start=System.currentTimeMillis();
FileInputStream is=new FileInputStream("F:\\logs\\1g.rar");
FileOutputStream fos=new FileOutputStream("F:\\logs\\2g.rar");
FileChannel fcIs,fcOut;
fcIs=is.getChannel();
fcOut=fos.getChannel();
ByteBuffer directByteBuffer= ByteBuffer.allocateDirect(2048);
while (fcIs.read(directByteBuffer)!=-1){
directByteBuffer.flip();
fcOut.write(directByteBuffer);
directByteBuffer.clear();
}
is.close();
fos.close();
long end=System.currentTimeMillis();
System.out.println("DirectByteBuffer需要时间:"+(end-start));
}
private static void heapByteBufferTest()throws IOException{
long start=System.currentTimeMillis();
FileInputStream is=new FileInputStream("F:\\logs\\1g.rar");
FileOutputStream fos=new FileOutputStream("F:\\logs\\3g.rar");
FileChannel fcIs,fcOut;
fcIs=is.getChannel();
fcOut=fos.getChannel();
ByteBuffer directByteBuffer= ByteBuffer.allocate(2048);
while (fcIs.read(directByteBuffer)!=-1){
directByteBuffer.flip();
fcOut.write(directByteBuffer);
directByteBuffer.clear();
}
is.close();
fos.close();
long end=System.currentTimeMillis();
System.out.println("HeapByteBuffer需要时间:"+(end-start));
}

17行输出:DirectByteBuffer需要时间:30456

35行输出:HeapByteBuffer需要时间:45285

 三、内存映射文件I/O   MappedByteBuffer

内存映射文件I/O是一种读和写文件数据的方法,它可以比常规的基于流或者基于通道的I/O快的多。内存映射文件I/O是通过使文件中的数据出现为 内存数组的内容来完成的,这其初听起来似乎不过就是将整个文件读到内存中,但是事实上并不是这样。一般来说,只有文件中实际读取或者写入的部分才会映射到内存中。

FileChannel提供了map方法来把文件影射为内存映像文件: MappedByteBuffer map(int mode,long position,long size); 可以把文件的从position开始的size大小的区域映射为内存映像文件,映射内存缓冲区是个直接缓冲区,继承自ByteBuffer,但相对于ByteBuffer,它有更多的优点 读取快 写入快  随时随地写入;

mode指出了 可访问该内存映像文件的方式:            
    1、READ_ONLY,(只读): 试图修改得到的缓冲区将导致抛出 ReadOnlyBufferException.(MapMode.READ_ONLY)

2、READ_WRITE(读/写): 对得到的缓冲区的更改最终将传播到文件;该更改对映射到同一文件的其他程序不一定是可见的。 (MapMode.READ_WRITE)
    3、PRIVATE(专用): 对得到的缓冲区的更改不会传播到文件,并且该更改对映射到同一文件的其他程序也不是可见的;相反,会创建缓冲区已修改部分的专用副本。 (MapMode.PRIVATE)

MappedByteBuffer 中的三个方法:

a. fore();缓冲区是READ_WRITE模式下,此方法对缓冲区内容的修改强行写入文件
    b. load()将缓冲区的内容载入内存,并返回该缓冲区的引用
    c. isLoaded()如果缓冲区的内容在物理内存中,则返回真,否则返回假

使用MappedByteBuffer 将数据写入文件:

private static void mappedOutFile()throws IOException{
String str="I Love MappedByteBuffer";
RandomAccessFile raf = new RandomAccessFile( filePath, "rw" );
FileChannel fc = raf.getChannel();
byte [] msg=str.getBytes("UTF-8");
MappedByteBuffer mbb = fc.map( FileChannel.MapMode.READ_WRITE, 0, msg.length);
mbb.put(msg);
fc.write(mbb);
raf.close();
}

NIO基础之Buffer的更多相关文章

  1. NIO 基础之 Buffer

    文章目录 1. 概述 2. 基本属性 3. 创建 Buffer 3.1 关于 Direct Buffer 和 Non-Direct Buffer 的区别 4. 向 Buffer 写入数据 5. 从 B ...

  2. NIO基础操作

    原文链接http://zhhll.icu/2020/05/18/java%E5%9F%BA%E7%A1%80/IO/NIO%E5%9F%BA%E6%9C%AC%E6%93%8D%E4%BD%9C/ N ...

  3. nio之缓冲区(Buffer)理解

    一.缓冲区简介 Nio中的 Buffer 是用于存储特定基础类型的一个容器.为了能熟练的使用 Nio中的各种 Buffer , 我们需要理解 Buffer 中的 三个重要 的属性. 1. capaci ...

  4. Java NIO Channel和Buffer

    Java NIO Channel和Buffer @author ixenos Channel和Buffer的关系 1.NIO速度的提高来自于所使用的结构更接近于OS执行I/O的方式:通道和缓冲器: 2 ...

  5. Java中的NIO基础知识

    上一篇介绍了五种NIO模型,本篇将介绍Java中的NIO类库,为学习netty做好铺垫 Java NIO 由3个核心组成,分别是Channels,Buffers,Selectors.本文主要介绍着三个 ...

  6. Java NIO中的Buffer 详解

    Java NIO中的Buffer用于和NIO通道进行交互.如你所知,数据是从通道读入缓冲区,从缓冲区写入到通道中的.缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存.这块内存被包装成NIO ...

  7. NIO之缓冲区(Buffer)的数据存取

    缓冲区(Buffer) 一个用于特定基本数据类行的容器.有java.nio包定义的,所有缓冲区都是抽象类Buffer的子类. Java NIO中的Buffer主要用于与NIO通道进行交互,数据是从通道 ...

  8. Netty学习笔记(一)——nio基础

    Netty简单认识: 1) Netty 是由JBOSS 提供的一个Java 开源框架. 2) Netty 是一个异步的.基于事件驱动的网络应用框架,用以快速开发高性能.高可靠性的网络I0 程序. 3) ...

  9. NIO(一):Buffer缓冲区

    一.NIO与IO: IO:  一般泛指进行input/output操作(读写操作),Java IO其核心是字符流(inputstream/outputstream)和字节流(reader/writer ...

随机推荐

  1. bzoj 3714 [PA2014]Kuglarz——思路+最小生成树

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=3714 如果用s[ i ]表示前 i 个的奇偶性,那么c(i_j)表示s[ i-1 ]^s[ ...

  2. UltraEdit注册机原理简单说明

    UltraEdit注册机原理 By:大志若愚 UltraEdit 是 Windows 下一款流行的老牌文本/HEX 编辑器(非开源).UltraEdit 正被移植到 Linux 平台.该移植名为 UE ...

  3. VijosP1443:银河英雄传说

    描述 公元五八○一年,地球居民迁移至金牛座α第二行星,在那里发表银河联邦创立宣言,同年改元为宇宙历元年,并开始向银河系深处拓展. 宇宙历七九九年,银河系的两大军事集团在巴米利恩星域爆发战争.泰山压顶集 ...

  4. <c++primer plus>学习笔记1之第八章函数探幽

    1 c++内联函数 编译器将使用相应的函数代码替换函数调用,对于内联代码,函数无需跳到另一个位置执行代码再跳回来,所以内联函数运行速度比常规函数快. 但是代价是需要更多的内存. 使用场合: 执行函数代 ...

  5. API---CreateIoCompletionPort

    HANDLE WINAPI CreateIoCompletionPort(  __in          HANDLE FileHandle,  __in          HANDLE Existi ...

  6. UNPIVOT逆透视以及动态逆透视存储过程

    前几天一直练习PIVOT透视,还实现了动态透视的存过程<动态透视表>https://www.cnblogs.com/insus/p/10888277.html 今天练习MS SQL Ser ...

  7. css border实现三角形

    实现过程: 正常的border <div class="box"></div> .box { background: #ddd; width: 100px; ...

  8. ue4 动画相关方法杂记

    加载资源文件中的 骨骼,动画,蒙太奇----------------------------------------- FName anis("/Game/FirstPerson/Anima ...

  9. 利用外部协议让chrome启动外部应用程序

    http://bbs.kafan.cn/thread-1254526-1-1.html 原理:很简单,标题写的很明确了,不懂的google去. 步骤:举个例子,我要启动D:\Programe file ...

  10. hdu2612(dijkstra)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2612 题意:给出一个n*m的矩阵,' . ' 表示可以走的路, ' # '表示不能走的路 ,’ @'表 ...