1.前言
这一篇随笔是写 NIO 关于文件输入输出的总结
/* 总结: 1.io操作包括 socket io ,file io ; 2.在nio模型,file io使用fileChannel 管道 ,socket io 使用socketChannel管道, 3.在file io可以使用transferTo 或 transferFrom 实现管道向管道的的数据传输,但是别人说有可能传输数据不完成,不建议这样做; 4.GBK 为2 byte,如果是utf-8,最大长度是4字节 ,是可变的,需要循环判断获取字节长度后设置limit值才能获取完成的utf-8编码,否则会乱码, 5. buffer.compact();将所有未读的数据拷贝到Buffer起始处。然后将position设到最后一个未读元素正后面。limit属性依然像clear()方法一样 ,设置成capacity。现在Buffer准备好写数据了,但是不会覆盖未读的数据。也就是说把已经写入缓冲区但是还没有读的数据左移到开头, 然后将position设为该数据默认然后继续做写操作,不会覆盖这些数据 6. buffer.clear();调用的是clear()方法,position将被设回0,limit被设置成 capacity的值。换句话说,Buffer 被清空了。Buffer中的数据并未清除, 只是这些标记告诉我们可以从哪里开始往Buffer里写数据。如果Buffer中有一些未读的数据,调用clear()方法,数据将“被遗忘”, 意味着不再有任何标记会告诉你哪些数据被读过,哪些还没有。 7. buffer.flip();重置position,limit,capacity, 也就是limit= position,position=0,capacity=设好的最大值, 8. buffer.position();缓冲区的有定位位置 ,一般是position值小于limit值,否则无数据 9. buffer.limit(); 缓冲区的有限制位置 ,一般是position值小于limit值,否则无数据 10. 不论是读取还是写入,都是position 于limit之间的位置操作 11. buffer.capacity();缓冲区的最大容量 ,由ByteBuffer.allocate(【int】) 设置 12. buffer.hasRemaining();是否还有有效数据 ,即position值小于limit值 13.buffer缓冲区内部是字节流,使用buffer.array()从 缓冲区获取position 于limit之间的底层字节数组,然后使用 new String将字节数组转字符串 */
2.操作
(1)把字符串写入文件[使用 buffer.put]
/** * 把字符串写入文件[使用 buffer.put] */ @Test public void w1() { try { //================================= // //new文件对象 // File file = new File("C:/Users/cen/Desktop/ww/", "write.txt"); // //字节输出流,意思是指JVM的内容输出到文件对象里面 // //如果文件存在 覆盖 // FileOutputStream fos = new FileOutputStream(file); // //上面几句等同于下面一句 FileOutputStream fos = new FileOutputStream("C:/Users/cen/Desktop/ww/write.txt"); //============================================== //true表示追加,如果文件存在 向里面继续添加内容,不加默认是覆盖 // FileOutputStream fos=new FileOutputStream(file,true); //获取通道,该通道允许写操作【根据字节流是输入还是输出决定是读通道还是写通道】 FileChannel fc = fos.getChannel(); // // System.out.println("通道数量====" + fc.size()); //打印的结果是0 // //allocate是使用JVM的的内存的,适合小文件;allocateDirect则是使用系统的内存的,适合大文件 //设置字节缓的大小 ByteBuffer buffer = ByteBuffer.allocate(1024); // ByteBuffer.allocateDirect() //控制台写入字符串 // Scanner sca = new Scanner(System.in); // String str = sca.next(); // 转行符算是一个字符 String str = "sa34523ui2345672 " + "kkkkuuuushfjsdhjkfhs222333444555 " + "撒大噶和 " + "稍等哈肯定会介绍的几款圣诞卡十大科技"; //将字符串转成字节后,放入字节缓冲 buffer.put(str.getBytes(StandardCharsets.UTF_8)); // 此行语句一定要有,每次执行了这一句才可以做io操作 buffer.flip(); //通道对字节缓冲区里的内容写入文件对象里面 fc.write(buffer); System.out.println("写入成功"); //清空缓冲块 buffer.clear(); //关闭管道 fc.close(); //关闭字节输出流 fos.close(); } catch (IOException e) { e.printStackTrace(); } }
(2)把字符串写入文件[使用 ByteBuffer.wrap ,不设置缓冲区大小]
/** * 把字符串写入文件[使用 ByteBuffer.wrap ,不设置缓冲区大小] * * @throws Exception */ @Test public void w2() throws Exception { // 构造一个传统的文件输出流 FileOutputStream out = new FileOutputStream("C:/Users/cen/Desktop/ww/write.txt"); // 通过文件输出流获取到对应的FileChannel,以NIO的方式来写文件 FileChannel channel = out.getChannel(); // 将数据写入到Buffer中 ByteBuffer buffer = ByteBuffer.wrap("hello world".getBytes()); // 通过FileChannel管道将Buffer中的数据写到输出流中去,持久化到磁盘中去 channel.write(buffer); channel.close(); out.close(); }
(3)从文件中一次性读取字符串
/** * 从文件中一次性读取字符串 */ public static void main(String[] args) throws Exception { //============================================== //new文件对象 File file = new File("C:/Users/cen/Desktop/ww/write2.txt"); //字节输入流 //意思是文件内容向JVM输入 FileInputStream fis = new FileInputStream(file); // //上面几句等同于下面一句 // FileInputStream fis = new FileInputStream("C:/Users/cen/Desktop/ww/write.txt"); //============================================== // //开启管道 FileChannel fc = fis.getChannel(); //设置字节缓冲区的大小,这里设为文件对象的字节长度 // ByteBuffer buffer = ByteBuffer.allocate((int) file.length()); ByteBuffer buffer = ByteBuffer.allocate(1024); //管道将文件对象的内容读出并放入缓冲区里面,字符上限为上面缓冲区设置的最大值 //read()方法返回的是个抽象的整数,意为字符的长度/个数,当为空时返回-1 int nread = fc.read(buffer); // System.out.println(buffer); System.out.println("抽象整数==" + nread); // buffer.array()是缓冲区获取底层byte[]数组, // 然后使用 new String 将字节转成字符串 System.out.println(new String(buffer.array())); //清除缓冲区 buffer.clear(); //关闭管道 fc.close(); //关闭字节输入流 fis.close(); }
(4)多次读取文件数据--解决utf-8中文乱码问题
/** * 多次读取文件数据--解决utf-8中文乱码问题 */ @Test public void readutf8Data() throws Exception { //============================================== //new文件对象 File file = new File("C:/Users/cen/Desktop/ww/write.txt"); //字节输入流 //意思是文件内容向JVM输入 FileInputStream fis = new FileInputStream(file); //============================================== // //开启管道 FileChannel fc = fis.getChannel(); //如果设置太小的,会乱码【解决方式不靠谱】 //如果是utf-8,最大长度是4字节 ,GBK 为2 byte //因此这里设置最小是4 ByteBuffer buffer = ByteBuffer.allocate(6); //管道将文件对象的内容读出并放入缓冲区里面,字符上限为上面缓冲区设置的最大值 //read()方法返回的是个抽象的整数,意为字符的长度/个数,当为空时返回-1 int mread; //此时管道读取数据放入缓冲,缓冲的定位position 不再是0 ,需要将limit设为position,而position需要恢复到0 , //形成 【 limit == 当前的position值【也等于mread】,position == 0 , cap 不变】 while ((mread = fc.read(buffer)) != -1) { byte b; //计算出此时中文utf-8的编码长度后放入这里 int idx; //out可以bai改成任何不与保留关键字相同的字符,其中du标签out代表了这个for循环结构zhi体。理论上,标签可以标记任何结构体 //break out ;代表语句执行跳出整个for循环结构 out: for (idx = buffer.position() - 1; idx >= 0; idx--) { b = buffer.get(idx); if ((b & 0xff) >> 7 == 0) { // 0xxxxxxx break; } if ((b & 0xff & 0xc0) == 0xc0) { // 11xxxxxx,110xxxxx、1110xxxx、11110xxx idx -= 1; break; } if ((b & 0xff & 0x80) == 0x80) { for (int i = 1; i < 4; i++) { b = buffer.get(idx - i); if ((b & 0xff & 0xc0) == 0xc0) { if ((b & 0xff) >> (5 + 1 - i) == 0xf >> (3 - i)) { //break out ;代表语句执行跳出整个for循环结构 break out; } else { idx = idx - 1 - i; //break out ;代表语句执行跳出整个for循环结构 break out; } } } } } //缓冲区定位参数移位 buffer.flip(); //获取限制数,备份 int limit = buffer.limit(); //设限制数为上面计算出的utf-8字符的长度 buffer.limit(idx + 1); // 阻止读取跨界数据 // System.out.println(Charset.forName("UTF-8").decode(buffer).toString()); // 恢复limit buffer.limit(limit); //compact()方法将所有未读的数据拷贝到Buffer起始处。然后将position设到最后一个未读元素正后面。 // limit属性依然像clear()方法一样,设置成capacity。现在Buffer准备好写数据了,但是不会覆盖未读的数据。 //也就是说把已经写入缓冲区但是还没有读的数据左移到开头,然后将position设为该数据默认 //然后继续做写操作,不会覆盖这些数据 buffer.compact(); } //清除缓冲区 buffer.clear(); //关闭管道 fc.close(); //关闭字节输入流 fis.close(); }
(5) 文件复制 【如果已经存在则覆盖,因此需要提前判断是否存在】
/** * 文件复制 【如果已经存在则覆盖,因此需要提前判断是否存在】 */ @Test public void copyFile() throws Exception { //分别创建流 //字节输入流,用于从文件读取数据,即原地址文件【被拷贝的文件】 FileInputStream fis = new FileInputStream("C:/Users/cen/Desktop/ww/write.txt"); //字节输出流,用于从将数据输出到文件里,即目标地址文件【拷贝文件】 FileOutputStream fos = new FileOutputStream("C:/Users/cen/Desktop/ww/write_copy.txt"); // //分别开启通道 FileChannel sourceFC = fis.getChannel(); FileChannel destFC = fos.getChannel(); // //复制 【一个主动一个被动】 //从哪里转移到这里 【参数 0 ,其实是管道的数量,等同于 destFC.size()】 destFC.transferFrom(sourceFC, 0, sourceFC.size()); //从这里转移到哪里 // sourceFC.transferTo(destFC,0,sourceFC.size()); // //关闭通道 sourceFC.close(); destFC.close(); // // 关闭字节输入流 fis.close(); fos.close(); }