JAVA NIO按行读写大文件出现中文乱码问题的解决

 更新时间:2025年02月18日 08:58:19   作者:程序员囧辉  
这篇文章主要为大家详细介绍了JAVA在使用NIO进行按行读写大文件时出现中文乱码问题是如何解决的,文中的示例代码简洁易懂,有需要的小伙伴可以参考一下

前言

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

例子

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

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

代码

package com.joonwhee.imp;
 
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
 
/**
 * @author joonwhee
 * @date 2019/3/22
 */
public class NIOTest {
    
    public static void main(String args[]) throws Exception {
 
        int bufSize = 1000000;//一次读取的字节长度
        File fin = new File("D:\\test\\20160622_627975.txt");//读取的文件
        File fout = new File("D:\\test\\20160622_627975_1.txt");//写出的文件
        Date startDate = new Date();
        FileChannel fcin = new RandomAccessFile(fin, "r").getChannel();
        ByteBuffer rBuffer = ByteBuffer.allocate(bufSize);
 
        FileChannel fcout = new RandomAccessFile(fout, "rws").getChannel();
        ByteBuffer wBuffer = ByteBuffer.allocateDirect(bufSize);
 
        readFileByLine(bufSize, fcin, rBuffer, fcout, wBuffer);
        Date endDate = new Date();
 
        System.out.print(startDate + "|" + endDate);//测试执行时间
        if (fcin.isOpen()) {
            fcin.close();
        }
        if (fcout.isOpen()) {
            fcout.close();
        }
    }
 
    public static void readFileByLine(int bufSize, FileChannel fcin,
                                      ByteBuffer rBuffer, FileChannel fcout, ByteBuffer wBuffer) {
        String enter = "\n";
        List<String> dataList = new ArrayList<String>();//存储读取的每行数据
        byte[] lineByte = new byte[0];
 
        String encode = "GBK";
//		String encode = "UTF-8";
        try {
            //temp:由于是按固定字节读取,在一次读取中,第一行和最后一行经常是不完整的行,因此定义此变量来存储上次的最后一行和这次的第一行的内容,
            //并将之连接成完成的一行,否则会出现汉字被拆分成2个字节,并被提前转换成字符串而乱码的问题
            byte[] temp = new byte[0];
            while (fcin.read(rBuffer) != -1) {//fcin.read(rBuffer):从文件管道读取内容到缓冲区(rBuffer)
                int rSize = rBuffer.position();//读取结束后的位置,相当于读取的长度
                byte[] bs = new byte[rSize];//用来存放读取的内容的数组
                rBuffer.rewind();//将position设回0,所以你可以重读Buffer中的所有数据,此处如果不设置,无法使用下面的get方法
                rBuffer.get(bs);//相当于rBuffer.get(bs,0,bs.length()):从position初始位置开始相对读,读bs.length个byte,并写入bs[0]到bs[bs.length-1]的区域
                rBuffer.clear();
 
                int startNum = 0;
                int LF = 10;//换行符
                int CR = 13;//回车符
                boolean hasLF = false;//是否有换行符
                for (int i = 0; i < rSize; i++) {
                    if (bs[i] == LF) {
                        hasLF = true;
                        int tempNum = temp.length;
                        int lineNum = i - startNum;
                        lineByte = new byte[tempNum + lineNum];//数组大小已经去掉换行符
 
                        System.arraycopy(temp, 0, lineByte, 0, tempNum);//填充了lineByte[0]~lineByte[tempNum-1]
                        temp = new byte[0];
                        System.arraycopy(bs, startNum, lineByte, tempNum, lineNum);//填充lineByte[tempNum]~lineByte[tempNum+lineNum-1]
 
                        String line = new String(lineByte, 0, lineByte.length, encode);//一行完整的字符串(过滤了换行和回车)
                        dataList.add(line);
//						System.out.println(line);
                        writeFileByLine(fcout, wBuffer, line + enter);
 
                        //过滤回车符和换行符
                        if (i + 1 < rSize && bs[i + 1] == CR) {
                            startNum = i + 2;
                        } else {
                            startNum = i + 1;
                        }
 
                    }
                }
                if (hasLF) {
                    temp = new byte[bs.length - startNum];
                    System.arraycopy(bs, startNum, temp, 0, temp.length);
                } else {//兼容单次读取的内容不足一行的情况
                    byte[] toTemp = new byte[temp.length + bs.length];
                    System.arraycopy(temp, 0, toTemp, 0, temp.length);
                    System.arraycopy(bs, 0, toTemp, temp.length, bs.length);
                    temp = toTemp;
                }
            }
            if (temp != null && temp.length > 0) {//兼容文件最后一行没有换行的情况
                String line = new String(temp, 0, temp.length, encode);
                dataList.add(line);
//				System.out.println(line);
                writeFileByLine(fcout, wBuffer, line + enter);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
 
    /**
     * 写到文件上
     *
     * @param fcout
     * @param wBuffer
     * @param line
     */
    @SuppressWarnings("static-access")
    public static void writeFileByLine(FileChannel fcout, ByteBuffer wBuffer,
                                       String line) {
        try {
            fcout.write(wBuffer.wrap(line.getBytes("UTF-8")), fcout.size());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

到此这篇关于JAVA NIO按行读写大文件出现中文乱码问题的解决的文章就介绍到这了,更多相关JAVA NIO按行读写大文件内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java中Lambda表达式的使用详细教程

    Java中Lambda表达式的使用详细教程

    这篇文章将通过示例为大家详细展示Java中Lambda表达式的用法,同时也会介绍Lambda的相关知识,感兴趣的小伙伴可以跟随小编一起学习一下
    2022-05-05
  • 详解Spring Aop实例之xml配置

    详解Spring Aop实例之xml配置

    本篇文章主要介绍了详解Spring Aop实例之xml配置,使用xml可以对aop进行集中配置,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-04-04
  • Java8 CompletableFuture runAsync学习总结submit() execute()等

    Java8 CompletableFuture runAsync学习总结submit() execute()等

    这篇文章主要介绍了Java8 CompletableFuture runAsync学习总结submit() execute()等,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-10-10
  • Spring Boot管理用户数据的操作步骤

    Spring Boot管理用户数据的操作步骤

    SpringBoot结合Thymeleaf模板引擎,可以快速搭建Web应用,介绍了使用SpringBoot处理JSON数据的基本过程,包括创建实体类、视图页面和控制器,通过这些步骤,即可完成基于SpringBoot和Thymeleaf的简单Web开发,感兴趣的朋友跟随小编一起看看吧
    2024-09-09
  • Eclipse如何导入Maven项目详解(新手初学)

    Eclipse如何导入Maven项目详解(新手初学)

    这篇文章主要介绍了Eclipse如何导入Maven项目详解(新手初学),小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-12-12
  • 使用spring security明文密码校验时报错-BadCredentialsException: Bad credentials的问题

    使用spring security明文密码校验时报错-BadCredentialsException:&nbs

    小编遇到这样一个问题在学习spring security时使用明文密码进行登录校验时报错"org.springframework.security.authentication.BadCredentialsException: Bad credentials,今天给大家分享问题原因及解决方案,感兴趣的朋友一起看看吧
    2023-10-10
  • mybatis的mapper.xml中resultMap标签的使用详解

    mybatis的mapper.xml中resultMap标签的使用详解

    这篇文章主要介绍了mybatis的mapper.xml中resultMap标签的使用详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-06-06
  • 解决springboot读取application.properties中文乱码问题

    解决springboot读取application.properties中文乱码问题

    初用properties,读取java properties文件的时候如果value是中文,会出现乱码的问题,所以本文小编将给大家介绍如何解决springboot读取application.properties中文乱码问题,需要的朋友可以参考下
    2023-11-11
  • 详解Spring Boot对 Apache Pulsar的支持

    详解Spring Boot对 Apache Pulsar的支持

    Spring Boot通过提供spring-pulsar和spring-pulsar-reactive自动配置支持Apache Pulsar,类路径中这些依赖存在时,Spring Boot自动配置命令式和反应式Pulsar组件,PulsarClient自动注册,默认连接本地Pulsar实例,感兴趣的朋友一起看看吧
    2024-11-11
  • 深入剖析Spring如何解决循环依赖

    深入剖析Spring如何解决循环依赖

    循环依赖(Circular Dependency)是指两个或多个Bean相互依赖,形成一个闭环的情况,本文将和大家深入探讨一下Spring如何解决循环依赖,需要的可以参考下
    2025-04-04

最新评论