Java NIO —— Buffer(缓冲区)

Wesley13
• 阅读 782

Buffer是一个抽象类,位于java.nio包中,主要用作缓冲区。注意:Buffer是非线程安全类。

缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存。这块内存被包装成NIO Buffer对象,并提供了一组方法,用来方便的访问该块内存。

NIO 有以下几种Buffer类型:

  • ByteBuffer

  • MappedByteBuffer

  • CharBuffer

  • DoubleBuffer

  • FloatBuffer

  • IntBuffer

  • LongBuffer

  • ShortBuffer

    Java NIO —— Buffer(缓冲区)

一、属性
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、读写操作

Java NIO —— Buffer(缓冲区) Java NIO —— Buffer(缓冲区)

  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、数据转移操作(复制操作)

Java NIO —— Buffer(缓冲区) Java NIO —— Buffer(缓冲区)

 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、流读取比较

Java NIO —— Buffer(缓冲区) Java NIO —— Buffer(缓冲区)

  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("\n===>文件大小:%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("\n===>文件大小:%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("\n===>文件大小:%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("\n===>文件大小:%s 字节", size));
224         System.out.println(String.format("===>读取并打印文件耗时:%s毫秒", end - start));
225     }
226 
227 }

流读取比较

4、内存映射

Java NIO —— Buffer(缓冲区) Java NIO —— Buffer(缓冲区)

 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、中文乱码问题

Java NIO —— Buffer(缓冲区) Java NIO —— Buffer(缓冲区)

  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 = "\n";
 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

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Karen110 Karen110
2年前
一篇文章带你了解JavaScript日期
日期对象允许您使用日期(年、月、日、小时、分钟、秒和毫秒)。一、JavaScript的日期格式一个JavaScript日期可以写为一个字符串:ThuFeb02201909:59:51GMT0800(中国标准时间)或者是一个数字:1486000791164写数字的日期,指定的毫秒数自1970年1月1日00:00:00到现在。1\.显示日期使用
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Stella981 Stella981
2年前
Docker 部署SpringBoot项目不香吗?
  公众号改版后文章乱序推荐,希望你可以点击上方“Java进阶架构师”,点击右上角,将我们设为★“星标”!这样才不会错过每日进阶架构文章呀。  !(http://dingyue.ws.126.net/2020/0920/b00fbfc7j00qgy5xy002kd200qo00hsg00it00cj.jpg)  2
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这