ByteBuffer知识

相关背景-->读写操作-->关键属性-->读写测试--> 常用方法-->文件读写

1、数据类型
1.1 基本类型长度
在Java中有很多的基本类型,比如:
byte,一个字节是8位bit,也就是1B
short,16位bit,也就是2B
int,32位bit,也就是4B
long, 64位bit,也就是8B
char,16位bit,也就是2B
float,32位bit,也就是4B
double,64位bit,也就是8B
不同的类型都会按照自己的位数来存储,并且可以自动进行转换提升。
byte、char、short都可以自动提升为int,如果操作数有long,就会自动提升为long,float和double也是如此。
1.2 大端小端
由于一个数据类型可能有很多个字节组成的,那么它们是如何摆放的。这个是有讲究的:
大端:低地址位 存放 高有效字节
小端:低地址位 存放 低有效字节
举个例子,一个char是有两个字节组成的,这两个字节存储可能会显示成如下的模样,比如字符a:
1
2
3 低地址位 高地址位
大端; 00 96
小端: 96 00
1.3 String与new String的区别
说说”hello”和new String(“hello”)的区别:
如果是”hello”,JVM会先去共享的字符串池中查找,有没有”hello”这个词,如果有直接返回它的引用;如果没有,就会创建这个对象,再返回。因此,”a”+”b”相当于存在3个对象,分别是”a”、”b”、”ab”。
而new String(“hello”),则省去了查找的过程,直接就创建一个hello的对象,并且返回引用。
2、读写操作
在直接内存中,通过allocateDirect(int byte_length)申请直接内存。这段内存可以理解为一段普通的基于Byte的数组,因此插入和读取都跟普通的数组差不多。
只不过提供了基于不同数据类型的插入方法,比如:
put(byte) 插入一个byte
put(byte[]) 插入一个byte数组
putChar(char) 插入字符
putInt(int) 插入Int
putLong(long) 插入long
等等….详细的使用方法,也可以参考下面的图片:

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

注意所有没有index参数的方法,都是按照当前position的位置进行操作的。
3、关键属性
它有几个关键的指标:
1 mark–>position–>limit–>capacity
另外,还有remaining=limit-position。
3.1 当前位置——position

position是当前数组的指针,指示当前数据位置。举个例子: 
 1 
 2 
 3 
 4 
 5 
 6 
 7 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的位置分别是:
1 2,4,8
注意,Position的位置是插入数据的当前位置,如果插入数据,就会自动后移。
也就是说,如果存储的是两个字节的数据,position的位置是在第三个字节上,下标就是2。

1 
 2 
 3 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可以通过position()获得,也可以通过position(int)设置。 
 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 //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大
3.2 空间容量——capacity
capacity是当前申请的直接内存的容量,它是申请后就不会改变的。
capacity则可以通过capacity()方法获得。
3.3 限制大小——limit
我们可能想要改变这段直接内存的大小,因此可以通过一个叫做Limit的属性设置。
limit则可以通过limit()获得,通过limit(int)进行设置。
注意limit要比mark和position大,比capacity小。

1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 //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; 
 }


3.4 标记位置——mark
mark,就是一个标记为而已,记录当前的position的值。常用的场景,就是记录某一次插入数据的位置,方便下一次进行回溯。
可以使用mark()方法进行标记,
使用reset()方法进行清除,
使用rewind()方法进行初始化

1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 //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; 
 } 
  
 使用案例 
  
 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 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);

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

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

3.5 剩余空间——remaing

remaing则表示当前的剩余空间: 
 1 
 2 
 3 public final int remaining() { 
 return limit - position; 
 }


4、读写测试
写操作主要就是按照自己的数据类型,写入到直接内存中,注意每次写入数据的时候,position都会自动加上写入数据的长度,指向下一个该写入的起始位置:
下面看看如何写入一段byte[]或者字符串:

1 
 2 
 3 
 4 
 5 
 6 
 7 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);


输出的内容为:

1 
 2 写byte[]后 java.nio.DirectByteBuffer[pos=2 lim=10 cap=10] 
 写string后 java.nio.DirectByteBuffer[pos=5 lim=10 cap=10] 
 读的时候,可以通过一个外部的byte[]数组进行读取。由于没有找到直接操作直接内存的方法: 因此如果想在JVM应用中使用直接内存,需要申请一段堆中的空间,存放数据。 
 如果有更好的方法,还请留言。 
 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 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);
输出为

1 
 2 
 3 
 4 
 5 
 6 
 7 刚写完数据 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]


5、常用方法
上面的读写例子中,有几个常用的方法:
5.1 clear()
这个方法用于清除mark和position,还有limit的位置:

1 
 2 
 3 
 4 
 5 
 6 public final Buffer clear() { 
 position = 0; 
 limit = capacity; 
 mark = -1; 
 return this; 
 }


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

1 
 2 
 3 
 4 
 5 
 6 public final Buffer flip() { 
 limit = position; 
 position = 0; 
 mark = -1; 
 return this; 
 }


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

1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 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;

}
比如一段空间内容为:
1 123456789
当position的位置在2时,调用compact方法,会变成:
1 345678989
5.4 isDirect()
这个方法用于判断是否是直接内存。如果是返回true,如果不是返回false。
5.5 rewind()
这个方法用于重置mark标记:
1
2
3
4
5 public final Buffer rewind() {
position = 0;
mark = -1;
return this;
}

6、文件读写

package testdemo;


import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

/**
* @Description    ReadByteBufferTest Read A File Into ByteBuffer, And Then Write Into A File.
* @Author         0262000099 Hengtai Nie
* @CreateDate     2018/9/4 19:42
* @Version        V2.00.01.01
*/
public class ReadByteBufferTest {

  public static void main(String[] args) throws IOException {
    FileInputStream fileInputStream = new FileInputStream("F:\\gpudemo\\test\\test.txt");
    FileOutputStream fileOutputStream = new FileOutputStream("F:\\gpudemo\\test\\" + "testresult" + "ReadByteBufferTest" + ".txt");
    FileChannel inChannel = fileInputStream.getChannel();
    FileChannel outChannel= fileOutputStream.getChannel();

    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

    long start = System.currentTimeMillis();
    while(true)
    {
      int eof = inChannel.read(byteBuffer);
      if(eof == -1 ) {
        break;
      }
      byteBuffer.flip();
      outChannel.write(byteBuffer);
      byteBuffer.clear();
    }
    System.out.println("spending : " + (System.currentTimeMillis()-start));
    inChannel.close();
    outChannel.close();
  }
}