`
airu
  • 浏览: 267456 次
  • 性别: Icon_minigender_1
  • 来自: 云南
社区版块
存档分类
最新评论

Java NIO 缓冲区基础二

 
阅读更多
上一篇讲了基本的缓冲区概念,以及NIO的缓冲区属性以及部分操作。
现在让我们在看看缓冲区的一些细节。
1、缓冲区的创建
之前也许你已经看到了。我们使用alocate方法创建一个缓冲区。下面是一个创建方法集合。
public abstract class CharBuffer
extends Buffer implements CharSequence, Comparable {
// This is a partial API listing 
public static CharBuffer allocate(int capacity);
public static CharBuffer wrap (char [] array);
public static CharBuffer wrap (char [] array, int offset, int length);
public final boolean hasArray( ) 
public final char [] array( )
public final int arrayOffset( ) 
}

注意,这些都是静态方法。
wrap方法使用一个现有的数组作为缓冲区备份。这里很重要,因为,其实缓冲区都需要这样一个真正存放数据的地方,我们可以看看如果allocate方法
/**
     * Allocates a new byte buffer.
     *
     * <p> The new buffer's position will be zero, its limit will be its
     * capacity, and its mark will be undefined.  It will have a {@link #array
     * </code>backing array<code>}, and its {@link #arrayOffset </code>array
     * offset<code>} will be zero.
     *
     * @param  capacity
     *         The new buffer's capacity, in bytes
     *
     * @return  The new byte buffer
     *
     * @throws  IllegalArgumentException
     *          If the <tt>capacity</tt> is a negative integer
     */
 public static ByteBuffer allocate(int capacity) {
	if (capacity < 0)
	    throw new IllegalArgumentException();
	return new HeapByteBuffer(capacity, capacity);
    }

HeapByteBuffer又是什么呢?继续跟踪
HeapByteBuffer(int cap, int lim) {		// package-private

	super(-1, 0, lim, cap, new byte[cap], 0);
	/*
	hb = new byte[cap];
	offset = 0;
	*/

    }

这里解释下,HeapByteBuffer是ByteBuffer的一个实现类。她直接使用父类的构造函数来初始化。super第一个参数是mark,-1表示undefined,然后是position,然后是limit,然后是capacity,接下来就是一个真正存放的数组,最后是数组的偏移。

这下可以理解了,缓冲区实际上是由缓冲区的几个属性、操作和一个存放数据的数组(内存)组成的。我们如果改变数组,或者是通过缓冲区改变数组,都会影响双方的。

例如 CharBuffer charBuffer = CharBuffer.wrap(myArray,12,42)
这样的操作,设置了初始的position和limit。我们可以使用clear函数,然后再开始填充缓冲区,这里的缓冲区,并不是从12到42,而是从0到myArray.length

这里,我们还应该看到一个函数,hasArray(),这说明并非所有的缓冲区都包含一个数组,那么什么样的缓冲区不包含数组呢?答案是直接缓冲区。通过allocate和wrap创建的缓冲区都是间接的。直接缓冲区我们待会儿说。但是无论是直接还是间接,事实上缓冲区都必须又一个存放数据的地方。
如果是直接缓冲区,我们是不能获取到数组的,所以array方法不能再直接缓冲区中调用,否则抛出UnsupportedOperationException异常。当然,如果是只读的缓冲区,我们也不能调用array方法或者是arrayOffset方法。
arrayOffset方法是返回作为备份数组的起始下标。这就说明,并非一个数组必须是所有元素都在缓冲区内,而是可以拆分的。但是这里还没讲到,所以,以上提到的缓冲区,如果使用arrayOffset方法,返回都是0.

为了方便,我们的CharBuffer提供了几个方法
public abstract class CharBuffer
extends Buffer implements CharSequence, Comparable {
// This is a partial API listing
public static CharBuffer wrap (CharSequence csq) 
public static CharBuffer wrap (CharSequence csq, int start, int end)
}

我们可以这样用。
CharBuffer charBuffer = CharBuffer.wrap ("Hello World");
这对于字符集码和正则表达式处理都是很方便的。

2、复制缓冲区
这里的复制,仅仅是复制缓冲区,而不是数据。
public abstract class CharBuffer
extends Buffer implements CharSequence, Comparable {
// This is a partial API listing 
public abstract CharBuffer duplicate( ); 
public abstract CharBuffer asReadOnlyBuffer( );
public abstract CharBuffer slice( ); 
}

这三个函数可以完成复制功能。前两个顾名思义,容易理解,最后一个slice,其实也很容易理解,分割缓冲区,这时,起决于position和limit,新的缓冲区将针对原来的数据中的position到limit之间的数据作为新的缓冲区数据。这时,arrayOffset方法返回的就是position的位置了。

例如:要创建一个映射到数组位置12-20(9个元素)的buffer对象:
char[] myBuffer = new char[100];
CharBuffer cb = CharBuffer.wrap(myBuffer);
cb.position(12).limit(21);
CharBuffer sliced = cb.slice();


3、字节缓冲区
虽然我们有很多缓冲区类型,除了boolean型都有一一对应。
但是,ByteBuffer作为一个基本的缓冲区,其作用非同寻常。我们的通道只和字节缓冲区打交道。并且,其他的缓冲区和字节缓冲区都可以转换。
说到这里,我们就不得不理解计算机如何存储数据了。
实际上,内存是按照字节为单位来存放数据的。现在最流行的便是8个位一个字节。我们熟悉的是int占用4个字节,这样,这四个字节如何排列,又成为一个问题。
这个问题已经是硬件问题了,因为不同的硬件有不同的表现。
对于intel处理器,一般都是小端(little endian)
而对于摩托罗拉,Sun的处理器,一般都是大端(big endian)
还有网络字节顺序实际上也是大端的。
那么这两者有什么区别呢?
假设 一个int值 0x01234567 (注意是16进制),内存从左到右增长。地址左小右大
那么他在大端的存放 就类似 :   0x01  0x23  0x45  0x67
那么他在小端的存放,就应该是: 0x67  0x45  0x23  0x01

所以我们的字节操作中有了顺序问题。
package java.nio;
public final class ByteOrder
{
public static final ByteOrder BIG_ENDIAN;
public static final ByteOrder LITTLE_ENDIAN;
public static ByteOrder nativeOrder();
public String toString();
}

在JVM中,实际上我们是使用大端的。
看看关于Order的操作。
public abstract class ByteBuffer extends Buffer
implements Comparable {
// This is a partial API listing 
public final ByteOrder order();
public final ByteBuffer order(ByteOrder bo);
}

order返回系统使用的字节顺序。注意如果系统字节顺序和JVM不一致,可能性能上会有些许慢。

现在知道了字节顺序,那么字节缓冲区转换为其他缓冲区,也就清楚了。
比如,我们有一个4字节缓冲区 0x01  0x23  0x45  0x67
转换为一个IntBuffer,那么,根据字节顺序,如果是大端,直接就是 0x1234567
如果是小端顺序,那么读出来就是 0x67452301
注意:视图缓冲区一旦创建,字节顺序是不可改变的。视图缓冲区后面讲到。

4、直接缓冲区
前面说到,allocate和wrap创建的都是间接缓冲区。间接缓冲区,就是在操作系统和JVM之间其实有一层,我们如果对一个间接缓冲区使用,那么首先是要在操作系统层次创建一个临时缓冲区,然后copy过去,再操作,在删除临时缓冲区。这都很麻烦。
所以,直接缓冲区。也就是在操作系统,而不是JVM进程中创建缓冲区,这就绕过了JVM栈,所以依赖于具体的操作系统。事实上,在操作系统中创建缓冲区,比起在JVM中,更加消耗资源。(因为JVM中是已经划分好的内存,直接设置一下,而操作系统中要通过系统调用,个人理解。)

使用 allocateDirect方法就可以获得直接缓冲区。
public static ByteBuffer allocateDirect (int capacity)
public abstract boolean isDirect( );
isDirect对于直接缓冲区的非字节视图缓冲区,也可能返回true

5、视图缓冲区

可以这样理解,字节缓冲区是所有缓冲类型的基础。我们在通道中,网络中文件流中四处传递字节缓冲区。但是,一旦进入应用,我们就要通过视图缓冲区来解读具体的数据了。
字节缓冲区提供了很多这样的API
public abstract class ByteBuffer
extends Buffer implements Comparable {
// This is a partial API listing 
public abstract CharBuffer asCharBuffer( ); 
public abstract ShortBuffer asShortBuffer( ); 
public abstract IntBuffer asIntBuffer( ); 
public abstract LongBuffer asLongBuffer( ); 
public abstract FloatBuffer asFloatBuffer( );
public abstract DoubleBuffer asDoubleBuffer( );
}

前面提到过从ByteBuffer到IntBuffer的转换。其实这种转换,是一种解释包装。
所以需要提供一个顺序,然后按照顺序和类型,转换成另外一种视图,而原始数据其实是不变的。
例如,我们可以这样;
ByteBuffer byteBuffer = 
    ByteBuffer.allocate(7).order(ByteOrder.BIG_ENDIAN);
CharBuffer charBuffer = byteBuffer.asCharBuffer();

注意,java中char是占两个字节的。我们的转换可以看下图:



新的charBuffer视图其实还是使用的原来的缓冲区的数据,只是这时的元素变成了2个字节的char了。

看一段代码:
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.CharBuffer;


public class CharBufferView {
	public static void print(Buffer buffer){
		System.out.println("pos="+buffer.position()+", limit="+ 
				buffer.limit() + ", capacity="+buffer.capacity()
				+":'" + buffer.toString()+"'");
	}
	public static void main(String[] args)
		throws Exception{
		ByteBuffer byteBuffer = ByteBuffer.allocate(8).order(ByteOrder.BIG_ENDIAN);
		CharBuffer charBuffer = byteBuffer.asCharBuffer();
		
		byteBuffer.put(0,(byte)0);
		byteBuffer.put(1,(byte)'H');
		byteBuffer.put(2,(byte)0);
		byteBuffer.put(3,(byte)'e');
		byteBuffer.put(4,(byte)0);
		byteBuffer.put(5,(byte)'l');
		byteBuffer.put(6,(byte)0);
                  byteBuffer.put(7,(byte)'l');
                  byteBuffer.put(8,(byte)0);
                  byteBuffer.put(9,(byte)'o');
		
		CharBufferView.print(byteBuffer);
		CharBufferView.print(charBuffer);
		
	}
}

结果:
pos=0, limit=10, capacity=10:'java.nio.HeapByteBuffer[pos=0 lim=10 cap=10]'
pos=0, limit=5, capacity=5:'Hello'

可以看到,转换为视图后,limit和capacity都变了。

6、数据元素视图

上面的视图使用了一个视图类来转换。在ByteBuffer中,我们可以直接获取不同视图的元素。
public abstract class ByteBuffer
extends Buffer implements Comparable {
public abstract char getChar( ); 
public abstract char getChar (int index); 
public abstract short getShort( ); 
public abstract short getShort (int index); 
public abstract int getInt( ); 
public abstract int getInt (int index); 
public abstract long getLong( ); 
public abstract long getLong (int index); 
public abstract float getFloat( );
public abstract float getFloat (int index);
public abstract double getDouble( ); 
public abstract double getDouble (int index); 
public abstract ByteBuffer putChar (char value); 
public abstract ByteBuffer putChar (int index, char value); 
public abstract ByteBuffer putShort (short value); 
public abstract ByteBuffer putShort (int index, short value); 
public abstract ByteBuffer putInt (int value); 
public abstract ByteBuffer putInt (int index, int value); 
public abstract ByteBuffer putLong (long value); 
public abstract ByteBuffer putLong (int index, long value); 
public abstract ByteBuffer putFloat (float value); 
public abstract ByteBuffer putFloat (int index, float value); 
public abstract ByteBuffer putDouble (double value); 
public abstract ByteBuffer putDouble (int index, double value); 
}

这些操作一看便知。前面也提过其实就是根据大端小端的字节顺序,和这些数据类型的长度来组织数据。
这里需要注意的是:如果get或者是put时,数据不足,或者空间不够,都会发生异常。
如果不是用putXX的方法,直接getXX,那么可能产生意想不到的问题。


7、无符号数存取
对于java来说,没有无符号数处理(除了char)。为此,书中给出一个程序,用来处理无符号数缓冲区。
其基本原理是,使用比要存取的数据类型更大的数据类型来存放这个数,同时使用与运算强制设置符号位为0,剩余的位就可以作为数据而不是符号位了。
public class Unsigned
{
public static short getUnsignedByte (ByteBuffer bb) {
    return ((short)(bb.get( ) & 0xff)); 
}
public static void putUnsignedByte (ByteBuffer bb, int value) {
    bb.put ((byte)(value & 0xff));
 }
...
}

这里省略了其他部分。这样我们就可以看到带符号的

8、内存映射缓冲区
映射缓冲区是与文件存储的数据元素关联的字节缓冲区,它通过内存映射来访问。映射缓
冲区通常是直接存取内存的,只能通过 FileChannel 类创建。映射缓冲区的用法和直接缓冲
区类似,但是 MappedByteBuffer 对象具有许多文件存取独有的特征。
  • 大小: 20.5 KB
0
2
分享到:
评论

相关推荐

    java NIO 视频教程

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

    Java NIO(通道+缓冲区+选择器)

    Java NIO通道:通道基础、文件通道、Socket通道、工具类 Java NIO缓冲区:基础、缓冲区(Buffer)、创建缓冲区、直接缓冲区(DirectByteBuffer) Java NIO选择器:核心概念、选择器使用、Demo、选择器深入、

    Java NIO实战开发多人聊天室

    01-Java NIO-课程简介....17-Java NIO-Buffer-缓冲区分片.mp4 18-Java NIO-Buffer-只读缓冲区.mp4 19-Java NIO-Buffer-直接缓冲区.mp4 21-Java NIO-Selector-概述.mp4 23-Java NIO-Selector-示例代码(客户端).mp4 24

    java nio教程pdf

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

    java nio 中文版

    第二章 缓冲区 第三章 通道 第四章 选择器 第五章 正则表达式 第六章 字符集 --------------------- 作者:仓鼠洞 来源:CSDN 原文:https://blog.csdn.net/zhiyong499/article/details/78711911 版权声明...

    java nio.doc

     java.nio 包定义了缓冲区类,这些类用于所有 NIO API。java.nio.charset 包中定义了字符集 API,java.nio.channels 包中定义了信道和选择器 API。每个子包都具有自己的服务提供程序接口 (SPI) 子包,SPI 子包的...

    基于java NIO的简单聊天软件示例

    NIO是一种基于通道和缓冲区的I/O方式,它可以使用Native函数库直接分配堆外内存(区别于JVM的运行时数据区),然后通过一个存储在java堆里面的DirectByteBuffer对象作为这块内存的直接引用进行操作。这样能在一些...

    Java视频教程 Java游戏服务器端开发 Netty NIO AIO Mina视频教程

    [第4节] JavaNIO流-通道1.flv [第5节] Java NIO流-通道2.flv [第6节] Java NIO流-socket通道操作.flv [第7节] Java NIO流-文件通道操作.flv [第8节] Java NIO流-选择器 .flv [第9节] Java NIO流-选择器操作.flv...

    Java NIO入门

    NIO 的创建目的是为了让 Java 程序员可以实现高速 I/O 而无需编写自定义的本机代码。NIO 将最耗时的 I/O 操作(即填充和提取缓冲区)转移回操作系统,因而可以极大地提高速度。

    Java NIO缓冲

    缓冲区(buffer)是从即将写入通道...  NIO缓冲区特征  · Java NIO的基本组成模块是缓冲区。  · 缓冲区提供一个固定大小的容器来读取数据。  · 每个缓冲区都是可读的,但只有特定缓冲区才是可写的。  

    详细了解JAVA NIO之Buffer(缓冲区)

    主要介绍了JAVA NIO之Buffer(缓冲区)的相关资料,文中讲解非常细致,帮助大家更好的学习JAVA NIO,感兴趣的朋友可以了解下

    java NIO教程

    java NIO高清电子书,新的,无阻塞的IO,主要面向缓冲区的。

    javasnmp源码-nio-learn:JavaNIO使用示例,NIO的使用,TCP,UDP的简单示例

    nio缓冲区buffer 简介 Buffer是数据的容器,在nio中负责数据的存取,java为不同数据类型提供了相对应的缓冲区类型 如:ByteBuffer、CharBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer 、DoubleBuffer 等...

    yubo-java-nio:java nio的学习项目

    yubo-java-nioNIO 直接缓冲区 VS 非直接缓冲区直接缓冲区1、直接缓冲区最适合I/O 2、创建成本比非直接缓冲区高 3、直接缓冲区使用的内存是通过调用原生的、操作系统特定的代码来分配的 4、内存存储区域不受限制垃圾...

    尚硅谷Java视频_NIO 视频教程

    尚硅谷_NIO_直接缓冲区与非直接缓冲区 ·04. 尚硅谷_NIO_通道(Channel)的原理与获取 ·05. 尚硅谷_NIO_通道的数据传输与内存映射文件 ·06. 尚硅谷_NIO_分散读取与聚集写入 ·07. 尚硅谷_NIO_字符集 Charset ·08. ...

    java8中NIO缓冲区(Buffer)的数据存储详解

    在本篇文章中小编给大家分享了关于java8中NIO缓冲区(Buffer)的数据存储的相关知识点,需要的朋友们参考下。

    java8源码-nio:java8nio使用的总结

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

    java nio超好资源

    NIO 的创建目的是为了让 Java 程序员可以实现高速 I/O 而无需编写自定义的本机代码。NIO 将最耗时的 I/O 操作(即填充和提取缓冲区)转移回操作系统,因而可以极大地提高速度。

    Java NIO 网络编程初探

    NIO支持面向缓冲区的、基于通道的IO操作。能够更加高效的进行IO操作。NIO同样拥有文件读写,网络通信等IO操作,今天我们来看看NIO中的TCP网络通信的使用方法。 2. Java NIO 三大核心 Java NIO 有三大核心要素:...

    java.nio demo

    Java的IO操作集中在java.io这个包中,是基于流的同步(blocking)API。对于大多数应用来说,这样...从JDK 1.4起,NIO API作为一个基于缓冲区,并能提供异步(non-blocking)IO操作的API被引入。本文对其进行深入的介绍。

Global site tag (gtag.js) - Google Analytics