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 package com.example.nio; 2 3 import java.io.FileInputStream; 4 import java.io.FileNotFoundException; 5 import java.io.FileOutputStream; 6 import java.io.IOException; 7 import java.io.RandomAccessFile; 8 import java.nio.ByteBuffer; 9 import java.nio.channels.FileChannel; 10 11 /** 12 * 读写操作 13 */ 14 public class ReadWriteDemo { 15 private static final int SIZE = 1024; 16 17 public static void main(String[] args) throws Exception { 18 String filePath; 19 20 filePath = writeTest(); 21 readTest(filePath); 22 23 filePath = writeTest2(); 24 readTest(filePath); 25 } 26 //注意:buffer.flip();一定得有,如果没有,就是从文件最后开始读取的,当然读出来的都是byte=0时候的字符。 27 // 通过buffer.flip();这个语句,就能把buffer的当前位置更改为buffer缓冲区的第一个位置。 28 29 public static String writeTest() { 30 String filePath = "C:\Users\use\Desktop\111.txt"; 31 // 获取通道,该通道允许写操作 32 33 try ( 34 FileChannel fc = new FileOutputStream(filePath).getChannel(); 35 ) { 36 String data = "1234##"; 37 // 将字节数组包装到缓冲区中 38 fc.write(ByteBuffer.wrap(data.getBytes())); 39 } catch (FileNotFoundException e) { 40 e.printStackTrace(); 41 } catch (IOException e) { 42 e.printStackTrace(); 43 } 44 return filePath; 45 46 } 47 48 public static String writeTest2() { 49 // 随机读写文件流创建的管道 50 String filePath = "C:\Users\use\Desktop\222.txt"; 51 String data = "abcd##"; 52 try { 53 FileChannel fc = new RandomAccessFile(filePath, "rw").getChannel(); 54 // fc.position()计算从文件的开始到当前位置之间的字节数 55 System.out.println("此通道的文件位置:" + fc.position()); 56 // 设置此通道的文件位置,fc.size()此通道的文件的当前大小,该条语句执行后,通道位置处于文件的末尾 57 fc.position(fc.size()); 58 // 在文件末尾写入字节 59 fc.write(ByteBuffer.wrap(data.getBytes())); 60 } catch (FileNotFoundException e) { 61 e.printStackTrace(); 62 } catch (IOException e) { 63 e.printStackTrace(); 64 } 65 return filePath; 66 } 67 68 public static void readTest(String filePath) { // 用通道读取文件 69 70 try ( 71 FileChannel fc = new FileInputStream(filePath).getChannel(); 72 ) { 73 74 ByteBuffer buffer = ByteBuffer.allocate(SIZE); 75 int len; 76 // 将文件内容读到指定的缓冲区中 77 while ((len = fc.read(buffer)) != -1) { 78 // 注意先调用flip方法反转Buffer,再从Buffer读取数据 79 buffer.flip();//此行语句一定要有 80 81 // 有几种方式可以操作ByteBuffer 82 // // 1.可以将当前Buffer包含的字节数组全部读取出来 83 // byte[] bytes = buffer.array(); 84 // System.out.println("========方法1:bytes = " + bytes); 85 // System.out.println(new String(bytes)); 86 // 87 // // 2.类似与InputStrean的read(byte[],offset,len)方法读取 88 // buffer.get(bytes, 0, len); 89 // System.out.println("========方法2:bytes = " + new String(bytes, 0, len)); 90 91 // 3.也可以遍历Buffer读取每个字节数据 92 // 一个字节一个字节打印在控制台,但这种更慢且耗时 93 System.out.println("========方法3:一个字节一个字节打印"); 94 while (buffer.hasRemaining()) { 95 System.out.print((char) buffer.get()); 96 } 97 // 最后注意调用clear方法,将Buffer的位置回归到0 98 buffer.clear(); 99 } 100 } catch (FileNotFoundException e) { 101 e.printStackTrace(); 102 } catch (IOException e) { 103 e.printStackTrace(); 104 } 105 106 } 107 }
2、数据转移操作(复制操作)
1 package com.example.nio; 2 3 import java.io.BufferedInputStream; 4 import java.io.BufferedOutputStream; 5 import java.io.File; 6 import java.io.FileInputStream; 7 import java.io.FileNotFoundException; 8 import java.io.FileOutputStream; 9 import java.io.IOException; 10 import java.io.RandomAccessFile; 11 import java.nio.channels.FileChannel; 12 13 /** 14 * 数据转移操作(复制操作) 15 */ 16 public class TransferDemo { 17 public static void main(String[] args) { 18 String filePath = "C:\Users\use\Desktop\test.txt"; 19 // initBigFile(filePath); 20 System.out.println(String.format("===>文件大小:%s 字节", new File(filePath).length())); 21 22 // 普通Java IO 缓冲流读取 23 transferTest2(filePath); 24 //普通 NIO 25 transferTest1(filePath); 26 } 27 28 public static void initBigFile(String filePath) { 29 30 try (FileOutputStream outputStream = new FileOutputStream(filePath);) { 31 32 File file = new File(filePath); 33 if (!file.exists()) { 34 file.mkdir(); 35 } 36 long size = 100 * 1024 * 1024;//100M 37 String data = 38 "111111111111111111111111111111111测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + 39 "22222222222222222222222222222222222222222222张三张三张三张三张三张三张三张三张三bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + 40 "33333333333333333333333333333333测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试cccccccccccccccccccccccccccccccccccccccccccc"; 41 42 while (file.length() < size) { 43 outputStream.write(data.getBytes()); 44 } 45 System.out.println(String.format("===>文件大小:%s 字节", file.length())); 46 } catch (FileNotFoundException e) { 47 e.printStackTrace(); 48 } catch (IOException e) { 49 e.printStackTrace(); 50 } 51 } 52 53 /** 54 * 普通 NIO transfer 55 * 56 * @param filePath 57 */ 58 public static void transferTest1(String filePath) { 59 String descPath = "C:\Users\use\Desktop\test1.txt"; 60 long start = System.currentTimeMillis(); 61 try ( 62 FileChannel fromChannel = new RandomAccessFile(filePath, "rw").getChannel(); 63 FileChannel toChannel = new RandomAccessFile(descPath, "rw").getChannel(); 64 ) { 65 fromChannel.transferTo(0, fromChannel.size(), toChannel); 66 } catch (FileNotFoundException e) { 67 e.printStackTrace(); 68 } catch (IOException e) { 69 e.printStackTrace(); 70 } 71 System.out.println(String.format("===>普通 NIO 读取并打印文件耗时:%s毫秒", System.currentTimeMillis() - start)); 72 } 73 74 /** 75 * 普通Java IO 缓冲流读取 76 * 77 * @param filePath 78 */ 79 public static void transferTest2(String filePath) { 80 String descPath = "C:\Users\use\Desktop\test2.txt"; 81 long start = System.currentTimeMillis(); 82 try ( 83 BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(filePath)); 84 BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(descPath)); 85 ) { 86 int len; 87 while ((len = inputStream.read()) != -1) { 88 outputStream.write(len); 89 } 90 } catch (FileNotFoundException e) { 91 e.printStackTrace(); 92 } catch (IOException e) { 93 e.printStackTrace(); 94 } 95 96 System.out.println(String.format("===>普通Java IO 读取并打印文件耗时:%s毫秒", System.currentTimeMillis() - start)); 97 } 98 }
3、流读取比较
1 package com.example.nio; 2 3 import java.io.BufferedInputStream; 4 import java.io.File; 5 import java.io.FileInputStream; 6 import java.io.IOException; 7 import java.io.RandomAccessFile; 8 import java.nio.ByteBuffer; 9 import java.nio.MappedByteBuffer; 10 import java.nio.channels.FileChannel; 11 import java.nio.channels.FileChannel.MapMode; 12 13 /** 14 * Channel类似与流,数据可以从Channel读取到Buffer,也可以从Buffer写入到Channel 15 * 但通道和流还是有区别,比如流只能是单向读或写,而通道可以异步读写 16 */ 17 public class FileChannelTest { 18 19 // 110M 20 private static String file = "C:\Users\use\Desktop\111.txt"; 21 22 public static void main(String[] args) throws IOException { 23 // 每次读取字节数 24 int allocate = 1024; 25 26 // 普通 NIO 读取 27 readByChannelTest(allocate); // 28151毫秒 28 29 // // 普通 NIO 读取 30 // // 每次读取1个字节,每次读取1个字节太慢了 31 // readByChannelTest(1); 32 33 // 使用内存映射文件来读取 34 // 从FileChannel拿到MappedByteBuffer,读取文件内容 35 readByChannelTest3(allocate); // 61毫秒,甚至不到100毫秒 36 37 // 对于一个只有110M的文件,验证使用FileChannel映射得到MappedByteBuffer 38 // 就能大幅提交文件读取速度 39 40 // 普通的缓冲流读取 41 readByBufferdStream(allocate); // 3922毫秒 42 } 43 44 /** 45 * 使用FileChannel读取文件,并打印在控制台 46 * 47 * @param allocate 每次读取多少个字节 48 * @throws IOException 49 */ 50 public static void readByChannelTest(int allocate) throws IOException { 51 long start = System.currentTimeMillis(); 52 FileInputStream fis = new FileInputStream(file); 53 54 // 1.从FileInputStream对象获取文件通道FileChannel 55 FileChannel channel = fis.getChannel(); 56 long size = channel.size(); 57 58 // 2.从通道读取文件内容 59 byte[] bytes = new byte[allocate]; 60 ByteBuffer byteBuffer = ByteBuffer.allocate(allocate); 61 62 // channel.read(ByteBuffer) 方法就类似于 inputstream.read(byte) 63 // 每次read都将读取 allocate 个字节到ByteBuffer 64 int len; 65 while ((len = channel.read(byteBuffer)) != -1) { 66 // 注意先调用flip方法反转Buffer,再从Buffer读取数据 67 byteBuffer.flip(); 68 69 // 有几种方式可以操作ByteBuffer 70 // 1.可以将当前Buffer包含的字节数组全部读取出来 71 bytes = byteBuffer.array(); 72 // System.out.print(new String(bytes)); 73 74 // 2.类似与InputStrean的read(byte[],offset,len)方法读取 75 // byteBuffer.get(bytes, 0, len); 76 // System.out.print(new String(bytes, 0 ,len)); 77 78 // 3.也可以遍历Buffer读取每个字节数据 79 // 一个字节一个字节打印在控制台,但这种更慢且耗时 80 // while(byteBuffer.hasRemaining()) { 81 // System.out.print((char)byteBuffer.get()); 82 // } 83 84 // 最后注意调用clear方法,将Buffer的位置回归到0 85 byteBuffer.clear(); 86 87 } 88 89 // 关闭通道和文件流 90 channel.close(); 91 fis.close(); 92 93 long end = System.currentTimeMillis(); 94 System.out.println(String.format(" ===>文件大小:%s 字节", size)); 95 System.out.println(String.format("===>读取并打印文件耗时:%s毫秒", end - start)); 96 } 97 98 /** 99 * 仍然是根据FileChannel操作ByteBuffer,从ByteBuffer读取内容 100 * 通道读取文件,速度比内存映射慢很多,甚至比普通缓冲流要慢 101 * 102 * @param allocate 103 * @throws IOException 104 */ 105 public static void readByChannelTest2(int allocate) throws IOException { 106 long start = System.currentTimeMillis(); 107 FileInputStream fis = new FileInputStream(file); 108 109 // 1.从FileInputStream对象获取文件通道FileChannel 110 FileChannel channel = fis.getChannel(); 111 long size = channel.size(); 112 113 // 每次读取allocate个字节,计算要循环读取多少次 114 long cycle = size / allocate; 115 // 看是否能整数倍读完 116 int mode = (int) (size % allocate); 117 118 // 循环读取 119 byte[] bytes; 120 ByteBuffer byteBuffer = ByteBuffer.allocate(allocate); 121 for (long i = 0; i < cycle; i++) { 122 if (channel.read(byteBuffer) != -1) { 123 byteBuffer.flip(); 124 bytes = byteBuffer.array(); 125 // System.out.print(new String(bytes)); 126 byteBuffer.clear(); 127 } 128 } 129 130 // 读取最后mode个字节 131 if (mode > 0) { 132 byteBuffer = ByteBuffer.allocate(mode); 133 if (channel.read(byteBuffer) != -1) { 134 byteBuffer.flip(); 135 bytes = byteBuffer.array(); 136 // System.out.print(new String(bytes)); 137 byteBuffer.clear(); 138 } 139 } 140 141 // 关闭通道和文件流 142 channel.close(); 143 fis.close(); 144 145 long end = System.currentTimeMillis(); 146 System.out.println(String.format(" ===>文件大小:%s 字节", size)); 147 System.out.println(String.format("===>读取并打印文件耗时:%s毫秒", end - start)); 148 } 149 150 /** 151 * 通过 FileChannel.map()拿到MappedByteBuffer 152 * 使用内存文件映射,速度会快很多 153 * 154 * @throws IOException 155 */ 156 public static void readByChannelTest3(int allocate) throws IOException { 157 long start = System.currentTimeMillis(); 158 159 RandomAccessFile fis = new RandomAccessFile(new File(file), "rw"); 160 FileChannel channel = fis.getChannel(); 161 long size = channel.size(); 162 163 // 构建一个只读的MappedByteBuffer 164 MappedByteBuffer mappedByteBuffer = channel.map(MapMode.READ_ONLY, 0, size); 165 166 // 如果文件不大,可以选择一次性读取到数组 167 // byte[] all = new byte[(int)size]; 168 // mappedByteBuffer.get(all, 0, (int)size); 169 // 打印文件内容 170 // System.out.println(new String(all)); 171 172 // 如果文件内容很大,可以循环读取,计算应该读取多少次 173 byte[] bytes = new byte[allocate]; 174 // 每次读取allocate个字节,计算要循环读取多少次 175 long cycle = size / allocate; 176 // 看是否能整数倍读完 177 int mode = (int) (size % allocate); 178 //byte[] eachBytes = new byte[allocate]; 179 for (int i = 0; i < cycle; i++) { 180 // 每次读取allocate个字节 181 mappedByteBuffer.get(bytes); 182 183 // 打印文件内容,关闭打印速度会很快 184 // System.out.print(new String(bytes)); 185 } 186 if (mode > 0) { 187 bytes = new byte[mode]; 188 mappedByteBuffer.get(bytes); 189 190 // 打印文件内容,关闭打印速度会很快 191 // System.out.print(new String(bytes)); 192 } 193 194 // 关闭通道和文件流 195 channel.close(); 196 fis.close(); 197 198 long end = System.currentTimeMillis(); 199 System.out.println(String.format(" ===>文件大小:%s 字节", size)); 200 System.out.println(String.format("===>读取并打印文件耗时:%s毫秒", end - start)); 201 } 202 203 204 /** 205 * 普通Java IO 缓冲流读取 206 * 207 * @throws IOException 208 */ 209 public static void readByBufferdStream(int allocate) throws IOException { 210 long start = System.currentTimeMillis(); 211 BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file)); 212 long size = bis.available(); 213 214 int len = 0; 215 byte[] eachBytes = new byte[allocate]; 216 while ((len = bis.read(eachBytes)) != -1) { 217 // System.out.print(new String(eachBytes, 0, len)); 218 } 219 220 bis.close(); 221 222 long end = System.currentTimeMillis(); 223 System.out.println(String.format(" ===>文件大小:%s 字节", size)); 224 System.out.println(String.format("===>读取并打印文件耗时:%s毫秒", end - start)); 225 } 226 227 }
4、内存映射
1 package com.example.nio; 2 3 import java.io.FileInputStream; 4 import java.io.FileNotFoundException; 5 import java.io.IOException; 6 import java.io.RandomAccessFile; 7 import java.nio.ByteBuffer; 8 import java.nio.MappedByteBuffer; 9 import java.nio.channels.FileChannel; 10 import java.util.Scanner; 11 12 /** 13 * 内存映射 14 */ 15 public class MapDemo { 16 public static void main(String[] args) { 17 18 // 从标准输入获取数据 19 System.out.println("请输入:"); 20 Scanner sc = new Scanner(System.in); 21 String str = sc.nextLine(); 22 byte[] bytes = str.getBytes(); 23 24 String filePath = "C:\Users\use\Desktop\mapTest.txt"; 25 try { 26 FileChannel fileChannel = new RandomAccessFile(filePath, "rw").getChannel(); 27 // 获取内存映射缓冲区,并向缓冲区写入数据 28 //追加 29 MappedByteBuffer byteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, fileChannel.size(), bytes.length); 30 byteBuffer.put(bytes); 31 32 // 再次打开刚刚的文件,读取其中的内容 33 FileChannel channel = new FileInputStream(filePath).getChannel(); 34 35 ByteBuffer buffer = ByteBuffer.allocate(1024); 36 System.out.println("文件内容:"); 37 while (channel.read(buffer) != -1) { 38 // 注意先调用flip方法反转Buffer,再从Buffer读取数据 39 buffer.flip();//此行语句一定要有 40 System.out.print(new String(buffer.array())); 41 } 42 System.out.println(""); 43 44 } catch (FileNotFoundException e) { 45 e.printStackTrace(); 46 } catch (IOException e) { 47 e.printStackTrace(); 48 } 49 System.out.println(""); 50 } 51 }
5、中文乱码问题
1 package com.example.nio; 2 3 4 import java.io.File; 5 import java.io.IOException; 6 import java.io.RandomAccessFile; 7 import java.nio.ByteBuffer; 8 import java.nio.channels.FileChannel; 9 import java.util.ArrayList; 10 import java.util.Date; 11 import java.util.List; 12 13 /** 14 * 中文乱码问题 15 */ 16 public class EnCodeDemo { 17 18 public static void main(String args[]) throws Exception { 19 20 int bufSize = 1000000;//一次读取的字节长度 21 String filePath = "C:\Users\use\Desktop\test.txt"; 22 File fin = new File(filePath);//读取的文件 23 String descPath = "C:\Users\use\Desktop\test_copy.txt"; 24 File fout = new File(descPath);//写出的文件 25 Date startDate = new Date(); 26 FileChannel fcin = new RandomAccessFile(fin, "r").getChannel(); 27 ByteBuffer rBuffer = ByteBuffer.allocate(bufSize); 28 29 FileChannel fcout = new RandomAccessFile(fout, "rws").getChannel(); 30 31 readFileByLine(bufSize, fcin, rBuffer, fcout); 32 Date endDate = new Date(); 33 34 System.out.print(startDate + "|" + endDate);//测试执行时间 35 if (fcin.isOpen()) { 36 fcin.close(); 37 } 38 if (fcout.isOpen()) { 39 fcout.close(); 40 } 41 } 42 43 public static void readFileByLine(int bufSize, FileChannel fcin, 44 ByteBuffer rBuffer, FileChannel fcout) { 45 String enter = " "; 46 List<String> dataList = new ArrayList<String>();//存储读取的每行数据 47 byte[] lineByte = new byte[0]; 48 49 // String encode = "GBK"; 50 String encode = "UTF-8"; 51 try { 52 //temp:由于是按固定字节读取,在一次读取中,第一行和最后一行经常是不完整的行,因此定义此变量来存储上次的最后一行和这次的第一行的内容, 53 //并将之连接成完成的一行,否则会出现汉字被拆分成2个字节,并被提前转换成字符串而乱码的问题 54 byte[] temp = new byte[bufSize]; 55 while (fcin.read(rBuffer) != -1) {//fcin.read(rBuffer):从文件管道读取内容到缓冲区(rBuffer) 56 int rSize = rBuffer.position();//读取结束后的位置,相当于读取的长度 57 byte[] bs = new byte[rSize];//用来存放读取的内容的数组 58 rBuffer.rewind();//将position设回0,所以你可以重读Buffer中的所有数据,此处如果不设置,无法使用下面的get方法 59 rBuffer.get(bs);//相当于rBuffer.get(bs,0,bs.length()):从position初始位置开始相对读,读bs.length个byte,并写入bs[0]到bs[bs.length-1]的区域 60 rBuffer.clear(); 61 62 int startNum = 0; 63 int LF = 10;//换行符 64 int CR = 13;//回车符 65 boolean hasLF = false;//是否有换行符 66 for (int i = 0; i < rSize; i++) { 67 if (bs[i] == LF) { 68 hasLF = true; 69 int tempNum = temp.length; 70 int lineNum = i - startNum; 71 lineByte = new byte[tempNum + lineNum];//数组大小已经去掉换行符 72 73 System.arraycopy(temp, 0, lineByte, 0, tempNum);//填充了lineByte[0]~lineByte[tempNum-1] 74 temp = new byte[0]; 75 System.arraycopy(bs, startNum, lineByte, tempNum, lineNum);//填充lineByte[tempNum]~lineByte[tempNum+lineNum-1] 76 77 String line = new String(lineByte, 0, lineByte.length, encode);//一行完整的字符串(过滤了换行和回车) 78 dataList.add(line); 79 // System.out.println(line); 80 writeFileByLine(fcout, line + enter); 81 82 //过滤回车符和换行符 83 if (i + 1 < rSize && bs[i + 1] == CR) { 84 startNum = i + 2; 85 } else { 86 startNum = i + 1; 87 } 88 89 } 90 } 91 if (hasLF) { 92 temp = new byte[bs.length - startNum]; 93 System.arraycopy(bs, startNum, temp, 0, temp.length); 94 } else {//兼容单次读取的内容不足一行的情况 95 byte[] toTemp = new byte[temp.length + bs.length]; 96 System.arraycopy(temp, 0, toTemp, 0, temp.length); 97 System.arraycopy(bs, 0, toTemp, temp.length, bs.length); 98 temp = toTemp; 99 } 100 } 101 if (temp != null && temp.length > 0) {//兼容文件最后一行没有换行的情况 102 String line = new String(temp, 0, temp.length, encode); 103 dataList.add(line); 104 // System.out.println(line); 105 writeFileByLine(fcout, line + enter); 106 } 107 } catch (IOException e) { 108 e.printStackTrace(); 109 } 110 } 111 112 /** 113 * 写到文件上 114 * 115 * @param fcout 116 * @param line 117 */ 118 @SuppressWarnings("static-access") 119 public static void writeFileByLine(FileChannel fcout, String line) { 120 try { 121 fcout.write(ByteBuffer.wrap(line.getBytes("UTF-8")), fcout.size()); 122 } catch (IOException e) { 123 e.printStackTrace(); 124 } 125 } 126 }
转自:
https://blog.csdn.net/u013096088/article/details/78638245
http://ifeve.com/buffers/
https://blog.csdn.net/elf8848/article/details/39926897#
https://docs.oracle.com/javase/8/docs/api/index.html
https://blog.csdn.net/v123411739/article/details/50620289