nio入门

nio中主要是要学会Buffer和Channel的使用.
[b]Buffer从它的名字就可以知道它表示的是一个缓冲. Buffer是一个抽象类, 它主要封装了与缓冲有关的一些抽象方法. 主要的方法有这几个:[/b]

int capacity() 这块缓冲的容量.

Buffer clear() 清空缓冲.

Buffer flip() 清算缓冲的实际使用大小(字节).

int limit() 缓冲实际使用了多少字节.

Buffer position(int newPosition) 将读取位置移动到指定的位置.

int position()
当前的读取位置, 以字节为单位, 每读取一个byte就会向后加1, 没读取一个char就会向后加2, 以此类推.

int remaining() 还有多少字节没读取(实际的字节数).

boolean hasRemaining() 是否还有没读取的字节数. 等同于remaining() > 0

Buffer rewind() 重新读取. (就是将当前位置放到开始处, 并重置mark标记)

boolean hasArray() 是否是以数组作为缓冲且数组缓冲是可读可写的.

abstract boolean isDirect()
该缓冲是否是一块直接缓冲(直接缓冲就是在系统层面创建的一块缓冲, 非直接缓冲说简单点就是使用一个数组作为缓冲)

boolean isReadOnly() 这个缓冲是不是只读的

Buffer limit(int newLimit) 调整实际使用的字节数.

Buffer mark() 在当前位置做一个标记, 重置时就可以回到这个位置

Buffer reset() 将当前位置重置到之前做的mark处 (注意没做mark是不允许重置的)

Object array() 如果是数组作为缓冲的话, 返回内部的数组; 直接缓冲不支持该方法

int arrayOffset()
如果是数组作为缓冲的话, 返回该Buffer是从数组中的哪个index开始的. (因为可能只是用了array的offset~offset+length部分来用作缓冲)


[b]Channel: 可以理解为导管, 数据之间的传输使用导管来进行. 他仅仅是一个接口, 有两个很容易理解的方法:[/b]
void close() 关闭导管

boolean isOpen() 导管是否是打开着的(可以传输数据的)


[b]Buffer和Channle的大体关系可以用下图来表示:[/b]
[img]http://dl.iteye.com/upload/attachment/0075/8849/3862feaf-915c-3de3-8583-bef701781b96.png[/img]


[b]Buffer层次中最重要的是ByteBuffer, 因为其它几个Buffer都是基于ByteBuffer的:[/b]
其实ByteBuffer的使用和RandomAccessFile的使用时非常类似的:
1. 读取时, RandomAccessFile用的都是readXxx, ByteBuffer中用的都是getXxx
2. 写入时, RandomAccessFile用的都是writeXxx, ByteBuffer中用的都是putXxx

好了接下来就直接上一些代码了:
首先来看一段理解capacity和limit的代码:

// 假设我申请了一块20B大小的缓冲, 然后添加了一个byte和一个char, 那么实际使用时多少呢? 3个字节
ByteBuffer arrayBuffer = ByteBuffer.allocate(20);
arrayBuffer.capacity(); // 20个字节
arrayBuffer.put((byte) 1); // 添加一个byte
arrayBuffer.putChar('a'); // 添加一个char
arrayBuffer.flip(); // 清算实际使用了多少字节, 并将position放到开始处
arrayBuffer.limit(); // 实际使用了3个字节


直接缓冲和非直接缓冲的区别:

ByteBuffer arrayBuffer = ByteBuffer.allocate(20); // 可读写数组缓冲
arrayBuffer.isDirect(); // false, 因为是数组缓冲
arrayBuffer.isReadOnly(); // false
arrayBuffer.hasArray(); // true, 是数组缓冲且不是可读的

byte[] bytes = new byte[20];
arrayBuffer = ByteBuffer.wrap(bytes);
if (bytes == arrayBuffer.array()) {
System.out.println("equals");
}

ByteBuffer directBuffer = ByteBuffer.allocateDirect(20);
directBuffer.isDirect();
directBuffer.isReadOnly(); // false
directBuffer.hasArray(); // false, 因为是直接缓冲


操作ByteBuffer, 向缓冲写入数据和从缓冲读取数据:

ByteBuffer arrayBuffer = ByteBuffer.allocate(50);
arrayBuffer.put((byte) 1);
arrayBuffer.putChar('2');
arrayBuffer.putInt(3);
arrayBuffer.putLong(4);
arrayBuffer.putFloat(5.0F);
arrayBuffer.putDouble(6.0);
arrayBuffer.position(); // 27
arrayBuffer.flip(); // 清算实际使用了多少字节(1+2+4+8+4+8=27), 并将position放到开始处

arrayBuffer.limit(); // 27

// 必须按照写入顺序读取, 否则读出来的结果将不正确
arrayBuffer.get();
arrayBuffer.getChar();
arrayBuffer.getInt();
arrayBuffer.getLong();
arrayBuffer.mark(); // 在当前位置做一个标记, 重置时就可以回到这个位置
arrayBuffer.getFloat();
arrayBuffer.getDouble();

arrayBuffer.reset(); // 重置到标记处, 即getLong()处
arrayBuffer.getFloat(); // 5.0F

arrayBuffer.rewind(); // 重读并清掉mark
arrayBuffer.get();
arrayBuffer.getChar(); // ...
// arrayBuffer.reset(); // mark清掉后再调用就要出错的


hasRemaining(), clear()的使用:

// 假设要读取的数据很大, 缓冲一次性装不下, 那就需要分成多次来读取了.
public static void copy() throws IOException {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream("d:/src.txt");
FileChannel src = fis.getChannel();
fos = new FileOutputStream("d:/dst.txt");
FileChannel dst = fos.getChannel();

ByteBuffer arrayBuffer = ByteBuffer.allocate(256);
while (src.read(arrayBuffer) != -1) {
arrayBuffer.flip(); // src中的数据读取到缓冲中后, 清算实际读取大小
dst.write(arrayBuffer); // 写出将缓冲中的数据写入目标
arrayBuffer.clear(); // 本次数据读完, 清掉之前读取的数据, 继续下一次的读取
}
} finally {
closeSinently(fis);
closeSinently(fos);
}
}


static void closeSinently(Closeable c) {
if (c != null) {
try {
c.close();
} catch (IOException ex) {
// ignore
}
}
}


[b]其它的基础类型Buffer类, 这些类可以看成是ByteBuffer的包装类. [/b]
比如: 用ByteBuffer是面向byte的, 用它来进行面向char的操作就会比较繁琐, 好那就把它包装为CharBuffer, 这样所有操作都以char为单位, 这样就方便多了.
CharBuffer 提供以char为单位的操作 (ByteBuffer.asCharBuffer())
ShortBuffer 提供以short为单位的操作 (ByteBuffer.asShortBuffer())
IntBuffer 提供以int为单位的操作 (ByteBuffer.asIntBuffer())
LongBuffer 提供以long为单位的操作 (ByteBuffer.asLongBuffer())
FloatBuffer 提供以float为单位的操作(ByteBuffer.asFloatBuffer())
DoubleBuffer 提供以double为单位的操作(ByteBuffer.asDoubleBuffer())

[b]内存映射文件Buffer, MappedByteBuffer[/b]
有时候我们想在内存中修改一个很大的文件, 比如: 10G, 但一次性加载电脑又会吃不消, 有了MappedByteBuffer就可以了.
它帮我们处理好了如何把这10G的内容加载到内存中, 我们就当是这10G是内存数据一样使用.

用MappedByteBuffer来计算大文件的hash值的代码:

/***
* <p> 获取文件的哈希校验值. 使用内存映射文件的方式来提升性能, 在获取大文件的哈希值时有用. </p>
*/
public static String getFileHashNio(long sizePerRead, String fileName, String hashType) throws IOException {
FileChannel fchannel = null;
try {
MessageDigest digest = MessageDigest.getInstance(hashType);
RandomAccessFile file = new RandomAccessFile(fileName, "r");
fchannel = file.getChannel();

long length = file.length();
// long sizePerRead = 1<<29; // 还和操作系统和jdk版本有关, win7 32bit, jdk 32bit就不能设为1<<30, 会报错
long time = (int) (length / sizePerRead);
byte[] buffer = new byte[1024];
if (time == 0) {
digest(buffer, fchannel, digest, 0, length);
} else {
for (int i = 0; i < time; i++) {
digest(buffer, fchannel, digest, i * sizePerRead, sizePerRead);
}
int remaining = (int) (length % sizePerRead);
if (remaining > 0) {
digest(buffer, fchannel, digest, time * sizePerRead, remaining);
}
}

return bytes2Hex(digest.digest());
} catch (NoSuchAlgorithmException ex) {
throw new IllegalArgumentException(hashType + " is not implemented by Providers");
} finally {
// nio中close Channel
closeSilently(fchannel);
System.gc(); // 尝试释放map所占用的文件句柄(它要到gc的时候才会释放), jdk中没提供api来释放
}
}

private static void digest(byte[] buffer, FileChannel fchannel, MessageDigest digest, long position, long size) throws IOException {
// map会占用底层的文件句柄
MappedByteBuffer mbuffer = fchannel.map(MapMode.READ_ONLY, position, size);
int bufferLength = buffer.length;
while (mbuffer.remaining() >= bufferLength) {
mbuffer.get(buffer);
digest.update(buffer);
}
int remaining = mbuffer.remaining();
if (remaining > 0) {
mbuffer.get(buffer, 0, remaining);
digest.update(buffer, 0, remaining);
}
// mbuffer.clear();
}

/** hex char look up table */
private static final char[] HEX_CHARS = {
'0','1','2','3','4','5','6','7','8','9',
'A','B','C','D','E','F'
};

public static String bytes2Hex(byte[] bytes) {
StringBuilder buffer = new StringBuilder();
for (int i = 0, len = bytes.length; i < len; i++) {
byte b = bytes[i];
buffer.append(HEX_CHARS[b>>4 & 0x0f]); // high
buffer.append(HEX_CHARS[b & 0x0f]); // low
}
return buffer.toString();
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值