【问题标题】:Understanding Binary, ByteStream and Characters in java理解Java中的二进制、字节流和字符
【发布时间】:2014-01-19 11:27:15
【问题描述】:

我在消化 Java IO 类中的一些概念时遇到了一些困难。例如有两种类型的流,字节和字符。据我了解,字节流逐字节读取。

1。如果 java 中的 char 存储为 16 位(两个字节)数据类型,我怎么可能使用面向字节的输入流从文件中准确读取 char,例如“A”。文件输入流?

2。是不是我使用的字符(在 ascii 图表上大多在 0 到 122 之间)存储在分配的两个字节中的一个字节中?

3. DataInputStream/DataOutputStream 允许我读取和写入二进制数据,其他输入流如 FileInputStream/FileOutputStream 允许我读取和写入究竟是什么?我基本上想知道当我希望将数据输出为我可以阅读的文本时使用哪个流(使用像记事本这样的简单文本编辑器),以及何时将其编码为原始二进制数据(在记事本中看起来像垃圾的文本)?

努力理解 java 中流的概念以及何时使用。

【问题讨论】:

  • 字符流处理字符,而不是字节。说字符流“逐字节读取”是不准确的。
  • char 16位数据类型。它不存储字符;它存储一个 UTF-16 代码单元。恰好一个或两个 UTF-16 代码单元构成一个 UTF-16 代码点。代码点标识特定的 Unicode 字符。此外,您正在查看错误的字符集。 Java 通常使用Unicode,尽管某些流类默认为操作系统默认字符集。

标签: java stream binary char java-io


【解决方案1】:

取决于您正在阅读的文件的格式。

如果文件是 ASCII 字节流,则执行以下操作:

InputStream is = new FileInputStream( filePath );
Reader reader = new InputStreamReader( is, "ISO-8859-1" );

char ch = reader.read();

您总是首先在面向字节的文件上打开输入流。然后,InputStreamReader 将字节转换为字符。当然,在这种情况下,ISO-8859-1 是从单字节值到完全相同的字符值的映射。显然其他映射也是可能的,但 ISO-8859-1 恰好与 Unicode 集的前 255 个字符相同,而其中的前 127 个字符恰好与 ASCII 相同。

写作时使用:

OutputStream os = new FileOutputStream( filePath ) ;
Writer w = new OutputStreamWriter( os, "ISO-8859-1" );

w.write( ch );

再一次,是根据 ISO-8859-1 字符集在字符和字节流之间进行适当转换的 OutputStreamWriter。生成的文件每个字符都有一个字节。

这里还有一些proper basic stream patterns 的示例。

如果使用上面的你执行这个:

w.write("AAAA");
w.flush();
w.close();

生成的文件将包含 4 个字节,每个字节的值为 65。使用顶部的代码读回该文件将导致内存中有四个“A”字符,但在内存中,每个字符占用 16 位。

如果文件以不同的字符集编码,可能包括多个字节字符,则只需在 InputStreamReader/OutputStreamWriter 中使用正确的编码,在读取和写入时就会发生正确的转换。

UTF-8 不是字符集,而是将常规 unicode 字符编码为字节序列,事实证明 UTF-8 编码非常聪明,将 unicode 字符的前 127 个字符映射为前 127 个字节值(作为单个字节本身)。然后字符 >= 128 连续使用 2 个或更多字节值,其中每个字节值 >= 128。如果您知道 ascii 文件仅使用“7 位”ASCII,那么 UTF-8 将起作用也为你。对于 Java,通常 UTF-8 是用于文件的最佳编码,因为它可以正确编码所有可能的 Java char 值而不会丢失。

了解有关流的这一点非常重要。我建议您不要尝试以任何其他方式将字节转换为字符。当然可以,但这是一种浪费,因为流中的转换非常可靠和正确。

(更糟糕的是......实际上一个字符是一个 32 位的数量,其中 20 位可以编码为 16 位 char 值的序列,编码为 UTF-16。建议你暂时忽略它,但请注意,即使在由 16 位字符值组成的 Java 字符串中,也有一些双字符序列。)

【讨论】:

  • 很好的答案,但是...... ASCII 的 Java Charset 是“US-ASCII”而不是“ISO-8859-1”。我认为您提到“ISO-8859-1”是因为很难找到仍然使用 ASCII 的示例。
  • 我明白你说的。有时,读取文件的选项如此之多可能会让人有些困惑。有些事情我不完全理解,但我想根据您的答案和其他答案阅读更多内容,并弄乱一些代码,看看我想出了什么。非常感谢!
  • @Tom 从技术上讲你是正确的,但这个答案没有那么有用。基本上应该始终使用 ISO-8859-1。 US-ASCII 仅定义 128 个字符。一个字节可以包含 256 个值。如何处理其他值? ISO-8859-1 的前 128 个字符与 US-ASCII 完全相同。如果文件仅包含 7 位 ASCII,则两种编码都可以正常工作;对 US-ASCII 没有优势。但是如果出现字节值 > 127,那么 ISO-8859-1 提供了合理的处理方式,并且 ISO-8859-1 是 WWW 上的默认编码,所以很多文件都是这样编码的。
【解决方案2】:

在我尝试回答您的问题之前,需要了解一些非常基本的内容。

  1. 在最低级别InputStream/OutputStream),一切都是比特和字节。因此,最低级别的流处理的是位/字节的原始数据。
  2. 现在要将原始字节转换为可读字符,您需要字符编码或Character Set。所以简而言之,字符编码是一个指令(从字节到视觉字符的映射),用于将原始字节转换为定义集(例如UTF-8)中的可读字符。

现在来回答你的问题:

如果 java 中的 char 存储为 16 位(两个字节)数据类型,我如何使用面向字节的输入流从文件中准确读取 char,比如“A”,例如.文件输入流?

为了读取字符数据,原始输入流被包装在面向字符的流中,例如

FileInputStream fis = new FileInputStream("test.txt");
InputStreamReader isr = new InputStreamReader(fis, "UTF8"); 

正如javadoc 所说,InputStreamReader 是从字节流到字符流的桥梁

是不是我使用的chars(ascii图表上大多在0到122之间)存储在分配的两个字节中的一个字节中?

是的。 ascii 字符集是较大的 Unicode 集的子集,例如 UTF-8

DataInputStream/DataOutputStream 允许我读写二进制数据,其他输入流如 FileInputStream/FileOutputStream 允许我读写究竟是什么?

我想现在很明显DataInputStream/DataOutputStream 用于字符数据,而ileInputStream/FileOutputStream 用于原始数据。

我基本上想知道当我希望将数据输出为我可以阅读的文本时使用哪个流(使用像记事本这样的简单文本编辑器),以及何时将其编码为原始二进制数据(文本看起来像记事本里的垃圾)?

对于文本,请使用任何 Readers/Writers (Here is an example)

【讨论】:

    【解决方案3】:

    如果java中的一个char存储为16bit(两个字节)的数据类型,是怎么做到的 我可以准确地从文件中读取一个字符,比如'A' 面向字节的输入流,例如。文件输入流?

    试试看

    System.out.println(Integer.toBinaryString('A'));
    

    打印出字符'A' 的二进制表示。这打印

    1000001
    

    由于'A'char,它实际上是用16 位存储的

    00000000 01000001
    

    所以你所要做的就是读取两个连续的字节并适当地使用它们来形成char。看看实际效果

    ByteBuffer buffer = ByteBuffer.wrap(new byte[] {0b00000000, 0b01000001});
    System.out.println(buffer.getChar());
    

    打印出来的

    A
    

    这样做是将数组中的第一个byte 用作char 中的前8 位,将第二个byte 用作后8 位。


    DataInputStream/DataOutputStream 允许我读写二进制 数据,其他输入流如 FileInputStream/FileOutputStream 允许 我到底要读写什么?我基本上想知道哪个 当我希望将数据输出为我可以阅读的文本时使用的流(使用 像记事本这样的简单文本编辑器)与我希望将其编码为原始时 二进制数据(在记事本中看起来像垃圾的文本)?

    无论您是在编写文本还是其他任何内容,都是位和字节。你可以做得很好

    "someString".getBytes()
    

    然后写下这些。所以这并不重要。使用最能代表你所做的事情。通常,您可以用PrintWriter 包装底层OutputStream,用ScannerBufferedReader 包装底层InputStream

    【讨论】:

    • 我喜欢你的回答,这很棒 - ByteBuffer buffer = ByteBuffer.wrap(new byte[] {0b00000000, 0b01000001}); System.out.println(buffer.getChar());
    猜你喜欢
    • 1970-01-01
    • 2020-12-20
    • 1970-01-01
    • 2018-11-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多