笔者最近在用多线程来计算中文文本的标点符号数目,遇到了以下问题:
- 在Windows下,文本中汉字通常采用Unicode编码,这就导致需要随机
(RandomAccessFile)
读取文本时,产生乱码现象。 - 多线程计算前(假设有2个线程),需要将文本内容尽量等分成2份,并输出到新的文件中,再进行计算。
总体思路:
- 规定一次读取的字节数,再在存储和输出时转化成GBK编码
- 由于
RandomAccessFile
可以随机定位读取起始点,当规定了一次读取的字节数,也就规定了读取结束点。 - 按行读取,每一行的字节有对应的数组保存,转化成GBK后,写入输出文本。
- 由于
- 引入
java.nio
,在读取文件和转化编码时方便很多,笔者认为java.io
也可以实现。- 关于NIO的详细教程可以参考:NIO系列教程
- 本文引入
java.nio.ByteBuffer
,java.nio.channels.FileChannel
,前者无需解释,后者为通道,相当于流。
具体代码实现如下:
package Yue.IO;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
/**
* 将文本内容尽量分成n份,使n个线程处理对应的文本
*/
public class SplitFile {
int fileNum; //分离的文件数
File fileIn = new File("E:\白夜行.txt");
int bufSize;
SplitFile(int threadsNum) {
fileNum = threadsNum;
bufSize = (int) (fileIn.length() / fileNum); //一次读取的字节数
}
FileChannel fileChaIn, fileChaOut;
ByteBuffer rBuffer, wBuffer;
/*设置缓冲区,读文件时,最后一行往往不完整,需要将存在断点的那一行保存,与下一次读文本时的第一行合并*/
byte[] temp = new byte[0];
/**
* 按行具体读出每一个线程所要处理的文本内容
*
* @param NO Thread-NO
*/
public void readByLine(int NO) {
String enter = "
";
byte[] lineByte; //保存每一行读取内容
/*确认读取范围*/
try {
RandomAccessFile raf = new RandomAccessFile(fileIn, "r");
raf.seek(NO * bufSize); //根据分离文本进程定位
fileChaIn = raf.getChannel();
rBuffer = ByteBuffer.allocate(bufSize);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (fileChaIn.read(rBuffer) != -1) {
/*生成输出文件Part-No.txt*/
try {
fileChaOut = new RandomAccessFile("E:\Part-" + NO + ".txt", "rws").getChannel();
wBuffer = ByteBuffer.allocateDirect(bufSize);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
/*根据一次读取,确定本次输出的字节长度*/
int rSize = rBuffer.position();
byte[] bs = new byte[rSize];
rBuffer.rewind();
rBuffer.get(bs);
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]; //数组大小已经去掉换行符
/*把上一次读取保存在缓冲区的内容和本次读取的这一行的内容合并,保存到lineByte[]中*/
System.arraycopy(temp, 0, lineByte, 0, tempNum);
temp = new byte[0];
System.arraycopy(bs, startNum, lineByte, tempNum, lineNum);
/*把该行内容转换成String类型,写入输出文件中*/
String line = new String(lineByte, 0, lineByte.length, "GBK");
writeByLine(line + enter);
/*过滤回车和换行*/
if (i == rSize - 1 && 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[bs.length + temp.length];
System.arraycopy(temp, 0, toTemp, 0, temp.length);
System.arraycopy(bs, 0, toTemp, temp.length, bs.length);
temp = toTemp;
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fileChaIn.close();
fileChaOut.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 写入输出文件
*
* @param line 已转换成String类型的当前行文本内容
*/
public void writeByLine(String line) {
try {
fileChaOut.write(wBuffer.wrap(line.getBytes("GBK")), fileChaOut.size());
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}