Buffer是一个抽象类,位于java.nio包中,主要用作缓冲区。注意:Buffer是非线程安全类。
缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存。这块内存被包装成NIO Buffer对象,并提供了一组方法,用来方便的访问该块内存。
NIO 有以下几种Buffer类型:
-
ByteBuffer
-
MappedByteBuffer
-
CharBuffer
-
DoubleBuffer
-
FloatBuffer
-
IntBuffer
-
LongBuffer
-
ShortBuffer

一、属性
Buffer有四个基本属性:
1、capacity: 容量,buffer能够容纳的最大元素数目,在Buffer创建时设定并不能更改
2、limit: buffer中有效位置数目
3、position: 下一个读或者写的位置
4、mark: 用于记忆的标志位,配合reset()使用,初始值未设定,调用mark后将当前position设为值
属性的关系与图解 见:https://blog.csdn.net/u013096088/article/details/78638245
标记、位置、限制和容量值遵守以下不变式:
0 <= 标记mark <= 位置position <= 限制limit <= 容量capacity
二、API
public final int capacity( ) 返回此缓冲区的容量。
public final int position() 返回此缓冲区的位置。
public final Buffer position(int newPosition) 设置此缓冲区的位置。如果mark已定义且大于新的位置,则丢弃该标记。
public final int limit( ) 返回此缓冲区的限制。
public final Buffer limit (int newLimit) 设置此缓冲区的限制。
public final Buffer mark( ) 在此缓冲区的位置设置标记。
public final Buffer reset( ) 将此缓冲区的位置重置为以前mark的位置。
public final Buffer clear( ) 清除此缓冲区。将position设置为 0,将limit设置为容量,并丢弃mark。
public final Buffer flip( ) 为读做好准备。它将limit设置为当前位置,然后将position设置为 0。如果已定义了标记,则丢弃该标记。
public final Buffer rewind( ) 一般flip()只能被执行一次,想第二次执行flip(),请使用rewind()。它使limit保持不变,将position设置为 0 ,并丢弃mark。
public final int remaining( ) 返回当前位置与限制之间的元素数,即还未读出的字节数。
public final boolean hasRemaining( ) 告知在当前位置和限制之间是否有元素。
public abstract boolean isReadOnly( ) 告知此缓冲区是否为只读缓冲区。
public abstract ByteBuffer compact();压缩数据
注释:
-
由于ByteBuffer是非线程安全的,所以多线程访问的时候也必须加锁。
-
ByteBuffer在内部也是利用byte[]作为内存缓冲区,只不过多提供了一些标记变量而已。当多线程访问的时候,可以清楚的知道当前数据的位置。
三、操作(以ByteBuffer为例)
1、访问:
get(),从当前position位置读取
get(index),从index位置读取,不改变当前position,下面要说到的put也一样。
2、填充:
put(byte),在当前position位置填充
put(index,byte),按照绝对位置填充不改变当前position属性
3、flipping,试想,我们将填充完毕的buffer传递给socket输出,那么socket读取是依据position属性确定,就会从结尾后一位开始读,这样肯定是不正确的,如果要正确的读取我们先要:
buffer.limit(buffer.position( )).position(0);
将limit设为position, 将position设为0,这个操作就叫flipping,API直接提供了这个操作: buffer.flip( );
**特别注意:**flip()方法会改变limit属性,将limit属性从capacity设置为当前position。
rewind()方法与flip()类似,但是仅将position设为0,同时取消mark标记,而不改变limit,通常用于重新读取已经被flip的buffer。
flip()另一个注意点是,两次调用buffer的flip方法,将使得position和limit属性都为0。
4、迭代取元素:
for (int i = 0; buffer.hasRemaining( ), i++) {
myByteArray [i] = buffer.get( );
}
int count = buffer.remaining( );
for (int i = 0; i < count, i++) {
myByteArray [i] = buffer.get( );
}
ByteBuffer不是线程安全的,前一种方式适合并发访问,后一种方式效率更高。这两种方式都是一个一个取,效率都比批量取低。
5.clear()方法,将buffer重设为空状态,也就是设置limit=capacity,position=0,以便重复利用。
**特别注意:**reset()方法和clear()方法一样用于写模式,
区别是reset()的作用是丢弃mark位置以后的数据,重新从mark位置开始写入,且mark不能未设置;而clear是从0位置开始重新写入。
6.compact()方法,用于压缩buffer,这个方法在多次重复调用时是比较低效。
compact()的作用是压缩数据。比如当前EOF是6,当前指针指向2(即0,1的数据已经写出了,没用了),那么compact方法将把2,3,4,5的数据挪到0,1,2,3的位置,然后指针指向4的位置。这样的意思是,从4的位置接着再写入数据。
7.mark(),初始是未定义的,这适合如果调用reset将抛出InvalidMarkException。调用makr()后,将当前position设为mark以便reset时返回。注意,rewind( ), clear( ), and flip( )方法都将丢弃已经创建的mark。调用limit(index),positioon(index),如果index的值小于当前mark,mark也将被丢弃。
8.比较,可以通过equals()和compateTo()方法来比较两个buffer,equals()返回boolean,compateTo()返回0,-1,1。两个buffer equal的条件是:
**特别注意:**equals()方法当满足下列条件时,表示两个Buffer 相等:
1)类型相同(byte、char、int等)
2)剩余元素的数目相等
3)剩余元素也一一相等
equals只是比较Buffer的一部分,不是每一个在它里面的元素都比较(即它只比较Buffer中的剩余元素)。
compareTo()方法比较两个Buffer的剩余元素(byte、char等), 如果满足下列条件,则认为一个Buffer“小于”另一个Buffer:
第一个不相等的元素小于另一个Buffer中对应的元素 。
所有元素都相等,但第一个Buffer比另一个先耗尽(第一个Buffer的元素个数比另一个少)。
9、批量移动数据,为了更有效地进行数据传送,批量的数据存取肯定是不能少的,Buffer及其子类都有提供类似的方法,比如CharBuffer:
public CharBuffer get (char [] dst)
public CharBuffer get (char [] dst, int offset, int length)
public final CharBuffer put (char[] src)
public CharBuffer put (char [] src, int offset, int length)
public CharBuffer put (CharBuffer src)
public final CharBuffer put (String src)
public CharBuffer put (String src, int start, int end)
四、创建Buffer
Buffer以及其子类都无法直接new,而必须把通过他们提供的工厂方法来创建。通常有两种方式:
1、allocate,例如
CharBuffer charBuffer = CharBuffer.allocate (100);
将在堆上分配一个可以存储100个字符的数组作为backing store。
2、wrap,包装一个已有的数组:
char [] myArray = new char [100];
CharBuffer charbuffer = CharBuffer.wrap (myArray);
注意,这样的方式创建的Buffer,将不会在堆上创建新的数组,而是直接利用myArray做backing store,这意味着任何对myArray或者buffer的修改都将影响到buffer或者myArray。可以通过public final boolean hasArray( )方法来判断是否拥有一个数组,通过array()方法取得这个数组。
五、复制Buffer
其实这个复制也是“浅拷贝”,通过duplicate()方法将返回一个新创建的buffer,这个新buffer与原来的Buffer共享数据,一样的capacity,但是有自己的position、limit和mark属性。通过asReadOnlyBuffer()方法复制的buffer与duplicate()类似,但是是只读的,不能调用put。比较特别的是slice()方法,故名思议,类似切割一个Buffer出来,与duplicate类似,但是它将从原来Buffer的当前position开始,并且capacity等于原来Buffer的剩余元素数目,也就是(limit-position)。
示例:
1、读写操作

1 1 package com.example.nio; 2 2 3 3 import java.io.FileInputStream; 4 4 import java.io.FileNotFoundException; 5 5 import java.io.FileOutputStream; 6 6 import java.io.IOException; 7 7 import java.io.RandomAccessFile; 8 8 import java.nio.ByteBuffer; 9 9 import java.nio.channels.FileChannel; 10 10 11 11 /** 12 12 * 读写操作 13 13 */ 14 14 public class ReadWriteDemo { 15 15 private static final int SIZE = 1024; 16 16 17 17 public static void main(String[] args) throws Exception { 18 18 String filePath; 19 19 20 20 filePath = writeTest(); 21 21 readTest(filePath); 22 22 23 23 filePath = writeTest2(); 24 24 readTest(filePath); 25 25 } 26 26 //注意:buffer.flip();一定得有,如果没有,就是从文件最后开始读取的,当然读出来的都是byte=0时候的字符。 27 27 // 通过buffer.flip();这个语句,就能把buffer的当前位置更改为buffer缓冲区的第一个位置。 28 28 29 29 public static String writeTest() { 30 30 String filePath = "C:\\Users\\use\\Desktop\\111.txt"; 31 31 // 获取通道,该通道允许写操作 32 32 33 33 try ( 34 34 FileChannel fc = new FileOutputStream(filePath).getChannel(); 35 35 ) { 36 36 String data = "1234##"; 37 37 // 将字节数组包装到缓冲区中 38 38 fc.write(ByteBuffer.wrap(data.getBytes())); 39 39 } catch (FileNotFoundException e) { 40 40 e.printStackTrace(); 41 41 } catch (IOException e) { 42 42 e.printStackTrace(); 43 43 } 44 44 return filePath; 45 45 46 46 } 47 47 48 48 public static String writeTest2() { 49 49 // 随机读写文件流创建的管道 50 50 String filePath = "C:\\Users\\use\\Desktop\\222.txt"; 51 51 String data = "abcd##"; 52 52 try { 53 53 FileChannel fc = new RandomAccessFile(filePath, "rw").getChannel(); 54 54 // fc.position()计算从文件的开始到当前位置之间的字节数 55 55 System.out.println("此通道的文件位置:" + fc.position()); 56 56 // 设置此通道的文件位置,fc.size()此通道的文件的当前大小,该条语句执行后,通道位置处于文件的末尾 57 57 fc.position(fc.size()); 58 58 // 在文件末尾写入字节 59 59 fc.write(ByteBuffer.wrap(data.getBytes())); 60 60 } catch (FileNotFoundException e) { 61 61 e.printStackTrace(); 62 62 } catch (IOException e) { 63 63 e.printStackTrace(); 64 64 } 65 65 return filePath; 66 66 } 67 67 68 68 public static void readTest(String filePath) { // 用通道读取文件 69 69 70 70 try ( 71 71 FileChannel fc = new FileInputStream(filePath).getChannel(); 72 72 ) { 73 73 74 74 ByteBuffer buffer = ByteBuffer.allocate(SIZE); 75 75 int len; 76 76 // 将文件内容读到指定的缓冲区中 77 77 while ((len = fc.read(buffer)) != -1) { 78 78 // 注意先调用flip方法反转Buffer,再从Buffer读取数据 79 79 buffer.flip();//此行语句一定要有 80 80 81 81 // 有几种方式可以操作ByteBuffer 82 82 // // 1.可以将当前Buffer包含的字节数组全部读取出来 83 83 // byte[] bytes = buffer.array(); 84 84 // System.out.println("========方法1:bytes = " + bytes); 85 85 // System.out.println(new String(bytes)); 86 86 // 87 87 // // 2.类似与InputStrean的read(byte[],offset,len)方法读取 88 88 // buffer.get(bytes, 0, len); 89 89 // System.out.println("========方法2:bytes = " + new String(bytes, 0, len)); 90 90 91 91 // 3.也可以遍历Buffer读取每个字节数据 92 92 // 一个字节一个字节打印在控制台,但这种更慢且耗时 93 93 System.out.println("========方法3:一个字节一个字节打印"); 94 94 while (buffer.hasRemaining()) { 95 95 System.out.print((char) buffer.get()); 96 96 } 97 97 // 最后注意调用clear方法,将Buffer的位置回归到0 98 98 buffer.clear(); 99 99 } 100100 } catch (FileNotFoundException e) { 101101 e.printStackTrace(); 102102 } catch (IOException e) { 103103 e.printStackTrace(); 104104 } 105105 106106 } 107107 }
读写操作
2、数据转移操作(复制操作)

1 1 package com.example.nio; 2 2 3 3 import java.io.BufferedInputStream; 4 4 import java.io.BufferedOutputStream; 5 5 import java.io.File; 6 6 import java.io.FileInputStream; 7 7 import java.io.FileNotFoundException; 8 8 import java.io.FileOutputStream; 9 9 import java.io.IOException; 1010 import java.io.RandomAccessFile; 1111 import java.nio.channels.FileChannel; 1212 1313 /** 1414 * 数据转移操作(复制操作) 1515 */ 1616 public class TransferDemo { 1717 public static void main(String[] args) { 1818 String filePath = "C:\\Users\\use\\Desktop\\test.txt"; 1919 // initBigFile(filePath); 2020 System.out.println(String.format("===>文件大小:%s 字节", new File(filePath).length())); 2121 2222 // 普通Java IO 缓冲流读取 2323 transferTest2(filePath); 2424 //普通 NIO 2525 transferTest1(filePath); 2626 } 2727 2828 public static void initBigFile(String filePath) { 2929 3030 try (FileOutputStream outputStream = new FileOutputStream(filePath);) { 3131 3232 File file = new File(filePath); 3333 if (!file.exists()) { 3434 file.mkdir(); 3535 } 3636 long size = 100 * 1024 * 1024;//100M 3737 String data = 3838 "111111111111111111111111111111111测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + 3939 "22222222222222222222222222222222222222222222张三张三张三张三张三张三张三张三张三bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + 4040 "33333333333333333333333333333333测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试cccccccccccccccccccccccccccccccccccccccccccc"; 4141 4242 while (file.length() < size) { 4343 outputStream.write(data.getBytes()); 4444 } 4545 System.out.println(String.format("===>文件大小:%s 字节", file.length())); 4646 } catch (FileNotFoundException e) { 4747 e.printStackTrace(); 4848 } catch (IOException e) { 4949 e.printStackTrace(); 5050 } 5151 } 5252 5353 /** 5454 * 普通 NIO transfer 5555 * 5656 * @param filePath 5757 */ 5858 public static void transferTest1(String filePath) { 5959 String descPath = "C:\\Users\\use\\Desktop\\test1.txt"; 6060 long start = System.currentTimeMillis(); 6161 try ( 6262 FileChannel fromChannel = new RandomAccessFile(filePath, "rw").getChannel(); 6363 FileChannel toChannel = new RandomAccessFile(descPath, "rw").getChannel(); 6464 ) { 6565 fromChannel.transferTo(0, fromChannel.size(), toChannel); 6666 } catch (FileNotFoundException e) { 6767 e.printStackTrace(); 6868 } catch (IOException e) { 6969 e.printStackTrace(); 7070 } 7171 System.out.println(String.format("===>普通 NIO 读取并打印文件耗时:%s毫秒", System.currentTimeMillis() - start)); 7272 } 7373 7474 /** 7575 * 普通Java IO 缓冲流读取 7676 * 7777 * @param filePath 7878 */ 7979 public static void transferTest2(String filePath) { 8080 String descPath = "C:\\Users\\use\\Desktop\\test2.txt"; 8181 long start = System.currentTimeMillis(); 8282 try ( 8383 BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(filePath)); 8484 BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(descPath)); 8585 ) { 8686 int len; 8787 while ((len = inputStream.read()) != -1) { 8888 outputStream.write(len); 8989 } 9090 } catch (FileNotFoundException e) { 9191 e.printStackTrace(); 9292 } catch (IOException e) { 9393 e.printStackTrace(); 9494 } 9595 9696 System.out.println(String.format("===>普通Java IO 读取并打印文件耗时:%s毫秒", System.currentTimeMillis() - start)); 9797 } 9898 }
数据转移操作(复制操作)
3、流读取比较

1 1 package com.example.nio; 2 2 3 3 import java.io.BufferedInputStream; 4 4 import java.io.File; 5 5 import java.io.FileInputStream; 6 6 import java.io.IOException; 7 7 import java.io.RandomAccessFile; 8 8 import java.nio.ByteBuffer; 9 9 import java.nio.MappedByteBuffer; 10 10 import java.nio.channels.FileChannel; 11 11 import java.nio.channels.FileChannel.MapMode; 12 12 13 13 /** 14 14 * Channel类似与流,数据可以从Channel读取到Buffer,也可以从Buffer写入到Channel 15 15 * 但通道和流还是有区别,比如流只能是单向读或写,而通道可以异步读写 16 16 */ 17 17 public class FileChannelTest { 18 18 19 19 // 110M 20 20 private static String file = "C:\\Users\\use\\Desktop\\111.txt"; 21 21 22 22 public static void main(String[] args) throws IOException { 23 23 // 每次读取字节数 24 24 int allocate = 1024; 25 25 26 26 // 普通 NIO 读取 27 27 readByChannelTest(allocate); // 28151毫秒 28 28 29 29 // // 普通 NIO 读取 30 30 // // 每次读取1个字节,每次读取1个字节太慢了 31 31 // readByChannelTest(1); 32 32 33 33 // 使用内存映射文件来读取 34 34 // 从FileChannel拿到MappedByteBuffer,读取文件内容 35 35 readByChannelTest3(allocate); // 61毫秒,甚至不到100毫秒 36 36 37 37 // 对于一个只有110M的文件,验证使用FileChannel映射得到MappedByteBuffer 38 38 // 就能大幅提交文件读取速度 39 39 40 40 // 普通的缓冲流读取 41 41 readByBufferdStream(allocate); // 3922毫秒 42 42 } 43 43 44 44 /** 45 45 * 使用FileChannel读取文件,并打印在控制台 46 46 * 47 47 * @param allocate 每次读取多少个字节 48 48 * @throws IOException 49 49 */ 50 50 public static void readByChannelTest(int allocate) throws IOException { 51 51 long start = System.currentTimeMillis(); 52 52 FileInputStream fis = new FileInputStream(file); 53 53 54 54 // 1.从FileInputStream对象获取文件通道FileChannel 55 55 FileChannel channel = fis.getChannel(); 56 56 long size = channel.size(); 57 57 58 58 // 2.从通道读取文件内容 59 59 byte[] bytes = new byte[allocate]; 60 60 ByteBuffer byteBuffer = ByteBuffer.allocate(allocate); 61 61 62 62 // channel.read(ByteBuffer) 方法就类似于 inputstream.read(byte) 63 63 // 每次read都将读取 allocate 个字节到ByteBuffer 64 64 int len; 65 65 while ((len = channel.read(byteBuffer)) != -1) { 66 66 // 注意先调用flip方法反转Buffer,再从Buffer读取数据 67 67 byteBuffer.flip(); 68 68 69 69 // 有几种方式可以操作ByteBuffer 70 70 // 1.可以将当前Buffer包含的字节数组全部读取出来 71 71 bytes = byteBuffer.array(); 72 72 // System.out.print(new String(bytes)); 73 73 74 74 // 2.类似与InputStrean的read(byte[],offset,len)方法读取 75 75 // byteBuffer.get(bytes, 0, len); 76 76 // System.out.print(new String(bytes, 0 ,len)); 77 77 78 78 // 3.也可以遍历Buffer读取每个字节数据 79 79 // 一个字节一个字节打印在控制台,但这种更慢且耗时 80 80 // while(byteBuffer.hasRemaining()) { 81 81 // System.out.print((char)byteBuffer.get()); 82 82 // } 83 83 84 84 // 最后注意调用clear方法,将Buffer的位置回归到0 85 85 byteBuffer.clear(); 86 86 87 87 } 88 88 89 89 // 关闭通道和文件流 90 90 channel.close(); 91 91 fis.close(); 92 92 93 93 long end = System.currentTimeMillis(); 94 94 System.out.println(String.format("\n===>文件大小:%s 字节", size)); 95 95 System.out.println(String.format("===>读取并打印文件耗时:%s毫秒", end - start)); 96 96 } 97 97 98 98 /** 99 99 * 仍然是根据FileChannel操作ByteBuffer,从ByteBuffer读取内容 100100 * 通道读取文件,速度比内存映射慢很多,甚至比普通缓冲流要慢 101101 * 102102 * @param allocate 103103 * @throws IOException 104104 */ 105105 public static void readByChannelTest2(int allocate) throws IOException { 106106 long start = System.currentTimeMillis(); 107107 FileInputStream fis = new FileInputStream(file); 108108 109109 // 1.从FileInputStream对象获取文件通道FileChannel 110110 FileChannel channel = fis.getChannel(); 111111 long size = channel.size(); 112112 113113 // 每次读取allocate个字节,计算要循环读取多少次 114114 long cycle = size / allocate; 115115 // 看是否能整数倍读完 116116 int mode = (int) (size % allocate); 117117 118118 // 循环读取 119119 byte[] bytes; 120120 ByteBuffer byteBuffer = ByteBuffer.allocate(allocate); 121121 for (long i = 0; i < cycle; i++) { 122122 if (channel.read(byteBuffer) != -1) { 123123 byteBuffer.flip(); 124124 bytes = byteBuffer.array(); 125125 // System.out.print(new String(bytes)); 126126 byteBuffer.clear(); 127127 } 128128 } 129129 130130 // 读取最后mode个字节 131131 if (mode > 0) { 132132 byteBuffer = ByteBuffer.allocate(mode); 133133 if (channel.read(byteBuffer) != -1) { 134134 byteBuffer.flip(); 135135 bytes = byteBuffer.array(); 136136 // System.out.print(new String(bytes)); 137137 byteBuffer.clear(); 138138 } 139139 } 140140 141141 // 关闭通道和文件流 142142 channel.close(); 143143 fis.close(); 144144 145145 long end = System.currentTimeMillis(); 146146 System.out.println(String.format("\n===>文件大小:%s 字节", size)); 147147 System.out.println(String.format("===>读取并打印文件耗时:%s毫秒", end - start)); 148148 } 149149 150150 /** 151151 * 通过 FileChannel.map()拿到MappedByteBuffer 152152 * 使用内存文件映射,速度会快很多 153153 * 154154 * @throws IOException 155155 */ 156156 public static void readByChannelTest3(int allocate) throws IOException { 157157 long start = System.currentTimeMillis(); 158158 159159 RandomAccessFile fis = new RandomAccessFile(new File(file), "rw"); 160160 FileChannel channel = fis.getChannel(); 161161 long size = channel.size(); 162162 163163 // 构建一个只读的MappedByteBuffer 164164 MappedByteBuffer mappedByteBuffer = channel.map(MapMode.READ_ONLY, 0, size); 165165 166166 // 如果文件不大,可以选择一次性读取到数组 167167 // byte[] all = new byte[(int)size]; 168168 // mappedByteBuffer.get(all, 0, (int)size); 169169 // 打印文件内容 170170 // System.out.println(new String(all)); 171171 172172 // 如果文件内容很大,可以循环读取,计算应该读取多少次 173173 byte[] bytes = new byte[allocate]; 174174 // 每次读取allocate个字节,计算要循环读取多少次 175175 long cycle = size / allocate; 176176 // 看是否能整数倍读完 177177 int mode = (int) (size % allocate); 178178 //byte[] eachBytes = new byte[allocate]; 179179 for (int i = 0; i < cycle; i++) { 180180 // 每次读取allocate个字节 181181 mappedByteBuffer.get(bytes); 182182 183183 // 打印文件内容,关闭打印速度会很快 184184 // System.out.print(new String(bytes)); 185185 } 186186 if (mode > 0) { 187187 bytes = new byte[mode]; 188188 mappedByteBuffer.get(bytes); 189189 190190 // 打印文件内容,关闭打印速度会很快 191191 // System.out.print(new String(bytes)); 192192 } 193193 194194 // 关闭通道和文件流 195195 channel.close(); 196196 fis.close(); 197197 198198 long end = System.currentTimeMillis(); 199199 System.out.println(String.format("\n===>文件大小:%s 字节", size)); 200200 System.out.println(String.format("===>读取并打印文件耗时:%s毫秒", end - start)); 201201 } 202202 203203 204204 /** 205205 * 普通Java IO 缓冲流读取 206206 * 207207 * @throws IOException 208208 */ 209209 public static void readByBufferdStream(int allocate) throws IOException { 210210 long start = System.currentTimeMillis(); 211211 BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file)); 212212 long size = bis.available(); 213213 214214 int len = 0; 215215 byte[] eachBytes = new byte[allocate]; 216216 while ((len = bis.read(eachBytes)) != -1) { 217217 // System.out.print(new String(eachBytes, 0, len)); 218218 } 219219 220220 bis.close(); 221221 222222 long end = System.currentTimeMillis(); 223223 System.out.println(String.format("\n===>文件大小:%s 字节", size)); 224224 System.out.println(String.format("===>读取并打印文件耗时:%s毫秒", end - start)); 225225 } 226226 227227 }
流读取比较
4、内存映射

1 1 package com.example.nio; 2 2 3 3 import java.io.FileInputStream; 4 4 import java.io.FileNotFoundException; 5 5 import java.io.IOException; 6 6 import java.io.RandomAccessFile; 7 7 import java.nio.ByteBuffer; 8 8 import java.nio.MappedByteBuffer; 9 9 import java.nio.channels.FileChannel; 1010 import java.util.Scanner; 1111 1212 /** 1313 * 内存映射 1414 */ 1515 public class MapDemo { 1616 public static void main(String[] args) { 1717 1818 // 从标准输入获取数据 1919 System.out.println("请输入:"); 2020 Scanner sc = new Scanner(System.in); 2121 String str = sc.nextLine(); 2222 byte[] bytes = str.getBytes(); 2323 2424 String filePath = "C:\\Users\\use\\Desktop\\mapTest.txt"; 2525 try { 2626 FileChannel fileChannel = new RandomAccessFile(filePath, "rw").getChannel(); 2727 // 获取内存映射缓冲区,并向缓冲区写入数据 2828 //追加 2929 MappedByteBuffer byteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, fileChannel.size(), bytes.length); 3030 byteBuffer.put(bytes); 3131 3232 // 再次打开刚刚的文件,读取其中的内容 3333 FileChannel channel = new FileInputStream(filePath).getChannel(); 3434 3535 ByteBuffer buffer = ByteBuffer.allocate(1024); 3636 System.out.println("文件内容:"); 3737 while (channel.read(buffer) != -1) { 3838 // 注意先调用flip方法反转Buffer,再从Buffer读取数据 3939 buffer.flip();//此行语句一定要有 4040 System.out.print(new String(buffer.array())); 4141 } 4242 System.out.println(""); 4343 4444 } catch (FileNotFoundException e) { 4545 e.printStackTrace(); 4646 } catch (IOException e) { 4747 e.printStackTrace(); 4848 } 4949 System.out.println(""); 5050 } 5151 }
内存映射
5、中文乱码问题

1 1 package com.example.nio; 2 2 3 3 4 4 import java.io.File; 5 5 import java.io.IOException; 6 6 import java.io.RandomAccessFile; 7 7 import java.nio.ByteBuffer; 8 8 import java.nio.channels.FileChannel; 9 9 import java.util.ArrayList; 10 10 import java.util.Date; 11 11 import java.util.List; 12 12 13 13 /** 14 14 * 中文乱码问题 15 15 */ 16 16 public class EnCodeDemo { 17 17 18 18 public static void main(String args[]) throws Exception { 19 19 20 20 int bufSize = 1000000;//一次读取的字节长度 21 21 String filePath = "C:\\Users\\use\\Desktop\\test.txt"; 22 22 File fin = new File(filePath);//读取的文件 23 23 String descPath = "C:\\Users\\use\\Desktop\\test_copy.txt"; 24 24 File fout = new File(descPath);//写出的文件 25 25 Date startDate = new Date(); 26 26 FileChannel fcin = new RandomAccessFile(fin, "r").getChannel(); 27 27 ByteBuffer rBuffer = ByteBuffer.allocate(bufSize); 28 28 29 29 FileChannel fcout = new RandomAccessFile(fout, "rws").getChannel(); 30 30 31 31 readFileByLine(bufSize, fcin, rBuffer, fcout); 32 32 Date endDate = new Date(); 33 33 34 34 System.out.print(startDate + "|" + endDate);//测试执行时间 35 35 if (fcin.isOpen()) { 36 36 fcin.close(); 37 37 } 38 38 if (fcout.isOpen()) { 39 39 fcout.close(); 40 40 } 41 41 } 42 42 43 43 public static void readFileByLine(int bufSize, FileChannel fcin, 44 44 ByteBuffer rBuffer, FileChannel fcout) { 45 45 String enter = "\n"; 46 46 List<String> dataList = new ArrayList<String>();//存储读取的每行数据 47 47 byte[] lineByte = new byte[0]; 48 48 49 49 // String encode = "GBK"; 50 50 String encode = "UTF-8"; 51 51 try { 52 52 //temp:由于是按固定字节读取,在一次读取中,第一行和最后一行经常是不完整的行,因此定义此变量来存储上次的最后一行和这次的第一行的内容, 53 53 //并将之连接成完成的一行,否则会出现汉字被拆分成2个字节,并被提前转换成字符串而乱码的问题 54 54 byte[] temp = new byte[bufSize]; 55 55 while (fcin.read(rBuffer) != -1) {//fcin.read(rBuffer):从文件管道读取内容到缓冲区(rBuffer) 56 56 int rSize = rBuffer.position();//读取结束后的位置,相当于读取的长度 57 57 byte[] bs = new byte[rSize];//用来存放读取的内容的数组 58 58 rBuffer.rewind();//将position设回0,所以你可以重读Buffer中的所有数据,此处如果不设置,无法使用下面的get方法 59 59 rBuffer.get(bs);//相当于rBuffer.get(bs,0,bs.length()):从position初始位置开始相对读,读bs.length个byte,并写入bs[0]到bs[bs.length-1]的区域 60 60 rBuffer.clear(); 61 61 62 62 int startNum = 0; 63 63 int LF = 10;//换行符 64 64 int CR = 13;//回车符 65 65 boolean hasLF = false;//是否有换行符 66 66 for (int i = 0; i < rSize; i++) { 67 67 if (bs[i] == LF) { 68 68 hasLF = true; 69 69 int tempNum = temp.length; 70 70 int lineNum = i - startNum; 71 71 lineByte = new byte[tempNum + lineNum];//数组大小已经去掉换行符 72 72 73 73 System.arraycopy(temp, 0, lineByte, 0, tempNum);//填充了lineByte[0]~lineByte[tempNum-1] 74 74 temp = new byte[0]; 75 75 System.arraycopy(bs, startNum, lineByte, tempNum, lineNum);//填充lineByte[tempNum]~lineByte[tempNum+lineNum-1] 76 76 77 77 String line = new String(lineByte, 0, lineByte.length, encode);//一行完整的字符串(过滤了换行和回车) 78 78 dataList.add(line); 79 79 // System.out.println(line); 80 80 writeFileByLine(fcout, line + enter); 81 81 82 82 //过滤回车符和换行符 83 83 if (i + 1 < rSize && bs[i + 1] == CR) { 84 84 startNum = i + 2; 85 85 } else { 86 86 startNum = i + 1; 87 87 } 88 88 89 89 } 90 90 } 91 91 if (hasLF) { 92 92 temp = new byte[bs.length - startNum]; 93 93 System.arraycopy(bs, startNum, temp, 0, temp.length); 94 94 } else {//兼容单次读取的内容不足一行的情况 95 95 byte[] toTemp = new byte[temp.length + bs.length]; 96 96 System.arraycopy(temp, 0, toTemp, 0, temp.length); 97 97 System.arraycopy(bs, 0, toTemp, temp.length, bs.length); 98 98 temp = toTemp; 99 99 } 100100 } 101101 if (temp != null && temp.length > 0) {//兼容文件最后一行没有换行的情况 102102 String line = new String(temp, 0, temp.length, encode); 103103 dataList.add(line); 104104 // System.out.println(line); 105105 writeFileByLine(fcout, line + enter); 106106 } 107107 } catch (IOException e) { 108108 e.printStackTrace(); 109109 } 110110 } 111111 112112 /** 113113 * 写到文件上 114114 * 115115 * @param fcout 116116 * @param line 117117 */ 118118 @SuppressWarnings("static-access") 119119 public static void writeFileByLine(FileChannel fcout, String line) { 120120 try { 121121 fcout.write(ByteBuffer.wrap(line.getBytes("UTF-8")), fcout.size()); 122122 } catch (IOException e) { 123123 e.printStackTrace(); 124124 } 125125 } 126126 }
中文乱码问题
转自:
https://blog.csdn.net/u013096088/article/details/78638245
https://blog.csdn.net/elf8848/article/details/39926897#