11. NIO
JDK1.4的java.nio.*已经加入新的IO类库,目的在于提高速度。实际上旧的IO包已经使用nio重写,即使不显示用nio写代码,速度已经得到提升。
这种速度提升得益于使用的结构接近于操作系统执行IO的方式:通道与缓冲器。
唯一直接与通道交互的缓冲器:ByteBuffer。
产生FileChannel的三个类:FileInputStream, FileOutputStream, RandomAccessFile.
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class GetChannel {
private static final int BSIZE = 1024;
public static void main(String[] args) throws IOException {
FileChannel fc = new FileOutputStream("data.txt").getChannel();
fc.write(ByteBuffer.wrap("Some text ".getBytes()));
fc.close();
fc = new RandomAccessFile("data.txt","rw").getChannel();
fc.position(fc.size());
fc.write(ByteBuffer.wrap("Some more".getBytes()));
fc.close();
fc = new FileInputStream("data.txt").getChannel();
ByteBuffer buff = ByteBuffer.allocate(BSIZE);
fc.read(buff);
buff.flip();
while(buff.hasRemaining()){
System.out.print((char)buff.get());
}
}
}
//*Output:
Some text Some more
*//
文件复制,注意flip()与clear()的使用
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class ChannelCopy {
private static final int BSIZE = 1024;
public static void main(String[] args) throws IOException {
if(args.length!=2){
System.out.println("args shit");
System.exit(1);
}
FileChannel in = new FileInputStream("D:\\javaLD\\workspace_IBM\\IO1\\src\\com\\ld\\tij\\ChannelCopy.java").getChannel(),
out = new FileOutputStream(args[1]).getChannel();
ByteBuffer buffer = ByteBuffer.allocate(BSIZE);
while(in.read(buffer)!=-1){
buffer.flip();
out.write(buffer);
buffer.clear();
}
//method 2:改进方法transferTo()/transferFrom() 可以将一个通道与另外一个通道相连
in.transferTo(0, in.size(), new FileOutputStream("test2.txt").getChannel());
}
}
11.1 NIO中转换数据
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
public class BufferToText {
private static final int BSIZE = 1024;
public static void main(String[] args) throws IOException {
FileChannel fc = new FileOutputStream("data2.txt").getChannel();
fc.write(ByteBuffer.wrap("Some text".getBytes()));
fc.close();
fc = new FileInputStream("data2.txt").getChannel();
ByteBuffer buff = ByteBuffer.allocate(BSIZE);
fc.read(buff);
buff.flip();
System.out.println(buff.asCharBuffer());
buff.rewind();
String encoding = System.getProperty("file.encoding");
System.out.println("Decoded using "+encoding+": "+ Charset.forName(encoding).decode(buff));
fc = new FileOutputStream("data2.txt").getChannel();
fc.write(ByteBuffer.wrap("Some text by utf".getBytes("UTF-16BE")));
fc.close();
fc = new FileInputStream("data2.txt").getChannel();
buff.clear();
fc.read(buff);
buff.flip();
System.out.println(buff.asCharBuffer());
fc = new FileOutputStream("data2.txt").getChannel();
buff = ByteBuffer.allocate(100);
buff.asCharBuffer().put("some text as charbuffer");
fc.write(buff);
fc.close();
fc = new FileInputStream("data2.txt").getChannel();
buff.clear();
fc.read(buff);
buff.flip();
System.out.println(buff.asCharBuffer());
}
}
//*output:
????
Decoded using GBK: Some text
Some text by utf
some text as charbuffer
*//
打印第一行错误原因是因为既没有编码也没有解码。
第二行有解码
第三行有编码
第四行读写都指定了相应的数据格式
在这个例子基础上,下面是一个对各种数据类型的例子
import java.nio.ByteBuffer;
public class GetData {
private static final int BSIZE = 1024;
public static void main(String[] args) {
ByteBuffer bb = ByteBuffer.allocate(BSIZE);
int i =0;
while(i++<bb.limit()){
if(bb.get()!=0)
System.out.println("nonzero");
}
System.out.println("i="+i);
bb.rewind();
bb.asCharBuffer().put("Howdy!");
char c;
while((c=bb.getChar())!=0){
System.out.print(c+" ");
}
System.out.println();
bb.rewind();
bb.asShortBuffer().put((short)471142);
System.out.println(bb.getShort());
bb.rewind();
bb.asIntBuffer().put(99471142);
System.out.println(bb.getInt());
bb.rewind();
bb.asLongBuffer().put(99471142);
System.out.println(bb.getLong());
bb.rewind();
bb.asFloatBuffer().put(99471142);
System.out.println(bb.getFloat());
bb.rewind();
bb.asDoubleBuffer().put(99471142);
System.out.println(bb.getDouble());
bb.rewind();
}
}
//*Output:
i=1025
H o w d y !
12390
99471142
99471142
9.9471144E7
9.9471142E7
*//
11.2 视图缓冲器
通过某个特定的基本数据类型的view查看其底层的ByteBuffer。ByteBuffer依然是实际保存数据的地方,并支持前面的view,所以对view任何修改都会映射成为修改后ByteBuffer中的数据。
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
public class IntBufferDemo {
private static final int BSIZE = 1024;
public static void main(String[] args) {
ByteBuffer bb = ByteBuffer.allocate(BSIZE);
IntBuffer ib = bb.asIntBuffer();
ib.put(new int[]{11,42,47,99,143,811,1016});
System.out.println("ib3:"+ib.get(3));
ib.put(3, 1811);
ib.flip();
while(ib.hasRemaining()){
System.out.println(ib.get());
}
}
}
//*Output:
ib3:99
11
42
47
1811
143
811
1016
*//
在同一个ByteBuffer建立不同的view buffer,将同一个字节序列翻译成short, int, float, long, double.
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.LongBuffer;
import java.nio.ShortBuffer;
public class ViewBuffers {
public static void main(String[] args) {
ByteBuffer bb = ByteBuffer.wrap(new byte[]{0,0,0,0,0,0,0,'a'});
bb.rewind();
System.out.println("Byte Buffer ");
while(bb.hasRemaining()){
System.out.print(bb.position()+"->"+bb.get()+", ");
}
System.out.println();
CharBuffer cb = ((ByteBuffer)bb.rewind()).asCharBuffer();
System.out.println("Char Buffer");
while(cb.hasRemaining()){
System.out.print(cb.position()+"->"+cb.get()+", ");
}
System.out.println();
FloatBuffer fb = ((ByteBuffer)bb.rewind()).asFloatBuffer();
System.out.println("Float Buffer");
while(fb.hasRemaining()){
System.out.print(fb.position()+"->"+fb.get()+", ");
}
System.out.println();
IntBuffer ib = ((ByteBuffer)bb.rewind()).asIntBuffer();
System.out.println("Int Buffer");
while(ib.hasRemaining()){
System.out.print(ib.position()+"->"+ib.get()+", ");
}
System.out.println();
LongBuffer lb = ((ByteBuffer)bb.rewind()).asLongBuffer();
System.out.println("Long Buffer");
while(lb.hasRemaining()){
System.out.print(lb.position()+"->"+lb.get()+", ");
}
System.out.println();
ShortBuffer sb = ((ByteBuffer)bb.rewind()).asShortBuffer();
System.out.println("Short Buffer");
while(sb.hasRemaining()){
System.out.print(sb.position()+"->"+sb.get()+", ");
}
System.out.println();
DoubleBuffer db = ((ByteBuffer)bb.rewind()).asDoubleBuffer();
System.out.println("Double Buffer");
while(db.hasRemaining()){
System.out.print(db.position()+"->"+db.get()+", ");
}
System.out.println();
}
}
//*output:
Byte Buffer
0->0, 1->0, 2->0, 3->0, 4->0, 5->0, 6->0, 7->97,
Char Buffer
0-> , 1->, 2-> , 3-> a,
Float Buffer
0->0.0, 1->1.36E-43,
Int Buffer
0->0, 1->97,
Long Buffer
0->97,
Short Buffer
0->0, 1->0, 2->0, 3->97,
Double Buffer
0->4.8E-322,
*//
ByteBuffer是8字节数组产生,然后通过不同的基本类型view buffer显示出来,如下图:
11.2.1字节存放次序(二进制)
big endian 高位优先:将最重要的字节存放在地址最低的存储单元。
little endian 低位优先:讲最重要的字节存放在地址最高的存储单元。
ByteBuffer以高位优先存储数据,网上传送也常用此方式。
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;
public class Endians {
public static void main(String[] args) {
// TODO Auto-generated method stub
String s = "abcdef";
ByteBuffer bb = ByteBuffer.wrap(new byte[12]);
bb.asCharBuffer().put(s);
System.out.println(Arrays.toString(bb.array()));
bb.rewind();
bb.order(ByteOrder.BIG_ENDIAN);
bb.asCharBuffer().put(s);
System.out.println(Arrays.toString(bb.array()));
bb.rewind();
bb.order(ByteOrder.LITTLE_ENDIAN);
bb.asCharBuffer().put(s);
System.out.println(Arrays.toString(bb.array()));
}
}
//*Output:
[0, 97, 0, 98, 0, 99, 0, 100, 0, 101, 0, 102]
[0, 97, 0, 98, 0, 99, 0, 100, 0, 101, 0, 102]
[97, 0, 98, 0, 99, 0, 100, 0, 101, 0, 102, 0]
*//
11.2.2 深入缓冲区
Buffer由数据和四个索引组成:mark, position, limit, capacity。
capacity(): 返回缓冲区容量;
clear(): 清空缓冲区,position=0, limit=capacity();
flip(): limit=position, position=0;
limit(): 返回limit值;
limit(int lim): 设置limit值;
mark(): mark=position;
position(): 返回position值;
position(int pos): 设置position值;
remaining(): 返回(limit - position);
hasRemaining(): 如果存在position和limit之间元素(limit-position>0?),则返回ture;
reset(): position=mark.
例子:交换缓冲器中相邻的字符
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
public class UsingBuffers {
private static void symmetricScramble(CharBuffer buffer){
while(buffer.hasRemaining()){
buffer.mark();
char c1 = buffer.get();
char c2 = buffer.get();
buffer.reset();
buffer.put(c2).put(c1);
}
}
public static void main(String[] args) {
char[] data = "UsingBuffers".toCharArray();
ByteBuffer bb = ByteBuffer.allocate(data.length*2);
CharBuffer cb = bb.asCharBuffer();
cb.put(data);
System.out.println(cb.rewind());
symmetricScramble(cb);
System.out.println(cb.rewind());
symmetricScramble(cb);
System.out.println(cb.rewind());
}
}
//*Output:
UsingBuffers
sUniBgfuefsr
UsingBuffers
*//
11.2.3 内存映射文件
内存映射文件用于创建和修改因为太大不能放入内存的文件,使用这种方式,我们可以假定这个文件都放在内存中,而且可以完全把它当做巨大的数组来访问。
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
public class LargeMappedFiles {
static int length = 0x8FFFFFF; //128MB
public static void main(String[] args) throws IOException, Exception {
// TODO Auto-generated method stub
MappedByteBuffer out
= new RandomAccessFile("test.dat","rw").getChannel().map(FileChannel.MapMode.READ_WRITE, 0, length);
for(int i=0;i<length;i++){
out.put((byte)'x');
}
System.out.println("Finished writing");
for(int i=length/2;i<length/2+60;i++){
System.out.print((char)out.get(i));
}
}
}
//*Output:
Finished writing
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
*//
11.2.4 加锁
import java.io.FileOutputStream;
import java.nio.channels.FileLock;
import java.util.concurrent.TimeUnit;
public class FileLocking {
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
FileOutputStream fos = new FileOutputStream("file.txt");
FileLock fl = fos.getChannel().tryLock();
if(fl!=null){
System.out.println("Locked File");
TimeUnit.MILLISECONDS.sleep(1000);
fl.release();
System.out.println("Released Lock");
}
fos.close();
}
}
//*Output
Locked File
Released Lock
*//
这里是对整个文件加锁,tryLock()是非阻塞式的,它设法获得锁,否则直接在方法中返回。lock()是阻塞式,阻塞进程直至可以获得锁,或者调用lock()线程中断,或者lock()的通道关闭,FileLock.release()可以释放锁。
11.2.5 部分加锁
tryLock(long position, long size, boolean shared) / lock(long position, long size, boolean shared)
无参数加锁根据文件大小变化而变化,固定大小加锁不会随着文件变化变化,如果获得一段区域加锁(position~position+size),当文件增大时候,position+size以外的部分将不会被锁定。
对于独占锁或者共享锁由底层的操作系统所决定。如果操作系统不支持共享锁并为每一个请求都创建一个锁,那么它就是独占锁,FileLock.isShared()可以查询。
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
public class LockingMappedFiles {
static final int LENGTH = 0x8FFFFFF;//128MB
static FileChannel fc;
public static void main(String[] args) throws IOException {
fc = new RandomAccessFile("test.dat", "rw").getChannel();
MappedByteBuffer out = fc.map(FileChannel.MapMode.READ_WRITE, 0, LENGTH);
for(int i=0;i<LENGTH;i++){
out.put((byte)'x');
}
new LockAndModify(out,0,0+LENGTH/3);
new LockAndModify(out,LENGTH/2,LENGTH/2+LENGTH/4);
}
private static class LockAndModify extends Thread{
private ByteBuffer buff;
private int start, end;
LockAndModify(ByteBuffer mbb, int start, int end){
this.start=start;
this.end=end;
mbb.limit(end);
mbb.position(start);
buff = mbb.slice();
start();
}
@Override
public void run() {
try {
FileLock fl = fc.lock(start, end, false);
System.out.println("Locked: "+start+" to "+end);
while(buff.position()<buff.limit()-1){
buff.put((byte)(buff.get()+1));
}
fl.release();
System.out.println("Released: "+start+" to "+end);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
//*Output
Locked: 0 to 50331647
Locked: 75497471 to 113246206
Released: 75497471 to 113246206
Released: 0 to 50331647
*//
finished nio,IO未完持续~:)