缓存的意思是中间存储,相当于中转站,积累一定的货物,再往目的地运送。如果没有中转站,就会出现一件一件的运送,耗费大量的人力物力。
缓存的基类是:Buffer
缓存的基本子类有:Char/Byte/Short/Int/Long/Float/Double + Buffer
缓存一般用一个数组做存储,array()方法可以获取这个数组,但并不是所有Buffer都能够获取这个数组,使用hasArray()方法的返回值,查看Buffer是否能获取数组。有两种情况,Buffer是不能获取数组。
第一种情况,直接存储的Buffer:使用new创建一个数组,这个数组使用的存储空间是堆空间,如果使用JIN,然后直接从内存中开辟空间,这样就可以节省堆空间,解决一些堆空间不足的情况。因此,Buffer分堆存储,和直接存储。堆存储的Buffer使用allocate(int)静态方法创建,直接存储的Buffer使用allocateDirect(int)静态方法创建,wrap(...)静态方法是自己提供一个数组来创建Buffer,自己提供的数组肯定是属于堆空间上的。使用isDirect()方法,可以查看Buffer是否为直接储存,否则为堆存储。
第二种情况,只读模式的Buffer:缓存的基本操作就是读和写,使用get()方法进行读取操作,使用put()方法进行写入操作。Buffer创建的时候,既可以读,也可以写。有一种需求,Buffer需要提供给其它操作者,但只能读,不能写。使用asReadOnlyBuffer()方法就能创建出一个只读Buffer,这个只读Buffer与原Buffer是共享同一个数组,共享数组可以达到节省空间的目的。因此,为了不让操作者进行写操作,只读模式的Buffer就不允许操作者获得里面的数组。使用isReadOnly()方法,查看Buffer是否只读。
缓存在读取和写入数组单元时,其操作位与实际操作位会有偏差,使用arrayOffset()方法,可以获得这个偏差量。以下方法的参数和返回值,都是相对于偏差量的。
- 当前操作位:position()/position(int)
- 操作结束位:limit()/limit(int)
- 操作结束位的最大值:capacity()
每次操作完后,当前操作位就会指向下一个数组单元。一直到操作结束位时,缓存就不允许再操作。通过limit()-position(),也可以使用remaining()方法,查看剩余操作量。使用hasRemaining()方法,查看是否还能操作。
当前操作位与操作结束位可以通过position(int)和limit(int)方法单独调整,也可以通过其它方法进行整体调整。
- clear()方法,调整position为0,limit为capacity,表示清空数据,等待新数据写入。
- flip()方法,调整limi为position,position为0,表示从写模式切换为读模式。
- rewind()方法,调整position为0,表示重头操作(读/写)。
- reset()方法,调整position为回档位,表示操作从回档位开始。回档位是需要事先使用mark()方法标记当前操作位为回档位,回档位不能大于当接操作位,否则在调整过程中,回档位被消除。
- compact()方法,把未读数据复制至开始位置,调整position为剩余量,调整limit为capacity,表示清除已读数据,保留未读数据,并等待写入数据。
(注:图源于http://www.cnblogs.com/hvicen/p/6138690.html)
该图表示出Buffer在写与读的时候,limit所处的位置,左边是写模式,右边是读模式。使用flip()方法,使写模式转变为读模式。使用clear()和compact()方法,使读模式转变为写模式,clear()是清空所有数据,compact()只清除已读数据,保留未读数据。
使用duplicate()方法,拷贝出一个属性和状态一致的Buffer,两个Buffer是共享数组。
使用slice()方法,构建出一个Buffer,这个Buffer以当前操作位的实际操作位为偏差量,以剩余操作量为操作结束位的最大值。换句话说,slice的意思是切片,切出一个从当前操作位到操作结束位的Buffer,这个Buffer的读与写只能在这一段上面进行,同样切出来的Buffer与原Buffer是共享数组。
一个数据单元可以切分成多个字节,例如一个整型数据可以切分成四个字节,存储在数组时,可以从高位到低位存储,也可以从低位到高位存储。使用order()/order(ByteOrder)方法,可以查看和设置一个数据单元的排列方式,低位到高位排列为ByteOrder.BIG_ENDIAN,高位到低位排列为ByteOrder.LITTLE_ENDIAN。使用ByteOrder.nativeOrder()方法,查看本地的数据排列方式。
关于CharBuffer与ByteBuffer之间的编码与解码的转换,如图所示:
示例代码:
import java.io.FileInputStream; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.channels.FileChannel; import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import java.nio.charset.CharsetEncoder; import java.nio.charset.CoderResult; public class Test { public static void main(String[] args) throws IOException, InterruptedException { String fileName = "f:/test.txt"; writeFile(fileName); readFile(fileName); } public static void writeFile(String fileName) throws IOException{ CharBuffer cb = CharBuffer.allocate(100); cb.put("中文abcdefg"); cb.flip(); // FileOutputStream file = new FileOutputStream(fileName); RandomAccessFile file = new RandomAccessFile(fileName, "rw"); int length = 0; FileChannel channel = file.getChannel(); // 创建字节缓存,为测试效果,设置空间大小为5 ByteBuffer bb = ByteBuffer.allocate(5); // 创建编码器 CharsetEncoder encoder = Charset.defaultCharset().newEncoder(); // 设置字符编码不完整时的处理方式 // encoder.onMalformedInput(CodingErrorAction.REPLACE); // 设置字符编码不能映射时的处理方式 // encoder.onUnmappableCharacter(CodingErrorAction.REPLACE); CoderResult result = null; boolean end = false; while(true){ if(!end) { // 编码,第3个参数表示告诉编码器,输入空间的数据是否已经是最后一批数据 result = encoder.encode(cb, bb, true); } else { // 添加尾部:有些字符集需要添加尾部 result = encoder.flush(bb); } // 下溢,表示输入数据不足,即数据转换完毕; if(result.isUnderflow()){ if(!end) end = true; else { // 切换读模式 bb.flip(); // 写入文件 length += channel.write(bb); // 测试是否还有数据未写入文件 if(bb.hasRemaining()){ throw new RuntimeException("还有剩余数据未写入"); } break; } } // 上溢,表示输出空间不足; else if(!result.isOverflow()) result.throwException(); // 切换读模式 bb.flip(); // 写入文件 length += channel.write(bb); // 切换写模式 bb.compact(); } // 设置文件大小 file.setLength(length); // file.flush(); // 关闭通道 channel.close(); // 关闭文件 file.close(); } public static void readFile(String fileName) throws IOException{ CharBuffer cb = CharBuffer.allocate(100); FileInputStream file = new FileInputStream(fileName); // RandomAccessFile file = new RandomAccessFile(fileName, "rw"); FileChannel channel = file.getChannel(); // 创建字节缓存,为测试效果,设置空间大小为5 ByteBuffer bb = ByteBuffer.allocate(5); bb.flip(); // 创建解码器 CharsetDecoder decoder = Charset.defaultCharset().newDecoder(); // 设置字符编码不完整时的处理方式 // decoder.onMalformedInput(CodingErrorAction.REPLACE); // 设置字符编码不能映射时的处理方式 // decoder.onUnmappableCharacter(CodingErrorAction.REPLACE); CoderResult result = null; boolean eof = false; do { // 切换写模式 bb.compact(); // 读取文件 eof = (channel.read(bb) == -1); // 切换读模式 bb.flip(); // 解码,第3个参数表示告诉解码器,输入空间的数据是否已经是最后一批数据 result = decoder.decode(bb, cb, eof); // 下溢,表示输入数据不足;上溢,表示输出空间不足; if(!result.isUnderflow()) result.throwException(); } while(!eof); // 添加尾部:有些字符集需要添加尾部 result = decoder.flush(cb); // 下溢,表示输入数据不足;上溢,表示输出空间不足; if(!result.isUnderflow()) result.throwException(); // 关闭通道 channel.close(); // 关闭文件 file.close(); // 切换读模式 cb.flip(); // 打印读取的文件内容 System.out.println(cb.toString()); } }
(参考文献:《Java字符编码解码的实现详解》)
本文原创,待续更新!