字符集
1 字符集简述
首先我们要知道,在计算机中,任意数据都是以二进制形式存储的。且计算机的最小存储单元是1字节。
字符是各种文字和符号的总称,包括各国家文字、标点符号、图形符号、数字等。
字符集是多个字符的集合,字符集规定了某个文字对应的二进制数字存放方式(编码)和某串二进制数值代表了哪个文字(解码)的转换关系。
2 常见字符集
ASCII
ASCII(American Standard Code for Information Interchange,美国信息交换标准代码):包括了数字、英文、符号。
ASCII使用1个字节存储一个字符,一个字节是8位,总共可以表示128个字符信息,对于英文,数字来说是够用的。
GBK
GBK是中国的码表,简体中文版windows操作系统默认使用的码表就是GBK。
GBK字符集完全兼容ASCII字符集。
在GBK码表中:
- 一个中文占
2
个字节,二进制最高位字节的第一位是1
- 一个英文占
1
个字节,二进制第一位是0
Unicode
Unicode(又称统一码、万国码、单一码)是计算机科学领域里的一项业界字符编码标准。其容纳了世界上大多数国家的所有常见文字和符号。
Unicode的编码规则:
UTF-8
:用1-4个字节保存数据- UTF-16 :将数据转成16个比特位,即用2-4个字节保存数据
- UTF-32 :将数据转成32个比特位,用4个字节保存数据
其中最常用的就是 UTF-8
,在UTF-8中:
- 一个中文占
3
个字节,二进制最高位字节的第一位是1
,第一个字节转成十进制是负数 - 一个英文占
1
个字节,二进制第一位是0
,转成十进制是正数
3 编码
Java中编码的方法来自String类中的成员方法
成员方法 | 说明 |
---|---|
byte[] getBytes() | 使用平台的默认字符集编码,将结果存储到新的字节数组中 |
byte[] getBytes(String charsetName) | 使用指定的字符集编码,将结果存储到新的字节数组中 |
在IDEA中,默认使用的是Unicode中的UTF-8编码(显示在软件右下角)
String str = "hi世界";
//使用默认UTF-8编码
byte[] bytes1 = str.getBytes();
System.out.println(bytes1.length); // 8
运行结果为8。在UTF-8编码中,中文占3个字节,英文占1个字节。
使用指定编码会有编译时异常,旨在提醒“指定编码是否存在”,选择抛出即可。
public static void main(String[] args) throws UnsupportedEncodingException {
String str = "hi世界";
//使用指定GBK编码
byte[] bytes2 = str.getBytes("GBK");
System.out.println(bytes2.length); // 6
}
运行结果为6。GBK编码中,中文占两个字节,英文占一个字节。
4 解码
Java中解码的方法来自String类中的构造方法
构造方法 | 说明 |
---|---|
String(byte[] bytes) | 使用平台的默认字符集解码指定的字节数组来构造新的String对象 |
String(byte[] bytes, String charsetName) | 使用指定的字符集解码指定的字节数组来构造新的String对象 |
public class CharsetDemo1 {
public static void main(String[] args) throws UnsupportedEncodingException {
String str = "hi世界";
//使用默认UTF-8编码
byte[] bytes1 = str.getBytes();
//使用默认UTF-8解码
String s1 = new String(bytes1);
System.out.println(s1); // hi世界
//使用指定GBK解码
String s2 = new String(bytes1, "GBK");
System.out.println(s2); // hi涓栫晫
}
}
5 乱码
乱码,指的是由于本地计算机在用文本编辑器打开源文件时,使用了不相应字符集而造成部分或所有字符无法被阅读的一系列字符。
乱码的原因:
- 编码和解码方式不一致时,会出现乱码
- 读取数据时未读完所有表示汉字的字节(字节流的弊端)
解决:
- 编码和解码采用相同的方式
- 使用字符流读取数据
6 StandardCharsets
JDK17以后,Java在 java.nio.charset.StandardCharsets
类中封装了常用的字符集。定义如下:
public final class StandardCharsets {
...
// ASCII
public static final Charset US_ASCII = sun.nio.cs.US_ASCII.INSTANCE;
// UTF-8
public static final Charset UTF_8 = sun.nio.cs.UTF_8.INSTANCE;
// UTF-16
public static final Charset UTF_16 = new sun.nio.cs.UTF_16();
...
}
使用:
byte[] data = "hi".getBytes(StandardCharsets.UTF_8);
字符流
1 字符流简述
字符流的底层就是字节流,字符流在字节流基础上添加了字符集来解决中文乱码问题。
特点
输入流: 一次读一个字节,当遇到中文时,根据编码方式一次读多个字节。
输出流: 根据编码方式对数据编码,再转换为字节存储。
使用场景
操作纯文本文件。
继承体系
2 字节输入流
2.1 Reader
Reader
是Java的IO库提供的另一个输入流接口。和InputStream
的区别是,InputStream
是一个字节流,即以byte
为单位读取,而Reader
是一个字符流,即以char
为单位读取
Reader
是所有字符输入流的超类
导包 java.io.Reader
2.2 FileReader
FileReader
是Reader
的一个子类,它可以打开文件并获取Reader
。
2.2.1 构造方法
方法 | 说明 |
---|---|
FileReader(File file) | 根据File对象创建字符输入流对象。 |
FileReader(String Pathname) | 根据路径创建字符输入流对象。 |
FileReader(File file, Charset charset) | 创建指定字符集的字符输入流对象(JDK11) |
注意
构造方法底层在关联文件时,也会同时创建缓冲区(长度为8192的字节数组)。
- 当程序频繁地操作同一资源(如文件或数据库),会使性能降低。为了提升性能,可以将一部分数据暂时读入到内存的一块区域之中,以后直接从此区域中读取数据即可。这片区域就是缓冲区。
2.2.2 常用方法
方法 | 说明 |
---|---|
int read() | 读出一个字节的数据。返回数据解码后的十进制,如果没有数据返回-1 |
int read(char[] buffer) | 读出一个字节数组的数据,大小与创建的数组长度有关。返回值表示读取了多少字节数据,如果没有数据返回-1 |
void close() | 关闭流 |
注意
read()
空参方法
- 默认是一个字节一个字节读取数据,当遇到中文时才会一次读多个数据。
- 在读取到数据后,方法底层会对数据进行解码转成十进制,方法的返回值就是解码后的十进制。
- 如果想让结果为中文,可以将返回值进行强转。因为解码后的十进制对应着在字符集上的文字。
read(char[] buffer)
带参方法
- 底层会在空参的基础上再做一次强转。
2.2.3 读
无参read方法:需要强转
import java.io.FileReader;
import java.io.IOException;
public class FileReaderDemo1 {
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader("src\\do27CharStreamIO\\text.txt"); //已有数据 你好世界
int ch;
while ((ch = fr.read()) != -1) {
System.out.print((char) ch); // 你好世界
}
fr.close();
}
}
带参read方法:不需要强转
import java.io.FileReader;
import java.io.IOException;
public class FileReaderDemo2 {
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader("src\\do27CharStreamIO\\text.txt");
char[] chars = new char[3];
int len;
while ((len = fr.read(chars)) != -1) {
System.out.print(new String(chars, 0, len));
}
fr.close();
}
}
2.2.5 转换文件编码
原理: 将文件以原编码读取,再将文件用另一种编码写出。
private static void test() throws IOException {
FileReader fr = new FileReader("src\\do30ConvertStream\\text.txt", Charset.forName("GBK"));
FileWriter fw = new FileWriter("src\\do30ConvertStream\\newText.txt", Charset.forName("UTF-8"));
int b;
while ((b = fr.read()) != -1) {
fw.write(b);
}
fw.close();
fr.close();
}
3 字节输出流
3.1 Writer
Reader
是带编码转换器的InputStream
,它把byte
转换为char
,而Writer
就是带编码转换器的OutputStream
,它把char
转换为byte
并输出。
Writer
是所有字符输出流的超类。
3.2 FileWriter
FileWriter
就是向文件中写入字符流的Writer
。
3.2.1 构造方法
方法 | 说明 |
---|---|
public FileWriter(File file) | 根据File对象创建输出流对象。 |
public FileWriter(File file, boolean append) | 根据File对象创建输出流对象,续写。 |
public FileWriter(String pathname) | 根据路径创建输出流对象。 |
public FileWriter(String pathname, boolean append) | 根据路径创建输出流对象,续写。 |
public FileWriter(File file, Charset charset) | 创建指定字符集的字符输出流对象(JDK11) |
3.2.2 常用方法
方法 | 说明 |
---|---|
void write(int c) | 写入一个字符 |
void write(String str) | 写入一个字符串 |
void write(String str, int off, int len) | 写入一个字符串中从off开始共len个字符 |
void write(char[] cbuf) | 写入一个字符数组 |
void write(char[] cbuf, int off, int len) | 写入字符数组从off开始共len个字符 |
void flush() | 将缓冲区的数据,刷新到本地文件中 |
void close() | 关闭流 |
注意
缓冲区数据写到本地文件的三种情况:
- 缓冲区满了 -> 缓冲区默认
8192
字节 - 刷新 -> 调用
flush
方法 - 关流 -> 调用
close
方法
3.2.3 写
import java.io.FileWriter;
import java.io.IOException;
public class FileWriterDemo1 {
public static void main(String[] args) throws IOException {
FileWriter fw = new FileWriter("src\\do27CharStreamIO\\text.txt");
//写入单个字符
fw.write('你'); // 你
//写入一个字符串
fw.write("hello世界"); // hello世界
//写入一个字符数组
char[] chars = {'h', 'a', 'o', '好'}; // hao好
fw.write(chars);
fw.close();
}
}