JavaSE 进阶 (9) 字节流

IO的概述

以前是如何存储数据的?

1
2
3
int a = 10;
int [] arr = {1,2,3,4,5};
ArrayList<String> list = new ArrayList<>();

不能永久化存储,只要代码运行结束,所有数据都会丢失。


学习IO流的目的?

  1. 将数据写到文件中,实现数据永久化存储
  2. 读取文件中已经存在的数据

IO流概述

其中:I 表示 intput,是数据从硬盘进内存的过程,称之为读。

O 表示 output,是数据从内存到硬盘的过程,称之为写。


思考一个问题?

在数据传输的过程中,是谁在读?是谁在写?这个参照物是谁?

IO 的数据传输,可以看做是一种数据的流动,按照流动的方向,以内存为参照物,进行读写操作。

简单来说:内存在读,内存在写

图1

IO的分类

图2

字节流:读和写的时候,操作的都是字节。字节流可以操作 “任何文件“。

字符流:读和写的时候,操作的都是字符。字符流只能操作 “文本文件(可以用记事本正常打开的文件)”


IO流的技术选型

什么是纯文本文件?

用 windows 记事本打开能读的懂,那么这样的文件就是纯文本文件。

字节流-字节输出流快速入门

字节流写数据

图3

构造方法

方法 说明
FileOutputStream(File file) 创建文件输出流以写入由指定的File对象表示的文件
FileOutputStream(String name) 创建文件输出流以指定的名称写入文件
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public static void main(String[] args) throws IOException{
	//1.创建字节输出流的对象---告诉虚拟机我要往哪个文件中写数据了
	FileOutputStream fos = new FileOutputStream("D:\\a.txt");
	//FileOutputstream fos = new Fileoutputstream(new File("D:\\a.txt"));

	//2.写数据
	fos.write(97);
	
	//3.释放资源
	fos.close();
}

字节流-注意事项

new FileOutputStream(“C:\it\a.txt”);

  • 如果文件不存在,会帮我们自动创建出来
  • 如果文件存在,底层的构造方法会对这个已存在的文件源进行删除,然后创建一个新的同名文件,里面内容为空,也就是说创建这个输出流对象,执行的是一个删除和重建的操作过程
  • fos.write(97):传递一个整数时,那么实际上写到文件中的,是这个整数在码表中对应的那个字符
  • fos.close():关闭 IO 流,释放资源,否则文件会被占用,无法操作文件 (告诉操作系统,我现在已经不再使用这个文件了)

字节流-一次写多个数据

字节流写数据的3种方式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public class OutputDemo3{
	public static void main(String[] args)throws IOException{
		FileOutputStream fos = new FileOutputStream("bytestream\\a.txt");
		fos.write(97);
		fos.write(98);
		fos.write(99);
		
		fos.close();
	}
}

现在写三个字节,调用了三次 write 方法

我能不能一次就把这三个字节的信息写到本地文件中呢?

方法名 说明
void write(int b) 一次写一个字节数据
void write(byte[] b) 一次写一个字节数组数据
void write(byte[] b, int off, int len) 一次写一个字节数组的部分数据
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public class OutputDemo3{
	public static void main(String[] args)throws IOException{
		FileOutputStream fos = new FileOutputStream("bytestream\\a.txt");
		
		byte[] bys = {97, 98, 99};
		fos.write(bys);  //abc
		
		fos.close();
	}
}

void write(byte[] b, int off, int len)

  • off: 从那个索引开始写
  • len: 写多少个数据
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public class OutputDemo3{
	public static void main(String[] args)throws IOException{
		FileOutputStream fos = new FileOutputStream("bytestream\\a.txt");
		
		byte[] bys = {97, 98, 99, 100, 101, 102, 103};
		fos.write(bys, 1, 2);  //bc
		
		fos.close();
	}
}

字节流-两个问题

字节流写数据的两个小问题

  • 字节流写数据如何实现换行呢?
  • 字节流写数据如何实现追加写入呢?

字节流写数据如何实现换行呢?

写完数据后,加换行符

  • windows: \r\n
  • linux: \n
  • mac: \r
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
FileOutputStream fos = new FileOutputStream( name:"bytestream\\a.txt");

fos.write(97);
//能加一个换行\r\n
fos.write("\r\n");
fos.write(98);
//能加一个换行
fos.write(99);
//能加一个换行
fos.write(100);
//能加一个换行
fos.write(101);
//能加一个换行

fos.close();

fos.write() 中,参数需要的是字节,而我们提供的是字符串 “\r\n”

我们需要将字符串变为字节,String 中的 getBytes() 方法

返回值类型 方法名 说明
byte[] getBytes() 使用平台的默认字符集将该String编码为一系列字节,将结果存储到新的字节数组中。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
FileOutputStream fos = new FileOutputStream( name:"bytestream\\a.txt");

fos.write(97);
//能加一个换行\r\n
fos.write("\r\n".getBytes());
fos.write(98);
//能加一个换行
fos.write("\r\n".getBytes());
fos.write(99);
//能加一个换行
fos.write("\r\n".getBytes());
fos.write(100);
//能加一个换行
fos.write("\r\n".getBytes());
fos.write(101);
//能加一个换行
fos.write("\r\n".getBytes());

fos.close();

字节流写数据如何实现追加写入呢?

也就是说: 假如 a.txt 中有几个字,我们怎么在这几个字后面追加内容,而不是把他们覆盖了


构造方法

Constructor 描述
FileOutputStream(File file) 创建文件输出流以写入由指定的File对象表示的文件。
FileoutputStream(FileDescriptor fdobj) 创建文件输出流以写入指定的文件描述符,表示与文件系统中实际文件的现有连接。
FileoutputStream(File file, boolean append) 创建文件输出流以写入由指定的File对象表示的文件。
FileOutputStream(String name) 创建文件输出流以指定的名称写入文件。
FileoutputStream(String name, boolean append) 创建文件输出流以指定的名称写入文件。
1
FileOutputStream fos = new FileOutputStream("bytestream\\a.txt", true);

第二个参数就是续写开关,如果没有传递,默认就是false,表示不打开续写功能,那么创建对象的这行代码会清空文件

如果第二个参数为true,表示打开续写功能,那么创建对象的这行代码不会清空文件里面的内容

字节流-try{}catch{}捕获异常

字节流写数据加try…catch异常处理

1
2
3
4
5
6
7
try {
    FileOutputStream fos = new FileOutputStream("a.txt");
    fos.write(97);
    fos.close();
} catch (IOException e) {
    e.printStackTrace();
}

fos.wirte(97)出异常,try下面的代码就无法执行了,就导致fos.close()无法执行到,资源无法释放


思考:那么我们如何操作,才能让close方法无论什么情况都一定会执行呢?

finally:在异常处理时提供finally块来执行所有清除操作。比如说IO流中的释放资源

特点:被finally控制的语句一定会执行,除非JVM退出

异常处理标准格式:try…catch…finally

图4

fos是局部变量,所以报错,需要将他定义成全局变量

图5

close()也可能会出异常,我们把close()也try…catch一下

图6

System.out.println(2/0)会报算数异常,下面的就不再执行,所以并没有给fos赋值,此时fos=null,在finally中执行fos.close()时会报空指针异常

图7

所以,加一个对 fos ≠ null 的判断,fos ≠ null的时候才进行关流,如果 fos == null 说明根本就没有跟文件产生关系,就没必要关

try(){}catch

try(){}catch{} 自动对资源的关闭

之前使用 try catch 的方式,一般都是 try{}catch{} 形式,即对资源的声明一般放在 try 之外,然后对资源的关闭放在 finally 中。代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static void main(String[] args) {
 String filePath = "E:\\_89R1044.JPG";
	 //1、try外层声明资源的引用
    FileInputStream inputStream = null;
    try {
        //2、创建和具体资源的连接
        inputStream = new FileInputStream(new File(filePath));
        //3、对资源的操作
        System.out.println(inputStream.available());
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        //3、关闭流
        if (inputStream!=null){
            try {
                inputStream.close();
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
}

在 IO 多路复用中,发现了一种新用法,即 try( ){ }catch{ } 的使用,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public static void main(String[] args) {
 String filePath = "E:\\_89R1044.JPG";
        //1、try中声明对资源的引用和创建具体的连接
    try(InputStream inputStream = new FileInputStream(new File(filePath));) {
        //2、对资源的操作
        System.out.println(inputStream.available());
    } catch (Exception e) {
        e.printStackTrace();
    }
}

可以看到代码量明显比在 finally 中关闭资源中精简很多,我们将资源放在 try 后的小括号中声明,try 在遇到异常或者执行完毕的时候会自动的关闭对资源的使用。

try(){} catch{} 的用法在 jdk 1.7 的时候就已经有了,需要注意的是对资源的声明和创建具体的对象都要在 try 后的小括号中。

字节流-小结

步骤:

  1. 创建字节输出流对象
  • 文件不存在,就创建。
  • 文件存在就清空。如果不想被清空则加 true
  1. 写数据
  • 可以写一个字节,写一个字节数组,写一个字节数组的一部分
  • 写一个回车换行: \r\n
  1. 释放资源

字节流-字节输入流基本学习

字节流读数据(一次读一个字节)

步骤:

  1. 创建字节输入流对象。
Constructor 描述
FileInputstream(File file) 通过打开与实际文件的连接来创建一个FileInputStream, 该文件由文件系统中的File对象file命名。
FileInputStream(FileDescriptor fdobi) 通过使用文件描述符fdobj创建FileInputStream, 该文件描述符表示与文件系统中的实际文件的现有连接。
FileInputStream(String name) 通过打开与实际文件的连接来创建一个FileInputStream, 该文件由文件系统中的路径名name命名。
  1. 读数据
Modifier and Type 方法 描述
int read( ) 从该输入流读取一个字节的数据
  1. 释放资源
Modifier and Type 方法 描述
void close( ) 关闭此文件输出流并释放与此流相关联的任何系统资源。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public static void main(String[] args) throws IOException{
	//如果文件存在,那么就不会报错。
	//如果文件不存在,那么就直接报错,
	FileInputStream fis = new FileInputStream("bytestream\\a.txt");
	
	int read = fis.read();
	//一次读取一个字节,返回值就是本次读到的那个字节数据
	//也就是字符在码表中对应的那个数字。
	//如果我们想要看到的是字符数据,那么一定要强转成char
	system.out.println((char)read);

	//释放资源
	fis.close();
}

字节流-读多个字节

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
public class OutputDemo {
    public static void main(String[] args) throws IOException {
        FileInputStream fis = new FileInputStream( name:"bytestream\\a.txt");
        //1,文件中多个字节我怎么办?
        /*while(true){
            int i1 = fis.read();
            System.out.println(i1);
        }*/

        int b;
        while ((b = fis.read())!=-1){
            System.out.println((char) b);
        }

        fis.close();
    }
}

写死循环的话,它会一直读下去,当把字节读完后,没有字节了,它会继续读空白的内容,这时它的返回值就是 -1,代表数据丢失,-1 在码表中没有对应的值,这时用 char 强转会出现

所以我们需要写一个结束标记

字节流-文件复制

需求:把 “C:\it\a.avi” 复制到当前模块下

分析:

  1. 复制文件,其实就把文件的内容从一个文件中读取出来(数据源),然后写入到另一个文件中(目的地)
  2. 数据源:C:\it\a.avi–读数据–FilelnputStream
  3. 目的地:模块名称\copy.avi–写数据–FileOutputStream
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public class OutputDemo {
	public static void main(String[] args)throws IOException{
		//创建了字节输入流,准备读数据。
		FileInputStream fis = new FileInputStream("C:\\it\\a.avi");
		//创建了字节输出流,准备写数据,
		FileOutputStream fos = new FileOutputStream("bytestream\\a.avi");
		
		int b;
		while((b = fis.read()) != -1){
			fos.write(b);
		}
		
		fis.close();
		fos.close();
	}
}

字节流-定义小数组拷贝

思考:如果操作的文件过大,那么速度会不会有影响?

一个一个字节的拷贝文件,如果操作的文件过大,那么速度会有影响,慢

试想一下,如果有几千个字节,那就要执行几千次,时间会非常的长

图8

提高拷贝速度的解决方案

为了解决速度问题,字节流通过创建字节数组,可以一次读写多个数据

一次读一个字节数组的方法:

  • public int read(byte[] b): 从输入流读取最多 b.length 个字节的数据
  • 返回的是读入缓冲区的总字节数, 也就是实际的读取字节个数

返回的是读取到的有效的字节个数,当读不到的时候,返回 -1,如果读不到返回 0,容易引起歧义,让别人认为读到的是 0

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public static void main(String[] args)throws IOException{
	FileInputStream fis = new FileInputStream("C:\\it\\a.avi");
	FileoutputStream fos = new FileoutputStream("bytestream\\a.avi");
	
	byte[] bytes = new byte[1024];
	int 1en;  //本次读到的有效字节个数 -- 这次读了几个字节
	
	while((len = fis.read(bytes)) != -1){
		fos.write(bytes, 0, len);
	}
	
	fis.close();
	fos.close();
}

字节流-小数组拷贝原理

图9

第一次读取到的是 ab ,长度 len = 2

第二次读取到的是 cd ,当 cd 读到数组中的时候,就会把 ab 覆盖掉,长度 len = 2

第三次读取到的是 e ,当 e 读到数组中的时候,会把第一个 c 覆盖掉,第二个是不能覆盖的,此时数组中的元素就是 e 和原来的 d ,长度 len = 1

图10

缓冲流-一次读写一个字节代码实现

字节流缓冲流

我们首先学习的是,读一个字节,写一个字节的拷贝方式,发现这种方式拷贝起来比较慢,这个问题,我们学了用小数组拷贝,提高效率的解决方式,我们都发现这个问题了,Java的设计者也发现了,所以在Java当中,它提供了一种流,叫字节缓冲流,用来帮助我们提高读和写的效率


字节缓冲流 (帮助提高读和写的效率)

缓冲区的大小为 8192,也就是 1024 * 8

缓冲流 = 基本流 + 缓冲区 (8K数组)


字节缓冲流:

  • BufferedOutputStream(OutputStream out): 字节缓冲输出流
  • BufferedInputStream(InputStream in): 字节缓冲输入流

构造方法

Constructor 描述
BufferedInputStream(InputStream in) 创建一个BufferedInputStream并保存其参数,输入流in供以后使用
BufferedInputstream(Inputstream in, int size) 创建具有指定缓冲区大小的BufferedInputStream,并保存其参数,输入流in供以后使用

为什么构造方法需要的是字节流,而不是具体的文件或者路径呢?

  • 字节缓冲流仅仅提供缓冲区,这个缓冲区其实就是一个数组,这个数组的功能其实就是用来提高读和写的效率,但是缓冲流本身不能直接操作文件中的数据,而真正的读写数据还得依靠基本的字节流对象进行操作

缓冲区就是一个数组,就是为了提高读和写的效率,缓冲流本身并不能操作数据,操作数据还得字节流

图11
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public static void main(String[] args)throws IOException{
	//利用缓冲流去拷贝文件
	
	//创建一个字节缓冲输入流
	BufferedInputStream bis = new BufferedInputStream(new FileInputStream("bytestream\\a.avi"));
	//创建一个字节缓冲输出流
	BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("bytestream\\copy.avi"));
	
	int b;
	while((b = bis.read()) != -1){
		bos.write(b);
	}

	bis.close();
	bos.close();
}

在这里我们只需要把缓冲流关了就行,我们把缓冲流关了,他所关联的字节流就关了

缓冲流-一次读写一个字节原理

针对**BufferedInputStream()**来讲,我们创建它的对象,相当于就是在底层创建一个长度为 8192 的数组

图12 图13

**BufferedOutputStream()BufferedInputStream()**是一样的,也是在底层创建了一个长度为 8192 的字节数组

图14 图15

input其实就表示我们传递进来的字节流,这个字节流不为空,就把这个字节流关闭

图16
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
public static void main(String[] args)throws IOException{
	//利用缓冲流去拷贝文件
	
	//创建一个字节缓冲输入流
	//在底层创建了一个默认长度为8192的字节数组
	BufferedInputStream bis = new BufferedInputStream(new FileInputStream("bytestream\\a.avi"));
	//创建一个字节缓冲输出流
	//在底层创建了一个默认长度为8192的字节数组
	BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("bytestream\\copy.avi"));
	
	int b;
	while((b = bis.read()) != -1){
		bos.write(b);
	}

	//方法的底层会把字节流给关闭
	bis.close();
	bos.close();
}

循环拷贝的过程

图17
  • 读数据的还是字节流,缓冲流只是为了提高效率
  • 写数据的还是字节流,缓冲流只是为了提高效率

跟读取一个字节,写一个字节的区别在于,虽然缓冲流也是一个一个字节进行倒手的,从缓冲输入流的数组中取出一个字节,倒手到缓冲输出流的数组中,重复执行,但是这个过程是在内存中进行的,内存的运行速度是非常快的,而如果你一个字节一个字节的拷贝,那么数据是从最左边(数据源硬盘)开始一个一个跑到最右边(目的地硬盘),那就非常慢了,他要经历 硬盘 → 内存 → 硬盘的过程

缓冲流虽然一次读取8192个字节,写的时候也是一次写8192个字节,但是在内存中是一个一个字节进行倒手的,把字节缓冲输入流数组中的数据倒手到字节缓冲输出流的数组中

图18

在上图中的红色框的地方节省了时间,减少了从硬盘中读取数据的过程,也减少了将数据写到目的地的过程,总的来说,减少硬盘跟内存之间数据传递的次数,从而提高了性能

图19

缓冲流-一次读写一个字节数组

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
public static void main(String[] args) throws IOException{
	//缓冲流结合数组,进行文件拷贝
	//创建一个字节缓冲输入流
	BufferedInputStream bis = new BufferedInputStream(new FileInputStream("bytestream\\a.avi"));
	
	//创建一个字节缓冲输出流
	BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("bytestream\\copy.avi"));
	
	byte[] bytes = new byte[1024];
	int len;
	while((len = bis.read(bytes)) != -1){
		bos.write(bytes, 0, len);
	}

	bis.close();
	bos.close();
}

底层原理

图20

中间通过我们自己定义的数组进行倒手,每次倒手1024个字节,放到字节缓冲输出流内置的数组中

与上面缓冲流,一次读写一个字节相比,区别在于由原来一次倒手一个字节,变成一次倒手1024个字节 (自己定义数组的大小)

图21

BufferedInputStream-mark()、reset()用法,及readlimit问题

在 BufferedInputStream 中,有一个定义一个 byte 数组来存放流,文件等数据(俗称缓冲区数组),如下图所示

  1. 将文件数据部分读入缓冲区,缓冲区大小为 10
图22
  1. 从缓冲区中取出1,2两个数据
图23
  1. 调用mark(int readlimit)方法,具体值为:mark(3)
图24
  1. 继续输出 3,4,5,6 值
图25
  1. 调用 reset() 方法
图26
  1. 输出剩下的值

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package other;

import java.io.ByteArrayInputStream;
import java.io.IOException;

public class MarkExample {

    public static void main(String[] args) {
        try {
            // 初始化一个字节数组,内有10个字节的数据
            byte[] bytes = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
            // 用一个ByteArrayInputStream来读取这个字节数组
            ByteArrayInputStream in = new ByteArrayInputStream(bytes);
            // 将ByteArrayInputStream包含在一个BufferedInputStream
            BufferedInputStream bis = new BufferedInputStream(in);
            // 读取字节1,2
            System.out.print(bis.read() + ",");
            System.out.print(bis.read() + ",");
            // 在此处做标记,同时设置readlimit参数为3
            System.out.println("mark");
            bis.mark(3);

            // 继续输出3,4,5,6值
            System.out.print(bis.read() + ",");
            System.out.print(bis.read() + ",");
            System.out.print(bis.read() + ",");
            System.out.print(bis.read() + ",");
            // 调用reset()方法
            System.out.println("reset");
            bis.reset();
            // 输出剩下的值
            System.out.println("");
            int c;
            while ( (c = bis.read()) != -1 ) {
                System.out.print(c + ",");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}
图27

根据 JAVA 官方文档的描述,mark(int readlimit) 方法表示,标记当前位置,并保证在 mark 以后最多可以读取 readlimit 字节数据,mark 标记仍有效。如果在 mark 后读取超过 readlimit 字节数据,mark 标记就会失效,调用 reset() 方法会有异常。

但实际的运行情况却和 JAVA 文档中的描述并不完全相符。 有时候在 BufferedInputStream 类中调用 mark(int readlimit) 方法后,即使读取超过 readlimit 字节的数据,mark 标记仍有效,仍然能正确调用 reset 方法重置。

事实上,mark 在 JAVA 中的实现是和缓冲区相关的。只要缓冲区够大,mark 后读取的数据没有超出缓冲区的大小,mark 标记就不会失效。如果不够大,mark 后又读取了大量的数据,导致缓冲区更新,原来标记的位置自然找不到了。

因此,mark 后读取多少字节才失效,并不完全由 readlimit 参数确定,也和 BufferedInputStream 类的缓冲区大小有关。 如果 BufferedInputStream 类的缓冲区大小大于 readlimit,在 mark 以后只有读取超过缓冲区大小的数据,mark 标记才会失效。

小结

字节流:

  • 可以操作(拷贝)所有类型的文件

字节缓冲流:

  • 可以提高效率
  • 不能直接操作文件,需要传递字节流

拷贝文件的四种方式:

  • 字节流一次读写一个字节
  • 字节流一次读写一个字节数组
  • 字节缓冲流一次操作一个字节
  • 字节缓冲流一次操作一个字节数组

我的理解:

  • 字节流一次读写一个字节,是从硬盘读一个到内存,从内存写一个到硬盘
  • 字节流一次读写一个字节数组,是从硬盘读一个数组到内存,从内存写一个数组到硬盘

这两种方式没有内存中 “倒腾“ 的这个步骤

  • 字节缓冲流一次操作一个字节,是从硬盘读一个8K数组到内存,从内存写一个8K数组到硬盘,在内存中是一个一个字节 “倒腾” 的
  • 字节缓冲流一次操作一个字节数组,是从硬盘读一个8K数组到内存,从内存写一个8K数组到硬盘,在内存中是一个数组 “倒腾” 的

只有缓冲流有内存中 “倒腾” 的这个步骤,因为他套了一层字节流

字节流操作文本文件出现乱码的问题

思考:既然字节流可以操作所有文件,那么为什么还要学习字符流?

  • 如果利用字节流,把文本文件中的中文,读取到内存中,有可能出现乱码。
  • 如果利用字节流,把中文写到文本文件中,也有可能出现乱码。

字符流-编码表

编码表

基础知识:

  • 计算机中储存的信息都是用二进制数表示的。
  • 按照某种规则,将字符变成二进制,再存储到计算机中,称为编码
  • 按照同样的规则,将存储在计算机中的二进制数解析显示出来,称为解码
  • 编码和解码的方式必须一致,否则会导致乱码。

简单理解:

存储一个字符:a,首先需在码表中查到对应的数字是97,然后把97转换成二进制进行存储。

读取的时候,先把二进制解析出来,再把二进制转成97,通过97查找到对应的字符是a。


编码表

ASCII字符集:

  1. ASCII(American Standard Code for Information Interchange,美国信息交换标准代码):包括了数字,大小写字符和一些常见的标点符号。

注意:ASCI码表中是没有中文的。

图28
  1. GBK:window系统默认的码表。兼容ASCII码表,也包含了21003个汉字,并支持繁体汉字以及部分日韩文字

注意:GBK是中国的码表,一个中文以两个字节的形式存储。但不包含世界上所有国家的文字。


Unicode码表

由国际组织ISO制定,是统一的万国码,计算机科学领域里的一项业界标准,容纳世界上大多数国家的所有常见文字和符号。

但是因为表示的字符太多,所以Unicode码表中的数字不是直接以二进制的形式存储到计算机的。会先通过UTF-7,UTF-7.5,UTF-8,UTF-16,以及UTF-32进行编码,再存储到计算机,其中最为常见的就是UTF-8

注意:Unicode是万国码,通过UTF-8编码后, 一个中文以三个字节的形式存储