面试题:说一下字符流

面试题:说一下字符流

作为一个 Java 开发者,字符流这个话题,说实话,不止一次让我感慨“你要说它简单吧,是挺简单的,但你真要用好了,还真得绕几圈。”特别是你在处理中文的时候,一不留神就来个“乱码翻车”,像极了在会议上被老板灵魂拷问“这乱码是什么?乱码是你写的代码吗?”

我就从最常见的开发场景开始聊起,看看为啥 Java 会搞出“字节流”和“字符流”这两个流派。

都是流,怎么还分个“字节”和“字符”?

咱们先把话说清楚:不管你是读文件还是读网络传输的数据,最底层其实都是“字节”,也就是 byte,这是 Java 世界里的“搬砖单位”。所以 Java 的 InputStream 和 OutputStream 就是字节流,它们操作的就是 byte。

那为啥还要搞一个字符流呢?很简单,因为我们人类用的是“字符”——中文、英文、符号、表情包……统统是字符。计算机读这些东西的时候,不光要知道它的“内容”,还要知道它的“语言”(也就是编码格式),否则就像你拿着西班牙语的歌词去唱京剧,字虽然在,但调子全错了。

Java 是站在开发者角度考虑的,反正你要处理的是字符,我就帮你把字节和字符之间的转换封装一下,你直接用字符流不香吗?

乱码的真相,都是编码惹的祸

说到字符流,必须提一句大家最头疼的事:乱码

这个东西就像早上赶地铁没抢到座,虽然不至于致命,但心情真的不太好。

为什么会乱码?说白了,就是你用错误的方式解读了字节。比如,你以为某一串字节是 UTF-8 编码的中文,结果它其实是 GBK,那你读出来的自然是一堆“问号”、“火星文”或者莫名其妙的符号。

来看个例子:

FileInputStream inputStream = new FileInputStream("input.txt");
int content;
while ((content = inputStream.read()) != -1) {
    System.out.print((char) content);
}

假如 input.txt 里写的是中文,这段代码就大概率翻车。为啥?因为 FileInputStream 是字节流,不懂编码,你直接把 byte 强转成 char,输出就容易出错。

这时候该谁上场?当然是字符流。

InputStreamReader:翻译官上线

Java 提供了 InputStreamReader,它是个桥梁,连接字节流和字符流。

你可以把它理解成一个自动带字幕的翻译器,负责把字节(byte)转成字符(char),关键是,它还支持你告诉它“字幕语言”。

比如:

InputStreamReader reader = new InputStreamReader(
    new FileInputStream("input.txt"), "UTF-8"
);

你一说“UTF-8”,它就乖乖按 UTF-8 来解读字节,避免乱码。

而如果你用 FileReader 呢?它内部其实就是 InputStreamReader,不过默认用系统编码(比如 Windows 上是 GBK,Mac 上可能是 UTF-8),所以用起来虽然简单,但遇到跨平台就容易出事。

最稳妥的方案就是直接用 InputStreamReader,自己指定编码,不给乱码留一点机会。

Reader 的日常操作,不止读那么简单

说起字符流读文件,Reader 是整个家族的老大,它的子类有很多,常用的就是 FileReader 和 BufferedReader

我们来一个简单例子:

try (FileReader reader = new FileReader("input.txt")) {
    int ch;
    while ((ch = reader.read()) != -1) {
        System.out.print((char) ch);
    }
} catch (IOException e) {
    e.printStackTrace();
}

这个代码能运行,但不一定完美。比如里面的 read() 是一个一个字符地读,对于长文件来说,效率就不行了。

所以推荐更高级一点的玩法,比如用字符数组缓存一下:

char[] buffer = new char[1024];
int len;
while ((len = reader.read(buffer)) != -1) {
    System.out.print(new String(buffer, 0, len));
}

更懒一点的开发者会直接用 BufferedReader,它内置缓冲区,还能按行读:

BufferedReader br = new BufferedReader(new FileReader("input.txt"));
String line;
while ((line = br.readLine()) != null) {
    System.out.println(line);
}

是不是比 read() 那种逐字输出的方式舒服多了?

Writer 家族:输出字符的主力军

说完读,得说写。你想把“你好,世界”写进文件,总不能一个 byte 一个 byte 地拼吧?那多像小学抄课文。

这时候,Writer 就来了,它跟 Reader 是一对好兄弟,一个负责读,一个负责写。

我们用最简单的 FileWriter

try (Writer writer = new FileWriter("output.txt")) {
    writer.write("你好,世界!");
} catch (IOException e) {
    e.printStackTrace();
}

注意了,FileWriter 默认也是用系统编码,如果你想精确控制编码,还是用“桥梁式”写法更稳:

OutputStreamWriter writer = new OutputStreamWriter(
    new FileOutputStream("output.txt"), "UTF-8"
);
writer.write("你好,世界!");
writer.close();

这样不管你在哪个操作系统写,读出来都不会有乱码。

flush() 和 close(),别让内容卡在路上

这里得提一个点,很多初学者会忘了 flush() 和 close()

Java 的 Writer 是有缓冲区的,也就是说你写进去了字符,它可能先“存在兜里”,没马上写进文件。

这时候,如果你没手动 flush() 或 close(),有些内容可能就永远“卡在半路”。

我的建议是:尽量用 try-with-resources,也就是上面例子里的写法,它会自动调用 close(),写完就关门走人,不留隐患。

最后聊聊选择题:字节流 VS 字符流

很多人一开始懵:这两个到底啥时候该用哪个?

一句话定乾坤:

  • 你处理的是“文字”:用字符流 Reader / Writer

  • 你处理的是“图片、音频、视频、压缩包”:用字节流 InputStream / OutputStream

举个栗子,如果你读的是一张 .jpg 图片,别傻傻用字符流,不然不仅乱码,可能直接文件都读挂了。

总结一下

字符流和字节流,一个更底层,一个更贴近“人”。Java 把它们做了层次上的区分,其实是为了开发者更方便地处理不同的数据类型。

  • 如果你看到“乱码”,十有八九是编码没处理好;

  • 如果你看到“输出为空”,有可能是你没 flush()

  • 如果你在纠结“用哪个流”,看你处理的是人能读的内容,还是机器能懂的二进制。

技术这东西,说简单也简单,说深也深。关键是,别死记硬背,得知道背后的道理和使用场景。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值