Java直接(堆外)内存使用详解

更新时间:2016-10-20 14:14:12 点击次数:1833次

本篇主要讲解如何使用直接内存(堆外内存),并按照下面的步骤进行说明:

相关背景–>读写操作–>关键属性–>读写实践–>扩展–>参考说明 
希望对想使用直接内存的朋友,提供点快捷的参考。

数据类型

下面这些,都是在使用DirectBuffer中必备的一些常识,暂作了解吧!如果想要深入理解,可以看看下面参考的那些博客。

基本类型长度 
在Java中有很多的基本类型,比如:

不同的类型都会按照自己的位数来存储,并且可以自动进行转换提升。 
byte、char、short都可以自动提升为int,如果操作数有long,就会自动提升为long,float和double也是如此。

大端小端

由于一个数据类型可能有很多个字节组成的,那么它们是如何摆放的。这个是有讲究的:

举个例子,一个char是有两个字节组成的,这两个字节存储可能会显示成如下的模样,比如字符a: 
图片描述

String与new String的区别 
再说说”hello”和new String(“hello”)的区别:

如果是”hello”,JVM会先去共享的字符串池中查找,有没有”hello”这个词,如果有直接返回它的引用;如果没有,就会创建这个对象,再返回。因此,”a”+”b”相当于存在3个对象,分别是”a”、”b”、”ab”。

而new String(“hello”),则省去了查找的过程,直接就创建一个hello的对象,并且返回引用。 
读写数据

在直接内存中,通过allocateDirect(int byte_length)申请直接内存。这段内存可以理解为一段普通的基于Byte的数组,因此插入和读取都跟普通的数组差不多。

只不过提供了基于不同数据类型的插入方法,比如:

等等….详细的使用方法,也可以参考下面的图片: 
图片描述

对应读取数据,跟写入差不多:

图片描述
注意所有没有index参数的方法,都是按照当前position的位置进行操作的。

下面看看什么是position,还有什么其他的属性吧!

基本的属性值

它有几个关键的指标:

mark–>position–>limit–>capacity 
另外,还有remaining=limit-position。

先说说他们的意思吧!

当前位置——position

position是当前数组的指针,指示当前数据位置。举个例子:

ByteBuffer buffer = ByteBuffer.allocateDirect(1024); buffer.putChar('a'); System.out.println(buffer); buffer.putChar('c'); System.out.println(buffer); buffer.putInt(10); System.out.println(buffer);

由于一个char是2个字节,一个Int是4个字节,因此position的位置分别是:

2,4,8

注意,Position的位置是插入数据的当前位置,如果插入数据,就会自动后移。 
也就是说,如果存储的是两个字节的数据,position的位置是在第三个字节上,下标就是2。

java.nio.DirectByteBuffer[pos=2 lim=1024 cap=1024]
java.nio.DirectByteBuffer[pos=4 lim=1024 cap=1024]
java.nio.DirectByteBuffer[pos=8 lim=1024 cap=1024]
//position(int)方法的源码 public final Buffer position(int newPosition) { if ((newPosition > limit) || (newPosition < 0)) throw new IllegalArgumentException();
        position = newPosition; if (mark > position) mark = -1; return this;
    }

注意:position的位置要比limit小,比mark大

空间容量——capacity

capacity是当前申请的直接内存的容量,它是申请后就不会改变的。

我们可能想要改变这段直接内存的大小,因此可以通过一个叫做Limit的属性设置。

注意limit要比mark和position大,比capacity小。

//limit(int)方法的源码 public final Buffer limit(int newLimit) { if ((newLimit > capacity) || (newLimit < 0)) throw new IllegalArgumentException();
        limit = newLimit; if (position > limit) position = limit; if (mark > limit) mark = -1; return this;
    }

标记位置——mark 
mark,就是一个标记为而已,记录当前的position的值。常用的场景,就是记录某一次插入数据的位置,方便下一次进行回溯。

//mark方法标记当前的position,默认为-1 public final Buffer mark() {
mark = position; return this;
} //reset方法重置mark的位置,position的位置,不能小于mark的位置,否则会出错 public final Buffer reset() { int m = mark; if (m < 0) throw new InvalidMarkException();
position = m; return this;
} //重置mark为-1.position为0 public final Buffer rewind() {
position = 0;
mark = -1; return this;
}

使用案例

ByteBuffer buffer = ByteBuffer.allocateDirect(1024); buffer.putChar('a'); buffer.putChar('c'); System.out.println("插入完数据 " + buffer); buffer.mark();// 记录mark的位置 buffer.position(30);// 设置的position一定要比mark大,否则mark无法重置 System.out.println("reset前 " + buffer); buffer.reset();// 重置reset ,reset后的position=mark System.out.println("reset后 " + buffer); buffer.rewind();//清除标记,position变成0,mark变成-1 System.out.println("清除标记后 " + buffer);

可以看到如下的运行结果:

插入完数据 java.nio.DirectByteBuffer[pos=4 lim=1024 cap=1024] resetjava.nio.DirectByteBuffer[pos=30 lim=1024 cap=1024] resetjava.nio.DirectByteBuffer[pos=4 lim=1024 cap=1024] 清除标记后 java.nio.DirectByteBuffer[pos=0 lim=1024 cap=1024]

剩余空间——remaing

remaing则表示当前的剩余空间:

 public final int remaining() { return limit - position;
    }

读写实践

写操作主要就是按照自己的数据类型,写入到直接内存中,注意每次写入数据的时候,position都会自动加上写入数据的长度,指向下一个该写入的起始位置:

下面看看如何写入一段byte[]或者字符串:

ByteBuffer buffer = ByteBuffer.allocateDirect(10); byte[] data = {1,2}; buffer.put(data); System.out.println("写byte[]后 " + buffer); buffer.clear(); buffer.put("hello".getBytes()); System.out.println("写string后 " + buffer);

输出的内容为:

byte[]后 java.nio.DirectByteBuffer[pos=2 lim=10 cap=10]stringjava.nio.DirectByteBuffer[pos=5 lim=10 cap=10]

读的时候,可以通过一个外部的byte[]数组进行读取。由于没有找到直接操作直接内存的方法: 因此如果想在JVM应用中使用直接内存,需要申请一段堆中的空间,存放数据。

如果有更好的方法,还请留言。

ByteBuffer buffer = ByteBuffer.allocateDirect(10);
buffer.put(new byte[]{1,2,3,4});
System.out.println("刚写完数据 " +buffer);
buffer.flip();
System.out.println("flip之后 " +buffer); byte[] target = new byte[buffer.limit()];
buffer.get(target);//自动读取target.length个数据 for(byte b : target){
    System.out.println(b);
}
System.out.println("读取完数组 " +buffer);

输出为

刚写完数据 java.nio.DirectByteBuffer[pos=4 lim=10 cap=10] flip之后 java.nio.DirectByteBuffer[pos=0 lim=4 cap=10] 1 2 3 4 读取完数组 java.nio.DirectByteBuffer[pos=4 lim=4 cap=10]

常用方法

上面的读写例子中,有几个常用的方法:

clear() 
这个方法用于清除mark和position,还有limit的位置:

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

flip() 
这个方法主要用于改变当前的Position为limit,主要是用于读取操作。

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

compact() 
这个方法在读取一部分数据的时候比较常用。 
它会把当前的Position移到0,然后position+1移到1。

 public ByteBuffer compact() { int pos = position(); int lim = limit();
        assert (pos <= lim); int rem = (pos <= lim ? lim - pos : 0); unsafe.copyMemory(ix(pos), ix(0), rem << 0);
        position(rem);
        limit(capacity());
        discardMark(); return this;
    }

比如一段空间内容为:

123456789

当position的位置在2时,调用compact方法,会变成:

345678989

isDirect() 
这个方法用于判断是否是直接内存。如果是返回true,如果不是返回false。

rewind() 
这个方法用于重置mark标记:

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

本站文章版权归原作者及原出处所有 。内容为作者个人观点, 并不代表本站赞同其观点和对其真实性负责,本站只提供参考并不构成任何投资及应用建议。本站是一个个人学习交流的平台,网站上部分文章为转载,并不用于任何商业目的,我们已经尽可能的对作者和来源进行了通告,但是能力有限或疏忽,造成漏登,请及时联系我们,我们将根据著作权人的要求,立即更正或者删除有关内容。本站拥有对此声明的最终解释权。

回到顶部
嘿,我来帮您!