缘起
最近公司有个需要,将一个 utf8 编码的 csv 上传到 ftp 供上游零碎应用。
然而上游零碎要求上传到 ftp 的文件是 GBK 编码的。
计划一
惯例思路,间接应用 InputStreamReader 和 OutputStreamWriter 来读写文件就行
(代码抄他人的)
import java.io.*;
// 转换文件编码:将 GBK 编码文件转化为 UTF- 8 编码的文件
public class GBKtoUtf {public static void main(String[] args) throws IOException {InputStreamReader inputStreamReader = new InputStreamReader(new FileInputStream("C:\\test\\GBK 文件.txt"),"GBK");// 这里必须是 GBK
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(new FileOutputStream("C:\\test\\UTF- 8 文件.txt"),"UTF-8");
int len = 0;
while ((len = inputStreamReader.read()) != -1){outputStreamWriter.write(len);
}
outputStreamWriter.close();
inputStreamReader.close();}
}
计划二
对于小文件,计划一比拟简单明了
对于大文件,计划一须要读写磁盘,性能非常低(磁盘往往是一个操作系统的瓶颈)
对于上传文件,必定又一次文件的读取,有没有方法在读取的过程实现转码?
剖析 FtpClient 的 API
由此可知,FtpClient 上传文件其实就是字节流的 copy 而已。
剖析 OutputStreamWriter
翻阅 OutputStreamWriter 源码发现它应用了一个 StreamEncoder 的类来实现编码转换
而 StreamEncoder 则是实用 CharsetEncoder 实现编码转换
CharsetEncoder 有两个转换 API
public final CoderResult encode(CharBuffer in, ByteBuffer out, boolean endOfInput){......}
public final ByteBuffer encode(CharBuffer in){......}
文件读取过程实现转码
既然 FtpClient 上传文件只是字节流的 Copy,那只有在它读取字节流的时候实现转码就 OK。
CharsetEncoder 提供了 API 把 CharBuffer 转换为 ByteBuffer。
所以,只须要实现一个自定义 InputStream 即可
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CoderResult;
import java.nio.charset.StandardCharsets;
public class FtpInputStream extends InputStream{private static final Logger logger = LoggerFactory.getLogger(FtpInputStream.class);
private final InputStreamReader inputStreamReader;
private final Charset originCharset;
private final Charset destCharset;
private final CharsetEncoder encoder;
private final CharBuffer charBuffer;
private final ByteBuffer byteBuffer;
private CoderResult coderResult = CoderResult.UNDERFLOW;
private boolean end = false;
public FtpInputStream(InputStream inputStream, Charset originCharset, Charset destCharset) {this.inputStreamReader = new InputStreamReader(inputStream, originCharset);
this.originCharset = originCharset;
this.destCharset = destCharset;
this.encoder = destCharset.newEncoder();
this.charBuffer = CharBuffer.allocate(1000);
this.byteBuffer = ByteBuffer.allocate((int) (1000 * encoder.averageBytesPerChar()));
this.byteBuffer.flip();}
@Override
public void close() throws IOException {inputStreamReader.close();
}
@Override
public int read() throws IOException {if (byteBuffer.hasRemaining()) {
// byteBuffer 还有数据就间接读
return byteBuffer.get();} else {
// byteBuffer 曾经被读取完了,清空待用
byteBuffer.clear();}
if (end) {return -1;}
//byteBuffer 没有数据了,charBuffer 存在两种状况:曾经被耗费完了、未被耗费完
// 曾经被耗费完了,须要从新从 inputStreamReader 读取
// 未被耗费完,不需从 inputStreamReader 读取,间接编码
if (coderResult.isUnderflow()) {charBuffer.clear();
int r = inputStreamReader.read(charBuffer);
if(r == -1) {
// 读取完结
end = true;
charBuffer.flip();}else if (r == 0) {
// 不应该的状况
throw new IOException("read 0 chart");
} else {
// 翻转被编码器读取
charBuffer.flip();}
}
// 编码为字节
coderResult = encoder.encode(charBuffer, byteBuffer, end);
if (coderResult.isError()) {throw new IOException(coderResult.toString());
}
if (end) {encoder.flush(byteBuffer);
}
// 翻转,筹备读取
byteBuffer.flip();
if (byteBuffer.hasRemaining()) {return byteBuffer.get();
} else {
// 不应该呈现到清空
throw new IOException("encode result is null");
}
}
}