IO
概述
对于任何程序设计语言而言,输入输出( I/O )系统都是比较复杂的而且是比较核心的。程序运行需要数据,数据的获取往往需要跟外部系统进行通信,外部系统可能是文件、数据库、其他程序、网络、IO设备等等。我们可以发现,外部系统比较复杂多变,那么我们有必要通过某种手段进行抽象、屏蔽外部的差异。我们希望通过某种技术实现对所有外部系统的输入输出操作, java.io
包为我们提供了相关的API,这就是我们这章所要学习的技术。
数据源
data source , 提供原始数据的原始媒介。常见的:数据库、文件、其他程序、内存、网
络连接、IO设备。
数据源就像水箱,流就像水管中流着的水流,程序就是我们最终的用户。 流是一个抽象、动态的概念,是一连串连续动态的数据集合。
流的概念
Stream:名词,水流,趋势。动词:流出,流动
数据源就像水箱,流就像水管中流着的水流,程序就是我们最终的用户。 流是一个抽象、动态的概念,是一连串连续动态的数据集合。
IO流的种类
IO流很庞大,从不同角度进行分类,常见大分类字符流和字节流。
处理的数据单元
按处理数据单位分为:字节流和字符流。 处理数据是音频、视频、doc、文本等一切为字节流 , 仅能处理文本的为字符流 。 字节流和字符流的用法几乎完全一致,区别在于它们所操作的数据单元不同,字节流(8位)、字符流(16 位),字节流主要由 InputStream
和 OutputStream
作为基类,字符流主要由Reader
和 Writer
作为基类。
- 字节流:按照字节读取数据(InputStream、OutputStream)
- 字符流:按照字符读取数据(Reader、Writer)
流向分类
输入流和输出流。 从节点到 java 内存 叫输入流, 从 java 内存到节点 叫输出流。Java 的输入流主要由 InputStream 和 Reader 作为基类,输出流主要由 OutputStream 和 Writer 作为基类。 (一切以程序为中心)
- 输入流:数据源到程序(InputStream、Reader读进来)
- 输出流:程序到目的地(OutPutStream、Writer写出去)
功能分类
节点流和处理流。
直接从/向一个特定的I/0设备(磁盘、网络等)读写数据
的流称为节点流,也常被称为低级流。 处理流则对于一个已存在的节点流进行连接或封装
,常被称为高级流(装饰器设计模式)。处理流为增强、提升性能的,本身不具备直接操作节点的能力。如扩音器,就是放大声音的。 节点流处于io操作的第一线,所有操作必须通过他们进行;处理流可以对其他流 进行处理(提高效率或操作灵活性)。
- 节点流:可以直接从数据源或目的地读写数据。
- 处理流:不直接连接到数据源或目的地,是处理流的流。通过对其他流的处理提高程序的性能。
体系图
FileOutputStream 用于写入诸如图像数据之类的原始字节的流。要写入字符流,请考虑使用
FileWriter。
使用流抽象的概念,屏蔽了实际的 I/O设备中处理数据的细节。
IO操作步骤
在进行任何操作之前,首先要明确目的(读还是写),找准源头(读取),找准目的地(写出)。
- 建立联系 :这一步骤是为了获取流,如果此时是文件,则需要将文件抽象到内存形成对象。后期也可以是其他的数据源
- 选择流:从读写、数据单元和功能方面考虑。输入|输出,字节|字符,结点流|处理流。
- 执行操作:该读就读,该写就写。考虑是一次性完成还行需要循环。
- 释放资源:程序中打开的文件 IO 资源不属于内存中的资源,垃圾回收无法回收,需要显示关闭。
字符集和编码格式
字符集
字符
计算机科学和信息科学中字符(character,或译为字元)是一个信息单位,通常来说就是一个字母、一个汉字、一个数字、一个标点符号。另外,还存在着一些控制字符,通常是不可打印的(不可见的,或称为是功能性的),有特定用途,以及emoji(绘文字)之类特殊的符号。
字符集
字符集(character set)指的是指定若干字符组成的一个集合,通常这个集合具有一定的规模和合理性,比如囊括一个国家或地区日常使用的文字字符、数字、标点符号和控制字符等。不同的国家和地区由于历史和文化的原因,使用不同的语言文字,因此存在各种不同的字符集。
字符编码
字符编码(character encoding)是把字符集中的字符映射为指定集合中某一对象(例如数字系统中表示为某个特定的二进制数),以便文本在计算机中存储和在通信网络传递。
乱码
有字符的编码,就有相应的解码,解码出错就会导致乱码问题。乱码指的是由于使用不同的字符集或字符编码方式,导致显示的部分或全部字符无法被正常的阅读,如常见的文本、网页、邮件乱码。
编码格式
计算机中存储信息的最小单元是一个字节(byte)即 8 个 bit,所以能表示的字符范围是 0~255 个。人类要表示的符号太多,无法用一个字节来完全表示。
ASCII 码
学过计算机的人都知道 ASCII 码,总共有 128 个,用一个字节的低 7 位表示,0~31 是控制字符如换行回车删除等;32~126 是打印字符,可以通过键盘输入并且能够显示出来。
ISO-8859-1
128 个字符显然是不够用的,于是 ISO 组织在 ASCII 码基础上又制定了一些列标准用来扩展 ASCII 编码,它们是 ISO-8859-1~ISO-8859-15,其中 ISO-8859-1 涵盖了大多数西欧语言字符,所有应用的最广泛。ISO-8859-1 仍然是单字节编码,它总共能表示 256 个字符。
GB2312
它的全称是《信息交换用汉字编码字符集 基本集》,它是双字节编码,总的编码范围是 A1-F7,其中从A1-A9 是符号区,总共包含 682 个符号,从 B0-F7 是汉字区,包含 6763 个汉字。
GBK
全称叫《汉字内码扩展规范》,是国家技术监督局为 windows95 所制定的新的汉字内码规范,它的出现是为了扩展 GB2312,加入更多的汉字,它的编码范围是 8140~FEFE(去掉 XX7F)总共有 23940 个码位,它能表示 21003 个汉字,它的编码是和 GB2312 兼容的,也就是说用 GB2312 编码的汉字可以用 GBK 来解码,并且不会有乱码。
GB18030
全称是《信息交换用汉字编码字符集》,是我国的强制标准,它可能是单字节、双字节或者四字节编码,它的编码与 GB2312 编码兼容,这个虽然是国家标准,但是实际应用系统中使用的并不广泛。
BIG5
繁体中文编码方式。
UTF-16
说到 UTF 必须要提到 Unicode(Universal Code 统一码),ISO 试图想创建一个全新的超语言字典,世界上所有的语言都可以通过这本字典来相互翻译。可想而知这个字典是多么的复杂,关于 Unicode 的详细规范可以参考相应文档。Unicode 是 Java 和 XML 的基础,下面详细介绍 Unicode 在计算机中的存储形式。
UTF-16 具体定义了 Unicode 字符在计算机中存取方法。UTF-16 用两个字节来表示 Unicode 转化格式,这个是定长的表示方法,不论什么字符都可以用两个字节表示,两个字节是 16 个 bit,所以叫 UTF-16。UTF-16 表示字符非常方便,每两个字节表示一个字符,这个在字符串操作时就大大简化了操作,这也是 Java 以 UTF-16 作为内存的字符存储格式的一个很重要的原因。
UTF-8
UTF-16 统一采用两个字节表示一个字符,虽然在表示上非常简单方便,但是也有其缺点,有很大一部分字符用一个字节就 可以表示的现在要两个字节表示,存储空间放大了一倍,在现在的网络带宽还非常有限的今天,这样会增大网络传输的流量,而且也没必要。而 UTF-8 采用了一种变长技术,每个编码区域有不同的字码长度。不同类型的字符可以是由 1~6 个字节组成。
字符集和字符编码的关系
字符集是书写系统字母与符号的集合,而字符编码则是将字符映射为一特定的字节或字节序列,是一种规则。通常特定的字符集采用特定的编码方式(即一种字符集对应一种字符编码,但Unicode不是,它采用现代的模型),因此基本上可以将两者视为同义词。
基本输入流
用来读取的,将其他地方的数据读取到Java内存的流称为输入流。可以是字节也可以是字符,字节流和字符流的操作方式几乎完全一样,只是操作的数据单元不同而已 。字节流可以操作所有文件,字符流仅操作纯文本。
抽象类:InputStream
和 Reader
InputStream
和Reader
是所有输入流的基类,它们是两个抽象类,是所有输入流的模版,其中定义的方法在所有输入流中都可以使用。
在InputStream种常用如下几个方法:
Modifier and Type | Method and Description |
---|---|
void |
close() 关闭此输入流并释放与流相关联的任何系统资源。 |
abstract int |
read() 从输入流读取数据的下一个字节。 |
int |
read(byte[] b) 从输入流读取一些字节数,并将它们存储到缓冲区 b |
int |
read(byte[] b, int off, int len) 从输入流读取最多 len 字节的数据到一个字节数组。 |
在Reader中常用如下几个方法
Modifier and Type | Method and Description |
---|---|
abstract void |
close() 关闭流并释放与之相关联的任何系统资源。 |
int |
read() 读一个字符 |
int |
read(char[] cbuf) 将字符读入数组。 |
abstract int |
read(char[] cbuf, int off, int len) 将字符读入数组的一部分。 |
对比InputStream和Reader 所提供的方法,可以看出这两个基类的功能基本相似。在读取文件时返回结果为 -1 时表明到了输入流的结束点。 InputStream 和 Reade 都是抽象的,不能直接创建它们的实例,可以使用它们的子类。
文件节点类: FileInputStream 和 FileReader
FileInputStream 和 FileReader,它们都是节点流,直接和指定文件关联。 操作方式基本一致。而且都使用父类中的方法即可。
单个字节读取
public class TestFileIO {
public static void main(String[] args) {
//创建file对象,建立联系
File file = new File("D:\\test.txt");
//选择流
FileInputStream fileInputStream = null;
try {
fileInputStream = new FileInputStream(file);
//计算实际读取的字节数
long length = file.length();
System.out.println(length);
//循环读取
long num = 0;
while(num<length){
char</