java I/O系统(5)-Buffer类
来源:互联网 发布:见一页而知岁月将暮 编辑:程序博客网 时间:2024/05/16 12:59
引言
在java的IO系统中,在JDK1.4的时候引入了新的IO系统,也就是我们常说的nio,它的主要功能是提高速度。在本篇博文中,详细介绍关于nio的构造:通道和缓冲器,核心了解ByteBuffer,因为它是唯一与通道直接交互的缓冲器。笔者目前整理的一些blog针对面试都是超高频出现的。大家可以点击链接:http://blog.csdn.net/u012403290
通道与缓冲器
通道是指资源的真实存在,而缓冲器是运输资源的媒介。比如说我们喝水和时候:水就是通道,而杯子就是缓冲器。我们不直接喝水,通常我们都是用杯子装好水,然后再用杯子喝水。类似的,我们不直接操作通道,而是操作缓冲器,通过缓冲器来操作通道。
对于nio,jdk中有4个包,在java.nio包中主要是对缓冲器和缓冲器和缓冲器扩展等的描述;在java.nio.channels包中主要是对文件通道的描述。在本篇博文中主要是对通道和缓冲器的进一步解释。
通道(FileChannel),我们一般通过在老的IO系统中获取,主要包含3个类:FileInputStream,FileoutputStream和RandomAccessFile,对于这3个类,前面两者我们可以定义出2个输入通道和输出通道。对于后者,基于RandomAccessFile的性质,我们可以在文件中任意移动位置,获取恰当的资源。而缓冲器,我们一般需要自己创建一个恰当大小的对象。
有Buffer类实现的缓冲器有如下这些:
ByteBuffer
CharBuffer
DoubleBuffer
FloatBuffer
IntBuffer
LongBuffer
ShortBuffer
其中直接与通道交互的是ByteBuffer,因为它是直接面未加工的字节的。
下面的代码就是最简单的建立了2个通道与一个缓冲器:
package com.brickworkers.io.nio;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.nio.ByteBuffer;import java.nio.channels.FileChannel;public class ChannelAndBufferTest { public static void main(String[] args) throws FileNotFoundException { //建立一个输入通道 FileChannel in = new FileInputStream("F:/java/io/in.txt").getChannel(), //建立一个输出通道 out = new FileInputStream("F:/java/io/out.txt").getChannel(); //建立一个缓冲器 ByteBuffer bf = ByteBuffer.allocate(1024); }}
ByteBuffer缓冲器详解
1、类定义
以下是ByteBuffer的类结构定义:
public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer> }
从上面可以看出ByteBuffer是继承Buffer实现的,同时它自身是一个抽象类,而且它还实现了比较接口,两个ByteBuffer之间可以比较大小。我们不去讨论别的,在类定义中我们核心了解的是关于缓冲器的核心结构,我们先看一下Buffer的基本组成:
public abstract class Buffer { /** * The characteristics of Spliterators that traverse and split elements * maintained in Buffers. */ static final int SPLITERATOR_CHARACTERISTICS = Spliterator.SIZED | Spliterator.SUBSIZED | Spliterator.ORDERED; // Invariants: mark <= position <= limit <= capacity private int mark = -1; private int position = 0; private int limit; private int capacity; }
在Buffer中有4个重要的字段:①mark表示当前position的位置,也就是说当我们调用一次mark的时候,那么mark就会指向position所指向的位置。②position表示当前操作位置。在定义好大小的缓冲器中,我们每次读取或者写出数据的时候,这个position都会相应的移动,相当于上一篇博文中介绍的RandomAccessFile一样,文件指针的移动。③limit表示可读写的边界,当position的位置等于limit的时候就表示全部读取或者写入完成;④capacity表示定义的缓冲器容量大小。
在后面的demo中会详细展示这4个变量的具体运作情况。
2、构造函数
以下是ByteBuffer的构造函数源码:
ByteBuffer(int mark, int pos, int lim, int cap, // package-private byte[] hb, int offset) { super(mark, pos, lim, cap); this.hb = hb; this.offset = offset; } // Creates a new buffer with the given mark, position, limit, and capacity // ByteBuffer(int mark, int pos, int lim, int cap) { // package-private this(mark, pos, lim, cap, null, 0); }
看完构造函数,我们就知道,我们是不能直接通过构造函数创建ByteBuffer对象的,因为它是包等级的私密方法。那么我们应该如何获取一个缓冲器对象呢,和很多的类一样,ByteBuffer也是通过静态工厂方法创建对象的,请看下面这个方法:
//对象创建静态方法1 public static ByteBuffer allocateDirect(int capacity) { return new DirectByteBuffer(capacity); }//对象创建静态方法2 public static ByteBuffer allocate(int capacity) { if (capacity < 0) throw new IllegalArgumentException(); return new HeapByteBuffer(capacity, capacity); }//对象创建静态方法3 public static ByteBuffer wrap(byte[] array, int offset, int length) { try { return new HeapByteBuffer(array, offset, length); } catch (IllegalArgumentException x) { throw new IndexOutOfBoundsException(); } }//对象创建静态方法4 public static ByteBuffer wrap(byte[] array) { return wrap(array, 0, array.length); }
从ByteBuffer的静态工厂方法可以看出:①和②是直接建立一个空的、指定大小的新缓冲器。那么两者有什么区别呢?查阅相关资料,发现allocateDirect方法比allocate方法效率会更高。③和④比较容易理解,都是把一个byte数组包装成一个ByteBuffer缓冲器,但是③方法可以指定需要包装的byte的数组的具体位置,它有一个偏移作为入参。
3、核心方法
①从Buffer类中继承下来的方法:
1、clear() 清空缓存区
public final Buffer clear() { position = 0; limit = capacity; mark = -1; return this; }
从源码中可以看出,并不是真正的把数据清楚,而是改变了各个参数的值,其实就是把各个指针还原到原始状态。通常这个方法在复用一个缓冲器的时候使用。
2、hasRemaining() : 判断当前缓冲器中是否有可用数据
public final boolean hasRemaining() { return position < limit; }
从源码中就可以看出,其实就是看当前操作的position指针有没有和limit指针重合。这个方法主要是在获取缓冲器中的数据的时候进行判断。
3、flip(): 用于重置操作指针position,规整缓冲器中的数据,保证position与limit之间的数据都是有效的
public final Buffer flip() { limit = position; position = 0; mark = -1; return this; }
从源码中可以看出,相当于对缓冲器的操作复位一样,注意复位不是清空,而且,这个操作会把有效数据的结尾定义到当前操作的位置上。所以,这个方法的错误调用可能会导致缓冲器的失效,比如说你position为0的时候调用这个方法,那么有效数据就直接变为了0。
4、rewind():操作指针复位。相比于flip方法来说,这个方法不保证position与limit之间的数据都是有效的,而且不会意外的清除数据。
public final Buffer rewind() { position = 0; mark = -1; return this; }
这个方法一般适用于需要循环遍历某一个缓冲器的时候,比如说第一次操作从头读到尾,第二次进来需要先调用rewind方法,然后开始读,不然就读不到数据。
5、mark() 标记当前position位置
public final Buffer mark() { mark = position; return this; }
这个方法主要用于标记当前操作位置,对于特定操作,比如说对一个缓冲器中所有奇数位的数据进行处理。那么我们需要记录上一次操作位置,这个时候就可以用mark来标记。
6、reset() 把当前操作指针position指向mark的地方
public final Buffer reset() { int m = mark; if (m < 0) throw new InvalidMarkException(); position = m; return this; }
一般的,如果使用到了这个方法,那么前提是已经使用了mark方法,不然mark的默认是-1位置。
其实Buffer中的方法不仅仅到此。下面这段代码囊括了上面表述的所有方法:
package com.brickworkers.io.nio;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;import java.nio.ByteBuffer;import java.nio.channels.FileChannel;public class ChannelAndBufferTest { public static void main(String[] args) throws IOException { //建立2个通道,分别是输入和输出 FileChannel in = new FileInputStream("F:/java/io/bw.txt").getChannel(), out = new FileOutputStream("F:/java/io/bw.txt").getChannel(); //建立一个通用缓冲器 ByteBuffer bf = ByteBuffer.allocate(64); //get这个缓冲器中包装一个值 bf.put("brickworkers".getBytes()); //注意,在这里其实position指针的位置已经变化了,必须把操作指针移动到开始处,建议使用filp方法,因为brickwork字节数是小于64的,避免不必要的开销 bf.flip(); //把缓冲器交给输出通道,把值写入文件 out.write(bf); out.close(); //写完之后,要把bf清空用于存储从输入通道获取的值 bf.clear(); //输入通道把值交给缓冲器 in.read(bf); in.close(); //先把position指针回归的原始位置,这里既可以用flip也可以用rewind。 bf.flip(); //我们要把相邻的字符进行位置交换 while(bf.hasRemaining()){ //先用mark进行标记,表示当前操作位置 bf.mark(); byte ch1 = bf.get(); byte ch2 = bf.get(); //操作完这些的时候其实position指针已经向后移动了2个字节。在进行交换的时候必须把position指针从新移动到交换对象之前 bf.reset(); //进行位置交换 bf.put(ch2).put(ch1); } //重置position,因为在上面置换操作已经结束,所以既可以使用filp方法,也可以使用rewind方法 bf.flip(); while(bf.hasRemaining()){ System.out.print((char)bf.get()); } }}//输出结果://rbciwkroeksr//
但是,上面这段代码存在一个很大的问题,可能会抛出java.nio.BufferUnderflowException错误,之所以会产生这个错误是因为你写入的字符串是奇数还是偶数。如果是偶数经过两次的get(),其实position指针已经超过了limit。所以其实上面这段代码的第一个while中的hasRemaining()方法最好还是使用这个:remaining() > 2来判断会可靠的多,意思就是当最后不足2个的时候,最后一个字母不置换。大家可以自己尝试一下。
②创建一个合适的字节缓冲区视图,可以作为各种基本数据类型的缓冲区。其实就是获取一个更高级的缓冲器,比如说asCharBuffer,asIntBuffer,asDoubleBuffer等等。但是,能直接与通道交互的务必是ByteBuffer。
在上面的demo使用中,我们是用while循环输出字符,但是我们也可以用asCharBuffer直接把ByteBuffer转出CharBuffer,可以把上面的代码的最后while循环换成这样:
System.out.println(bf.asCharBuffer());
但是要注意,这里很有可能会产生乱码。在缓冲器ByteBuffer中存储的是字节,如果要转换成字符,要么对其输入的时候进行编码,要么就在输出的时候进行编码。我们一般用nio中的一个包java.nio.charset来指定编码,比如下面这段代码:
CharBuffer cb = Charset.forName("UTF-8").decode(bf);
我们就可以把ByteBuffer通过编码之后转换成CharBuffer。
②读取方法,读取缓冲器中的数据,get方法,可以读取对应的数据。可以读取完整字节数组,或者存在偏移的字节数组。甚至是getChar,getLong,getDouble等等方法。存储方法,把数据存储到position指针指向的位置。可以存储整个缓冲器的数据,或者存在偏移的字节数组。甚至是putChar,putLong,putDouble。其实这个相当于RandomAccessFile中的读写方式。具体请参考以下方法:
package com.brickworkers.io.nio;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;import java.nio.ByteBuffer;import java.nio.CharBuffer;import java.nio.channels.FileChannel;import java.nio.charset.Charset;public class Test { public static void main(String[] args) throws IOException { //建立2个通道,分别是输入和输出 FileChannel in = new FileInputStream("F:/java/io/bw.txt").getChannel(), out = new FileOutputStream("F:/java/io/bw.txt").getChannel(); //建立一个通用缓冲器 ByteBuffer bf = ByteBuffer.allocate(1024); //先进行存储一个字节 bf.put((byte)'a'); //存入一个char bf.putChar((char)'b'); //存入一个double bf.putDouble(3.1415926D); //存入一个long bf.putLong(14L); //存入一个int bf.putInt(13); //先把数据整理好,用flip方法。 bf.flip(); //把缓冲器中的数据运入输出通道 out.write(bf); //先清空缓冲器 bf.clear(); in.read(bf); bf.flip(); //接下来我们对数据进行读取 //读取一个字节 System.out.println(bf.get()); //读取一个char System.out.println(bf.getChar()); //读取一个double System.out.println(bf.getDouble()); //读取一个Long System.out.println(bf.getLong()); //读取一个int System.out.println(bf.getInt()); }}//输出结果://97//b//3.1415926//14//13////
有的小伙伴很奇怪,为什么我byte输入的是a,怎么变成97了呢?因为在ASCII编码中,a字母就是97。
- java I/O系统(5)-Buffer类
- [疯狂Java]I/O:NIO简介、Buffer
- Java I/O系统
- java I/O系统
- Java I/O系统
- Java I/O系统
- Java I/O系统
- java I/O系统
- Java I/O系统
- Java I/O系统
- JAVA I/O 系统
- Java I/O系统
- JAVA I/O系统
- java I/O系统
- JAVA : I/O系统
- Java I/O系统
- JAVA I/O系统
- Java I/O系统
- JavaScript常用属性
- Android 6.0 悬浮窗默认关闭解决方案
- 工厂模式
- java的正则匹配
- 关于sizeof 和strlen 的计算的知识总结
- java I/O系统(5)-Buffer类
- 百宝云企业版正式进入封测阶段
- 实现背景颜色改变
- shiro学习笔记1——基本概念
- tablayout增加选择tab 的事件.
- 一起来学习 系统封装接口层- CMSIS-OS 之freeRTOS
- zookeep基本操作
- 338. Counting Bits
- 解决处理后台返回json数据格式问题