Java中的字节和字符

Posted zhengshuangxi

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java中的字节和字符相关的知识,希望对你有一定的参考价值。

最近在看Java中的IO相关知识,发现对字节和字符的理解还不够。写篇总结记录一下。

一、字节

所谓字节(Byte),是计算机数据存储的一种计量单位。一个二进制位称为比特(bit),8个比特组成一个字节,也就是说一个字节可以用于区分256个整数(0~255)。由此我们可以知道,字节本是面向计算机数据存储及传输的基本单位,后续的字符也就是以字节为单位存储的,不同编码的字符占用的字节数不同。

那么在Java中,除了存储的意义外,Java还将字节Byte作为一种基本数据类型,该数据类型在内存中占用一个字节,用于(-128~127)范围内的整数

byte a = -128;
byte b = 127;

总的来说,字节在Java中有两种含义:

  • 存储的单位
  • Java的数据类型,用于表示-128~127范围的整数

二、字符

计算机底层存储的是字节,字符的设计则是用于展示符号。屏幕上显示的各种文字,数字,符号等就是解码的字符。所以我们说字符是用来显示的符号,它将存储的字节转换成人们看得懂的符号,因此字符的核心就是定义字节与展示符号之间的关系,这种映射关系通常也叫做编码。

2.1、编码的由来

为什么要编码呢?前面我们知道数据都是以字节为单位存储在计算机中,字节可以区分256个整数,最容易想到的就是将这256个整数定义为256种状态并分别对应256个字符。但是人类符号太多了,256种是不够的。所以人们想到将多个字节合并起来表示人类语言符号,编码的问题就转化成了字节的组合问题。

2.2、编码的常见格式

如今有很多编码格式,常见的如ASCII、ISO-8859-1、GB2312、GBK、UTF-8、UTF-16等等。

ASCII编码是最基础的编码格式,标准的ASCII码一共有128个,占用字节的低7位,将英语系语种的符号都能覆盖住,但是总的来说能表示的字符还是非常有限。

ISO-8859-1编码是ASCII编码的一种扩展,它用了字节的8位,能表示256种字符,且向下兼容ASCII,包含了绝大多数的西欧符号。

GB2312是双字节编码,意味着它使用两个字节来表示符号,包含有6763个汉字。

GBK是GB2312的一个扩展,也是双字节编码,能够表示21003个汉字,且向下兼容GB2312。

...

编码的规范越来越多,不同语言的国家都定义了自己的语言符号编码标准,一时间编码标准百花齐放,在互联网的时代里交流十分不便,不同编码体系之间的信息交流都需要采用不同的解码方案,不然就会出现乱码的现象。于是国际标准化组织ISO制定了一个能够容纳世界上所有文字和符号的字符编码方案Unicode。Unicode是一个字符集,它规定了人类所有字符对应的二进制数,至于这个二进制数怎么存储则是由开发者来进行实现。其中比较流行的实现是UTF-8和UTF-16,还有一种UTF-32。

UTF-32编码使用4个字节,也就是32位二进制存储Unicode字符,效率高但是空间浪费。

UTF-8编码是一种变长的编码方式,它使用1~6个字节来存储,对于英语系的字符使用一个字节,向下兼容ASCII,对于汉字则使用两个字节,依次类推,这样就能够节省一定的空间。

UTF-16编码是介于两者之间的一种编码方式。对于部分字符采用2个字节,另一部分字符采用4个字节。因此UTF-16无法兼容ASCII。

在平时的使用中,UTF-8的使用还是比较多,就是由于它既能向下兼容ASCII,还能够在一定程度上节省空间。

2.3、Java IO流中的编码和解码

Java中是如何进行编码和解码的呢?我们知道,编码/解码的过程主要是发生在字符与字节之间转换的过程。在展示字符的时候,我们将内存中的字节解码成符号,在存储或者传输文件时,我们将字符编码位字节数据。

解码

解码的过程是将字节转换为字符,也就是我们在读取文件或者网络数据的过程。

在java中,我们通过FileReader读取文件数据,FileReader继承自InputStreamReader。在InputStreamReader中使用了解码器StreamDecoder。

// InputStreamReader.java
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import sun.nio.cs.StreamDecoder;

public class InputStreamReader extends Reader 
  // 解码器,按照指定编码方式将字节转换成字符
    private final StreamDecoder sd;

  // 通过dec指定解码器使用的编码方案
    public InputStreamReader(InputStream in, CharsetDecoder dec)
        super(in);
        if (dec == null)
            throw new NullPointerException("charset decoder");
        sd = StreamDecoder.forInputStreamReader(in, this, dec);
    
  
  // 读字符,以int形式(4字节)返回字符
    public int read() throws IOException 
        return sd.read();
    

 通过上述InputStreamReader源码我们可以知道:

  • 读取输入流时,通过StreamDecoder完成字节到字符的转换
  • 可以通过构造方法来设置编码方案
  • 读取的字符以int型数据返回,即4个字节

另外,上述列举只是源码的一部分,我们设置编码方案有很多种形式,如在构造方法种传入编码方式的String类型名称、传入CharSet类型的字符集以及上述的CharsetDecoder类型的字符解码方式。如果不传入编码方案,则默认为当前环境的编码方案。

编码

与解码类似,在存储文件或者写入数据的时候,我们将字符转换为字节,写入文件或者网络。

在java种,我们通过FileWriter来写入文件,FileWriter继承自OutputStreamWriter。在OutputStreamWriter种使用了编码器StreamEncoder。

// OutputStreamWriter.java
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import sun.nio.cs.StreamEncoder;

public class OutputStreamWriter extends Writer 
    // 编码器,按照指定编码方式将字符转换成字节
    private final StreamEncoder se;

    // 通过enc指定编码方案
    public OutputStreamWriter(OutputStream out, CharsetEncoder enc) 
        super(out);
        if (enc == null)
            throw new NullPointerException("charset encoder");
        se = StreamEncoder.forOutputStreamWriter(out, this, enc);
    
    
    // 写字符,写入的字符以int类型传入
    public void write(int c) throws IOException 
        se.write(c);
    

 通过源码我们可以知道:

  • 写入输出流时,通过StreamEncoder完成字符到字节的转换
  • 通过构造方法指定编码方案
  • 写入的字符都是int类型

以上是关于Java中的字节和字符的主要内容,如果未能解决你的问题,请参考以下文章

Java中的字节输入出流和字符输入输出流

Java中的字节流和字符流区别

Java中的字节流和字符流区别

芯学苑:浅析Java字节流和字符流

如何在java中处理字节序列?

java中的字符都是以Unicode编码?那么这里的字符指的是啥