Java
Java IO

Java IO 24 - InputStreamReader和OutputStreamWriter详解

简介:流是一组有顺序的,有起点和终点的字节集合。是对设备文件间数据传输的总称和抽象。

1. InputStreamReader和OutputStreamWriter简介

InputStreamReader和OutputStreamWriter是字节流通向字符流的桥梁,它使用指定的charset读写字节并将其解码为字符。

InputStreamReader的作用是将字节输入流转换成字符输入流,它继承于Reader。OutputStreamWriter的作用是将字节输出流转换成字符输出流,它继承于Writer。

2. InputStreamReader源码解析

下面是InputStreamReader的源码,基于JDK 1.7.0_07:

  • package java.io;
  • import java.nio.charset.Charset;
  • import java.nio.charset.CharsetDecoder;
  • import sun.nio.cs.StreamDecoder;
  • // 将字节输入流转换成字符输入流
  • public class InputStreamReader extends Reader {
  • private final StreamDecoder sd;
  • // 根据in创建InputStreamReader,使用默认的编码
  • public InputStreamReader(InputStream in) {
  • super(in);
  • try {
  • sd = StreamDecoder.forInputStreamReader(in, this, (String)null); // ## check lock object
  • } catch (UnsupportedEncodingException e) {
  • // The default encoding should always be available
  • throw new Error(e);
  • }
  • }
  • // 根据in创建InputStreamReader,使用编码charsetName(编码名)
  • public InputStreamReader(InputStream in, String charsetName)
  • throws UnsupportedEncodingException
  • {
  • super(in);
  • if (charsetName == null)
  • throw new NullPointerException("charsetName");
  • sd = StreamDecoder.forInputStreamReader(in, this, charsetName);
  • }
  • // 根据in创建InputStreamReader,使用编码cs
  • public InputStreamReader(InputStream in, Charset cs) {
  • super(in);
  • if (cs == null)
  • throw new NullPointerException("charset");
  • sd = StreamDecoder.forInputStreamReader(in, this, cs);
  • }
  • // 根据in创建InputStreamReader,使用解码器dec
  • public InputStreamReader(InputStream in, CharsetDecoder dec) {
  • super(in);
  • if (dec == null)
  • throw new NullPointerException("charset decoder");
  • sd = StreamDecoder.forInputStreamReader(in, this, dec);
  • }
  • // 获取解码器
  • public String getEncoding() {
  • return sd.getEncoding();
  • }
  • // 读取并返回一个字符
  • public int read() throws IOException {
  • return sd.read();
  • }
  • // 将InputStreamReader中的数据写入cbuf中,从cbuf的offset位置开始写入,写入长度是length
  • public int read(char cbuf[], int offset, int length) throws IOException {
  • return sd.read(cbuf, offset, length);
  • }
  • // 能否从InputStreamReader中读取数据
  • public boolean ready() throws IOException {
  • return sd.ready();
  • }
  • // 关闭InputStreamReader
  • public void close() throws IOException {
  • sd.close();
  • }
  • }

从上面InputStreamReader的源码中可以看出,它的主要方法都调用了StreamDecoder的相关方法,下面将简要分析StreamDecoder类。

3. StreamDecoder源码解析

StreamDecoder类的源码是在sun.nio.cs包下的,这个包是在JDK中默认没有提供,下面是在OpenJDK中找到的源码:

  • package sun.nio.cs;
  • import java.io.*;
  • import java.nio.*;
  • import java.nio.channels.*;
  • import java.nio.charset.*;
  • public class StreamDecoder extends Reader {
  • // bytebuffer最小大小
  • private static final int MIN_BYTE_BUFFER_SIZE = 32;
  • // 默认的bytebuffer大小
  • private static final int DEFAULT_BYTE_BUFFER_SIZE = 8192;
  • private volatile boolean isOpen = true;
  • private void ensureOpen() throws IOException {
  • if (!isOpen)
  • throw new IOException("Stream closed");
  • }
  • // 当只换回一个字符的时候,另一个字符会被存放在这个属性中,以便后续返回
  • private boolean haveLeftoverChar = false;
  • private char leftoverChar;
  • public static StreamDecoder forInputStreamReader(InputStream in, Object lock, String charsetName)
  • throws UnsupportedEncodingException {
  • String csn = charsetName;
  • if (csn == null)
  • // 如果csn为空,取默认的编码
  • csn = Charset.defaultCharset().name();
  • try {
  • if (Charset.isSupported(csn))
  • return new StreamDecoder(in, lock, Charset.forName(csn));
  • } catch (IllegalCharsetNameException x) {
  • }
  • throw new UnsupportedEncodingException(csn);
  • }
  • public static StreamDecoder forInputStreamReader(InputStream in,
  • Object lock,
  • Charset cs) {
  • return new StreamDecoder(in, lock, cs);
  • }
  • public static StreamDecoder forInputStreamReader(InputStream in,
  • Object lock,
  • CharsetDecoder dec) {
  • return new StreamDecoder(in, lock, dec);
  • }
  • public static StreamDecoder forDecoder(ReadableByteChannel ch,
  • CharsetDecoder dec,
  • int minBufferCap) {
  • return new StreamDecoder(ch, dec, minBufferCap);
  • }
  • public String getEncoding() {
  • if (isOpen())
  • return encodingName();
  • return null;
  • }
  • public int read() throws IOException {
  • return read0();
  • }
  • private int read0() throws IOException {
  • synchronized (lock) {
  • // 如果有遗留字符,返回遗留字符
  • if (haveLeftoverChar) {
  • haveLeftoverChar = false;
  • return leftoverChar;
  • }
  • // 否则尝试读取两个字符
  • char cb[] = new char[2];
  • int n = read(cb, 0, 2);
  • switch (n) {
  • case -1:
  • // 表明没有读到
  • return -1;
  • case 2:
  • // 读到2个,将第一个字符返回,将第二个字符存入leftoverChar
  • leftoverChar = cb[1];
  • haveLeftoverChar = true;
  • // FALL THROUGH
  • case 1:
  • // 读到1个,直接返回
  • return cb[0];
  • default:
  • assert false : n;
  • return -1;
  • }
  • }
  • }
  • public int read(char cbuf[], int offset, int length) throws IOException {
  • int off = offset;
  • int len = length;
  • synchronized (lock) {
  • // 保证流是打开的
  • ensureOpen();
  • if ((off < 0) || (off > cbuf.length) || (len < 0) ||
  • ((off + len) > cbuf.length) || ((off + len) < 0)) {
  • throw new IndexOutOfBoundsException();
  • }
  • if (len == 0)
  • return 0;
  • int n = 0;
  • if (haveLeftoverChar) {
  • // 将上次遗留的一个字符存入
  • cbuf[off] = leftoverChar;
  • off++;
  • len--;
  • haveLeftoverChar = false;
  • n = 1;
  • if ((len == 0) || !implReady())
  • // Return now if this is all we can produce w/o blocking
  • return n;
  • }
  • if (len == 1) {
  • // 读取第二个字符;能执行到这里,说明传入的length为1或2
  • int c = read0();
  • if (c == -1)
  • return (n == 0) ? -1 : n;
  • cbuf[off] = (char) c;
  • return n + 1;
  • }
  • // 否则还需要继续读取
  • return n + implRead(cbuf, off, off + len);
  • }
  • }
  • public boolean ready() throws IOException {
  • synchronized (lock) {
  • ensureOpen();
  • return haveLeftoverChar || implReady();
  • }
  • }
  • public void close() throws IOException {
  • synchronized (lock) {
  • if (!isOpen)
  • return;
  • implClose();
  • isOpen = false;
  • }
  • }
  • private boolean isOpen() {
  • return isOpen;
  • }
  • private static volatile boolean channelsAvailable = true;
  • private static FileChannel getChannel(FileInputStream in) {
  • if (!channelsAvailable)
  • return null;
  • try {
  • return in.getChannel();
  • } catch (UnsatisfiedLinkError x) {
  • channelsAvailable = false;
  • return null;
  • }
  • }
  • private Charset cs;
  • private CharsetDecoder decoder;
  • private ByteBuffer bb;
  • // Exactly one of these is non-null
  • private InputStream in;
  • private ReadableByteChannel ch;
  • StreamDecoder(InputStream in, Object lock, Charset cs) {
  • this(in, lock, cs.newDecoder().onMalformedInput(CodingErrorAction.REPLACE).onUnmappableCharacter(CodingErrorAction.REPLACE));
  • }
  • StreamDecoder(InputStream in, Object lock, CharsetDecoder dec) {
  • super(lock);
  • this.cs = dec.charset();
  • this.decoder = dec;
  • // This path disabled until direct buffers are faster
  • if (false && in instanceof FileInputStream) {
  • // 获取流对应的channel
  • ch = getChannel((FileInputStream) in);
  • if (ch != null)
  • // 分配一个直接字节缓冲区
  • bb = ByteBuffer.allocateDirect(DEFAULT_BYTE_BUFFER_SIZE);
  • }
  • if (ch == null) {
  • this.in = in;
  • this.ch = null;
  • // 分配一个缓冲区
  • bb = ByteBuffer.allocate(DEFAULT_BYTE_BUFFER_SIZE);
  • }
  • bb.flip(); // So that bb is initially empty
  • }
  • StreamDecoder(ReadableByteChannel ch, CharsetDecoder dec, int mbc) {
  • this.in = null;
  • this.ch = ch;
  • this.decoder = dec;
  • this.cs = dec.charset();
  • // 分配一个缓冲区
  • this.bb = ByteBuffer.allocate(mbc < 0
  • ? DEFAULT_BYTE_BUFFER_SIZE
  • : (mbc < MIN_BYTE_BUFFER_SIZE
  • ? MIN_BYTE_BUFFER_SIZE
  • : mbc));
  • bb.flip();
  • }
  • private int readBytes() throws IOException {
  • bb.compact();
  • try {
  • if (ch != null) {
  • // Read from the channel
  • int n = ch.read(bb);
  • if (n < 0)
  • return n;
  • } else {
  • // Read from the input stream, and then update the buffer
  • int lim = bb.limit();
  • int pos = bb.position();
  • assert (pos <= lim);
  • int rem = (pos <= lim ? lim - pos : 0);
  • assert rem > 0;
  • int n = in.read(bb.array(), bb.arrayOffset() + pos, rem);
  • if (n < 0)
  • return n;
  • if (n == 0)
  • throw new IOException("Underlying input stream returned zero bytes");
  • assert (n <= rem) : "n = " + n + ", rem = " + rem;
  • bb.position(pos + n);
  • }
  • } finally {
  • // Flip even when an IOException is thrown,
  • // otherwise the stream will stutter
  • bb.flip();
  • }
  • int rem = bb.remaining();
  • assert (rem != 0) : rem;
  • return rem;
  • }
  • int implRead(char[] cbuf, int off, int end) throws IOException {
  • assert (end - off > 1);
  • CharBuffer cb = CharBuffer.wrap(cbuf, off, end - off);
  • if (cb.position() != 0)
  • // Ensure that cb[0] == cbuf[off]
  • cb = cb.slice();
  • boolean eof = false;
  • // 循环使用bb读取字节,并使用decoder解码后存入到cb中
  • for (; ; ) {
  • // cr会将bb中读取的内容尽可能地解码并存储到cb中
  • CoderResult cr = decoder.decode(bb, cb, eof);
  • if (cr.isUnderflow()) {
  • if (eof)
  • // 读到尾了
  • break;
  • if (!cb.hasRemaining())
  • // cb中没有剩余
  • break;
  • if ((cb.position() > 0) && !inReady())
  • break; // Block at most once
  • // 读取字节到bb属性中
  • int n = readBytes();
  • if (n < 0) {
  • eof = true;
  • if ((cb.position() == 0) && (!bb.hasRemaining()))
  • break;
  • decoder.reset();
  • }
  • continue;
  • }
  • if (cr.isOverflow()) {
  • assert cb.position() > 0;
  • break;
  • }
  • cr.throwException();
  • }
  • if (eof) {
  • // ## Need to flush decoder
  • decoder.reset();
  • }
  • if (cb.position() == 0) {
  • if (eof)
  • return -1;
  • assert false;
  • }
  • return cb.position();
  • }
  • String encodingName() {
  • return ((cs instanceof HistoricallyNamedCharset)
  • ? ((HistoricallyNamedCharset) cs).historicalName()
  • : cs.name());
  • }
  • private boolean inReady() {
  • try {
  • return (((in != null) && (in.available() > 0))
  • || (ch instanceof FileChannel)); // ## RBC.available()?
  • } catch (IOException x) {
  • return false;
  • }
  • }
  • boolean implReady() {
  • return bb.hasRemaining() || inReady();
  • }
  • void implClose() throws IOException {
  • if (ch != null)
  • ch.close();
  • else
  • in.close();
  • }
  • }

StreamDecoder类中的大部分工作原理都与NIO相关,大致的工作流程是通过NIO中提供的缓冲区ByteBuffer将数据进行读入并通过解码器转换后存储到CharBuffer。这里的解码流程根据选取的解码器不同而不通。在上面的StreamDecoder中重要的部分都已经做了注释,在此不再赘述,本站后面会讲解涉及到NIO相关的内容。

4. OutputStreamWriter源码解析

下面是OutputStreamWriter的源码,基于JDK 1.7.0_07:

  • package java.io;
  • import java.nio.charset.Charset;
  • import java.nio.charset.CharsetEncoder;
  • import sun.nio.cs.StreamEncoder;
  • // 将字节输出流转换成字符输出流
  • public class OutputStreamWriter extends Writer {
  • private final StreamEncoder se;
  • // 根据out创建OutputStreamWriter,使用编码charsetName(编码名)
  • public OutputStreamWriter(OutputStream out, String charsetName)
  • throws UnsupportedEncodingException
  • {
  • super(out);
  • if (charsetName == null)
  • throw new NullPointerException("charsetName");
  • se = StreamEncoder.forOutputStreamWriter(out, this, charsetName);
  • }
  • // 根据out创建OutputStreamWriter,使用默认的编码
  • public OutputStreamWriter(OutputStream out) {
  • super(out);
  • try {
  • se = StreamEncoder.forOutputStreamWriter(out, this, (String)null);
  • } catch (UnsupportedEncodingException e) {
  • throw new Error(e);
  • }
  • }
  • // 根据out创建OutputStreamWriter,使用编码cs
  • public OutputStreamWriter(OutputStream out, Charset cs) {
  • super(out);
  • if (cs == null)
  • throw new NullPointerException("charset");
  • se = StreamEncoder.forOutputStreamWriter(out, this, cs);
  • }
  • // 根据out创建OutputStreamWriter,使用编码器enc
  • public OutputStreamWriter(OutputStream out, CharsetEncoder enc) {
  • super(out);
  • if (enc == null)
  • throw new NullPointerException("charset encoder");
  • se = StreamEncoder.forOutputStreamWriter(out, this, enc);
  • }java io系列01之 "目录"
  • // 获取编码器enc
  • public String getEncoding() {
  • return se.getEncoding();
  • }
  • // 刷新缓冲区
  • void flushBuffer() throws IOException {
  • se.flushBuffer();
  • }
  • // 将单个字符写入到OutputStreamWriter中
  • public void write(int c) throws IOException {
  • se.write(c);
  • }
  • // 将字符数组cbuf从off开始的数据写入到OutputStreamWriter中,写入长度是len
  • public void write(char cbuf[], int off, int len) throws IOException {
  • se.write(cbuf, off, len);
  • }
  • // 将字符串str从off开始的数据写入到OutputStreamWriter中,写入长度是len
  • public void write(String str, int off, int len) throws IOException {
  • se.write(str, off, len);
  • }java io系列01之 "目录"
  • // 刷新输出流
  • // 它与flushBuffer()的区别是:flushBuffer()只会刷新缓冲,而flush()是刷新流,flush()包括了flushBuffer。
  • public void flush() throws IOException {
  • se.flush();
  • }
  • // 关闭输出流
  • public void close() throws IOException {
  • se.close();
  • }
  • }

与InputStreamReader类似,OutputStreamWriter的主要方法都调用了StreamEncoder的相关方法,下面将简要分析StreamEncoder类。

5. StreamEncoder源码解析

StreamEncoder类的源码同样是在sun.nio.cs包下的,这个包是在JDK中默认没有提供,下面是在OpenJDK中找到的源码:

  • package sun.nio.cs;
  • import java.io.*;
  • import java.nio.*;
  • import java.nio.channels.*;
  • import java.nio.charset.*;
  • public class StreamEncoder extends Writer {
  • // 默认的ByteBuffer大小
  • private static final int DEFAULT_BYTE_BUFFER_SIZE = 8192;
  • private volatile boolean isOpen = true;
  • private void ensureOpen() throws IOException {
  • if (!isOpen)
  • throw new IOException("Stream closed");
  • }
  • // Factories for java.io.OutputStreamWriter
  • public static StreamEncoder forOutputStreamWriter(OutputStream out,
  • Object lock,
  • String charsetName)
  • throws UnsupportedEncodingException {
  • String csn = charsetName;
  • if (csn == null)
  • // 如果csn为空,取默认的编码
  • csn = Charset.defaultCharset().name();
  • try {
  • if (Charset.isSupported(csn))
  • return new StreamEncoder(out, lock, Charset.forName(csn));
  • } catch (IllegalCharsetNameException x) {
  • }
  • throw new UnsupportedEncodingException(csn);
  • }
  • public static StreamEncoder forOutputStreamWriter(OutputStream out,
  • Object lock,
  • Charset cs) {
  • return new StreamEncoder(out, lock, cs);
  • }
  • public static StreamEncoder forOutputStreamWriter(OutputStream out,
  • Object lock,
  • CharsetEncoder enc) {
  • return new StreamEncoder(out, lock, enc);
  • }
  • public static StreamEncoder forEncoder(WritableByteChannel ch,
  • CharsetEncoder enc,
  • int minBufferCap) {
  • return new StreamEncoder(ch, enc, minBufferCap);
  • }
  • public String getEncoding() {
  • if (isOpen())
  • return encodingName();
  • return null;
  • }
  • public void flushBuffer() throws IOException {
  • synchronized (lock) {
  • if (isOpen())
  • implFlushBuffer();
  • else
  • throw new IOException("Stream closed");
  • }
  • }
  • public void write(int c) throws IOException {
  • char cbuf[] = new char[1];
  • cbuf[0] = (char) c;
  • write(cbuf, 0, 1);
  • }
  • public void write(char cbuf[], int off, int len) throws IOException {
  • synchronized (lock) {
  • // 先做各类检查
  • ensureOpen();
  • if ((off < 0) || (off > cbuf.length) || (len < 0) ||
  • ((off + len) > cbuf.length) || ((off + len) < 0)) {
  • throw new IndexOutOfBoundsException();
  • } else if (len == 0) {
  • return;
  • }
  • implWrite(cbuf, off, len);
  • }
  • }
  • public void write(String str, int off, int len) throws IOException {
  • /* Check the len before creating a char buffer */
  • if (len < 0)
  • throw new IndexOutOfBoundsException();
  • // 将需要写出的字符串片段存入cbuf中,然后通过write方法写出
  • char cbuf[] = new char[len];
  • str.getChars(off, off + len, cbuf, 0);
  • write(cbuf, 0, len);
  • }
  • public void flush() throws IOException {
  • synchronized (lock) {
  • ensureOpen();
  • implFlush();
  • }
  • }
  • public void close() throws IOException {
  • synchronized (lock) {
  • if (!isOpen)
  • return;
  • implClose();
  • isOpen = false;
  • }
  • }
  • private boolean isOpen() {
  • return isOpen;
  • }
  • // -- Charset-based stream encoder impl --
  • private Charset cs;
  • private CharsetEncoder encoder;
  • private ByteBuffer bb;
  • // Exactly one of these is non-null
  • private final OutputStream out;
  • private WritableByteChannel ch;
  • // Leftover first char in a surrogate pair
  • private boolean haveLeftoverChar = false;
  • private char leftoverChar;
  • private CharBuffer lcb = null;
  • private StreamEncoder(OutputStream out, Object lock, Charset cs) {
  • this(out, lock,
  • cs.newEncoder()
  • .onMalformedInput(CodingErrorAction.REPLACE)
  • .onUnmappableCharacter(CodingErrorAction.REPLACE));
  • }
  • private StreamEncoder(OutputStream out, Object lock, CharsetEncoder enc) {
  • super(lock);
  • this.out = out;
  • this.ch = null;
  • this.cs = enc.charset();
  • this.encoder = enc;
  • // This path disabled until direct buffers are faster
  • if (false && out instanceof FileOutputStream) {
  • ch = ((FileOutputStream) out).getChannel();
  • if (ch != null)
  • // 分配一个直接字节缓冲区
  • bb = ByteBuffer.allocateDirect(DEFAULT_BYTE_BUFFER_SIZE);
  • }
  • if (ch == null) {
  • // 分配一个字节缓冲区
  • bb = ByteBuffer.allocate(DEFAULT_BYTE_BUFFER_SIZE);
  • }
  • }
  • private StreamEncoder(WritableByteChannel ch, CharsetEncoder enc, int mbc) {
  • this.out = null;
  • this.ch = ch;
  • this.cs = enc.charset();
  • this.encoder = enc;
  • // 分配一个字节缓冲区
  • this.bb = ByteBuffer.allocate(mbc < 0
  • ? DEFAULT_BYTE_BUFFER_SIZE
  • : mbc);
  • }
  • private void writeBytes() throws IOException {
  • // 将bb切换为读模式
  • bb.flip();
  • int lim = bb.limit();
  • int pos = bb.position();
  • assert (pos <= lim);
  • int rem = (pos <= lim ? lim - pos : 0);
  • if (rem > 0) {
  • if (ch != null) {
  • // 如果ch不为空,写入ch中
  • if (ch.write(bb) != rem)
  • assert false : rem;
  • } else {
  • // 否则写入out流
  • out.write(bb.array(), bb.arrayOffset() + pos, rem);
  • }
  • }
  • bb.clear();
  • }
  • private void flushLeftoverChar(CharBuffer cb, boolean endOfInput)
  • throws IOException {
  • if (!haveLeftoverChar && !endOfInput)
  • return;
  • if (lcb == null)
  • lcb = CharBuffer.allocate(2);
  • else
  • lcb.clear();
  • if (haveLeftoverChar)
  • lcb.put(leftoverChar);
  • if ((cb != null) && cb.hasRemaining())
  • lcb.put(cb.get());
  • lcb.flip();
  • while (lcb.hasRemaining() || endOfInput) {
  • CoderResult cr = encoder.encode(lcb, bb, endOfInput);
  • if (cr.isUnderflow()) {
  • if (lcb.hasRemaining()) {
  • leftoverChar = lcb.get();
  • if (cb != null && cb.hasRemaining())
  • flushLeftoverChar(cb, endOfInput);
  • return;
  • }
  • break;
  • }
  • if (cr.isOverflow()) {
  • assert bb.position() > 0;
  • writeBytes();
  • continue;
  • }
  • cr.throwException();
  • }
  • haveLeftoverChar = false;
  • }
  • void implWrite
  • (char cbuf[], int off, int len)
  • throws IOException {
  • CharBuffer cb = CharBuffer.wrap(cbuf, off, len);
  • if (haveLeftoverChar)
  • // 如果有遗留字符,优先写出遗留字符
  • flushLeftoverChar(cb, false);
  • while (cb.hasRemaining()) {
  • // 将cb中的内容通过编码器编码为字节然后存入bb
  • CoderResult cr = encoder.encode(cb, bb, false);
  • if (cr.isUnderflow()) {
  • assert (cb.remaining() <= 1) : cb.remaining();
  • if (cb.remaining() == 1) {
  • // 如果cb还有剩余的内容,存入leftoverChar
  • haveLeftoverChar = true;
  • leftoverChar = cb.get();
  • }
  • break;
  • }
  • if (cr.isOverflow()) {
  • assert bb.position() > 0;
  • writeBytes();
  • continue;
  • }
  • cr.throwException();
  • }
  • }
  • // implFlushBuffer只会将bb缓冲区中的数据写出(有可能写到ch也有可能写到out)
  • void implFlushBuffer() throws IOException {
  • if (bb.position() > 0)
  • writeBytes();
  • }
  • /**
  • * implFlush会先将bb缓冲区中的数据写出(有可能写到ch也有可能写到out)
  • * 如果out不为空,则将out的数据也刷出
  • */
  • void implFlush() throws IOException {
  • implFlushBuffer();
  • if (out != null)
  • out.flush();
  • }
  • void implClose() throws IOException {
  • flushLeftoverChar(null, true);
  • try {
  • for (; ; ) {
  • CoderResult cr = encoder.flush(bb);
  • if (cr.isUnderflow())
  • break;
  • if (cr.isOverflow()) {
  • assert bb.position() > 0;
  • writeBytes();
  • continue;
  • }
  • cr.throwException();
  • }
  • if (bb.position() > 0)
  • writeBytes();
  • if (ch != null)
  • ch.close();
  • else
  • out.close();
  • } catch (IOException x) {
  • encoder.reset();
  • throw x;
  • }
  • }
  • String encodingName() {
  • return ((cs instanceof HistoricallyNamedCharset)
  • ? ((HistoricallyNamedCharset) cs).historicalName()
  • : cs.name());
  • }
  • }

StreamEncoder与StreamDecoder恰好相反,它的大致的工作流程是通过NIO中提供的缓冲区CharBuffer将数据进行读入并通过编码器转换后存储到ByteBuffer。这里的编码流程根据选取的解码器不同而不同。需要注意的是,在构造StreamEncoder时可以传入一个WritableByteChannel通道,如果该通道不为空,在最后的写出数据的时将会写出到该通道,否则写出到传入的流对象中。

6. InputStreamReader和OutputStreamWriter示例

在本示例中先通过OutputStreamWriter将一些内容写出到一个文件中,使用UTF-8编码,源码如下:

  • public void outputStreamWriterTest() throws IOException {
  • OutputStreamWriter outputStreamWriter = new OutputStreamWriter(new FileOutputStream("file.data"), "utf-8");
  • System.out.println("getEncoding: " +outputStreamWriter.getEncoding());
  • for (int i = 0; i < 26; i++) {
  • outputStreamWriter.write('a' + i);
  • }
  • outputStreamWriter.write("\nhello, 中国");
  • outputStreamWriter.append("\nThis is a word");
  • outputStreamWriter.close();
  • }

运行后将打印内容getEncoding: UTF8,并且将得到一个file.data的文件,内容如下:

  • abcdefghijklmnopqrstuvwxyz
  • hello, 中国
  • This is a word

同时,可以查看file.data文件的二进制码:

  • 00000000: 6162 6364 6566 6768 696a 6b6c 6d6e 6f70 7172 7374 7576 7778 :abcdefghijklmnopqrstuvwx
  • 00000018: 797a 0a68 656c 6c6f 2c20 e4b8 ade5 9bbd 0a54 6869 7320 6973 :yz.hello, .......This is
  • 00000030: 2061 2077 6f72 64 : a word

通过UTF-8的编码规则可知,字的UTF-8编码为E4B8AD,国字的UTF-8编码为E59BBD,正好对应于上述内容中的e4b8 ade5 9bbd段内容。

接下来通过InputStreamReader将file.data文件中的内容以UTF-8的解码格式读入,源码如下:

  • public void inputStreamReaderTest() throws IOException {
  • InputStreamReader inputStreamReader = new InputStreamReader(new FileInputStream("file.data"), "utf-8");
  • for (int i = 0; i < 26; i++) {
  • char c = (char) inputStreamReader.read();
  • System.out.print(String.valueOf(c));
  • }
  • char[] chars = new char[64];
  • int len;
  • while ((len = inputStreamReader.read(chars)) != -1) {
  • System.out.println(String.valueOf(chars, 0, len));
  • }
  • }

输出内容如下:

  • abcdefghijklmnopqrstuvwxyz
  • hello, 中国
  • This is a word