Java自学者论坛

 找回密码
 立即注册

手机号码,快捷登录

恭喜Java自学者论坛(https://www.javazxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,会员资料板块,购买链接:点击进入购买VIP会员

JAVA高级面试进阶训练营视频教程

Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程Go语言视频零基础入门到精通Java架构师3期(课件+源码)
Java开发全终端实战租房项目视频教程SpringBoot2.X入门到高级使用教程大数据培训第六期全套视频教程深度学习(CNN RNN GAN)算法原理Java亿级流量电商系统视频教程
互联网架构师视频教程年薪50万Spark2.0从入门到精通年薪50万!人工智能学习路线教程年薪50万大数据入门到精通学习路线年薪50万机器学习入门到精通教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程MySQL入门到精通教程
查看: 513|回复: 0

JAVA之NIO按行读写大文件,完美解决中文乱码问题

[复制链接]
  • TA的每日心情
    奋斗
    昨天 22:25
  • 签到天数: 790 天

    [LV.10]以坛为家III

    2049

    主题

    2107

    帖子

    72万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    722766
    发表于 2021-4-26 06:24:17 | 显示全部楼层 |阅读模式

    前言

    最近在开发的时候,接到了一个开发任务,要将百万行级别的txt数据插入到数据库中,由于内存方面的原因,因此不可能一次读取所有内容,后来在网上找到了解决方法,可以使用NIO技术来处理,于是找到了这篇文章http://www.sharejs.com/codes/java/1334,后来在试验过程中发现了一点小bug,由于是按字节读取,汉字又是2个字节,因此会出现汉字读取“一半”导致乱码的情况,于是花了几天时间将这个问题解决了。


    例子

    假设我们一次读取的字节是从下图的start到end,因为结尾是汉字,所以有几率出现上述的情况。

    解决方法如下:将第9行这半行(第9行阴影的部分)跟上一次读取留下来的半行(第9行没阴影的部分)按顺序存放在字节数组,然后转成字符串;中间第10行到第17行正常转换成字符串;第18行这半行(第18行阴影的部分)留着跟下一次读取的第1行(第18行没阴影的部分)连接成一行,因为是先拼接成字节数组再转字符串,因此不会出现乱码的情况。



    代码

    1. package com.chillax.imp;  
    2.   
    3. import java.io.File;  
    4. import java.io.IOException;  
    5. import java.io.RandomAccessFile;  
    6. import java.nio.ByteBuffer;  
    7. import java.nio.channels.FileChannel;  
    8. import java.util.ArrayList;  
    9. import java.util.Date;  
    10. import java.util.List;  
    11.   
    12. /** 
    13.  * NIO读取百万级别文件 
    14.  * @author Chillax 
    15.  * 
    16.  */  
    17. public class NIO {  
    18.   
    19.     public static void main(String args[]) throws Exception {  
    20.   
    21.         int bufSize = 1000000;//一次读取的字节长度  
    22.         File fin = new File("D:\\test\\20160622_627975.txt");//读取的文件  
    23.         File fout = new File("D:\\test\\20160622_627975_1.txt");//写出的文件  
    24.         Date startDate = new Date();  
    25.         FileChannel fcin = new RandomAccessFile(fin, "r").getChannel();  
    26.         ByteBuffer rBuffer = ByteBuffer.allocate(bufSize);  
    27.   
    28.         FileChannel fcout = new RandomAccessFile(fout, "rws").getChannel();  
    29.         ByteBuffer wBuffer = ByteBuffer.allocateDirect(bufSize);  
    30.   
    31.         readFileByLine(bufSize, fcin, rBuffer, fcout, wBuffer);  
    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, ByteBuffer wBuffer) {  
    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[0];  
    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 == 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, wBuffer, 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, wBuffer, line + enter);  
    106.             }  
    107.         } catch (IOException e) {  
    108.             e.printStackTrace();  
    109.         }   
    110.     }  
    111.   
    112.     /** 
    113.      * 写到文件上 
    114.      * @param fcout 
    115.      * @param wBuffer 
    116.      * @param line 
    117.      */  
    118.     @SuppressWarnings("static-access")  
    119.     public static void writeFileByLine(FileChannel fcout, ByteBuffer wBuffer,  
    120.             String line) {  
    121.         try {  
    122.             fcout.write(wBuffer.wrap(line.getBytes("UTF-8")), fcout.size());  
    123.         } catch (IOException e) {  
    124.             e.printStackTrace();  
    125.         }  
    126.     }  
    127. }  

    —————END—————


    哎...今天够累的,签到来了1...
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|小黑屋|Java自学者论坛 ( 声明:本站文章及资料整理自互联网,用于Java自学者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-9-11 11:32 , Processed in 0.196230 second(s), 29 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表