黑马程序员技术交流社区

标题: 【成都校区】IO字符流与字节流 [打印本页]

作者: 哈哈大圣    时间: 2018-11-22 12:48
标题: 【成都校区】IO字符流与字节流
---
## 一、IO

> 把这种数据的传输,可以看做是一种数据的流动,按照流动的方向,以内存为基准,分为 输入`input` 和 输出`output` ,即流向内存是输入流,流出内存的输出流。

Java中I/O操作主要是指使用**java.io**包下的内容,进行输入、输出操作。
- **输入** 也叫作 **读取数据**;
- **输出** 也叫作 **写出数据**

**根据数据的流向分为**
- **输入流**:把数据从 其他设备 上读取到 内存 中的流。
- **输出流**:把数据从 内存 中写出到 其他设备 上的流。

**根据数据的类型分为**
- **字节流**:以字节为单位,读写数据的流。
- **字符流**:以字符为单位,读写数据的流

**写入数据的原理**(内存-->硬盘):java程序-->JVM(java虚拟机)-->OS(操作系统)-->OS调用写数据的方法-->把数据写入到文件中

**顶级父类抽象类**

||输入流|输出流|
---|---|---|
字节流|字节输入流(InputStream)|字节输出流(OutputStream)|
字符流|字符输入流(Reader)|字符输出流(Writer)|

---
## 二、字节输出流【OutputStream】顶级父类

> 无论使用什么样的流,底层都是二进制数据。
> `StringObj.getBytes()`:得到字节字符串对应的字节数组

`java.io.OutputStream`**抽象类** 字节输出流超类定义了基本共性的方法(忽略public)
- `void close()` : 关闭此输出流并释放与此流相关联的任何系统资源。
- `void flush()` : 刷新此输出流并强制任何缓冲的输出字节被写出。
- `void write(byte[] b)` : 将b.length字节从指定的字节数组写入此输入流。
- `void write(byte[] b, int off, int len)` : 从指定的字节数组写入len字节,从偏移量为off的位置开始输出到此输出流
- `abstract void write(int b)` :将指定的 字节 输出流,就是写入一个字节

> **close()** 方法,当完成流操作后,必须调用此方法,释放系统资源。


### FileOutputStream 字节输出流
1. 属于OutputStream的子类
2. `java.io.FileOutputStream`类是文件输出流,将数据写出到文件。

**构造方法**
- public FileOutputStream(File file) : 创建文件输出流 写入指定File对象表示的文件
- public FileOutputStream(String name) : 创建文件输出流以指定的名称写入文件。

> 创建一个流对象,必须传入一个文件路径,该路径下,如果没有这个文件,会创建该文件。
如果这个文件已存在,会清空这个文件的数据

```java
// 使用File对象创建流对象
File file = new File(".\\day_09\\test.txt");
FileOutputStream fos = new FileOutputStream(file);

// 使用文件名创建流对象
FileOutputStream fos2 = new FileOutputStream(".\\day_09\\test2");
```

**1.写出字节数据**`write(int b)` : 每次写出一个字节数据

```java
FileOutputStream fos2 = new FileOutputStream(".\\day_09\\test2.txt");
fos2.write(100);
fos2.close(); // 必须关闭资源
```

> int类型在内存中为4个字节,但是上面的只需要一个字节就能存储,所以只会保留一个字节(字节流没编码那么麻烦)。
> 使用完必须释放资源

**3.写出字节数组** `write(byte[] b)`

```java
FileOutputStream fos2 = new FileOutputStream(".\\day_09\\test2.txt");
fos2.write("我是一个兵,来自老百姓".getBytes());
fos2.close(); // 必须关闭资源
```

**写多个字节,文本方式打开的时候,查询的规则**
- 第一个字节是正数(0-127),查询Ascii表
- 第一个字节是负数,(winGBK)查询第一个与第二个组合找中文。

**4.写出指定长度字节数组**`write(byte[] b, int off, int len)`

```java
FileOutputStream fos2 = new FileOutputStream(".\\day_09\\test2.txt");
fos2.write("天王盖地虎,宝塔镇河妖".getBytes(), 18, 15);
// utf-8编码   宝塔镇河妖
fos2.close(); // 必须关闭资源
```

**5.数据追加续写**

保留数据并进行***追加操作***,构造方法需要传入一个是否追加的参数
- `public FileOutputStream(File file, boolean append)`: 文件对象形式,追加操作
- `public FileOutputStream(String name, boolean append)`: 字符串路径形式, 追加操作。

> 如果要追加数据就传入true,清空就传入false(默认是false)

```java
FileOutputStream fos2 = new FileOutputStream(".\\day_09\\test2.txt", true);
fos2.write("天王盖地虎,宝塔镇河妖".getBytes(), 18, 15);
fos2.close();
```

**6.写出换行**

```java
FileOutputStream fos2 = new FileOutputStream(".\\day_09\\test2.txt", true);
fos2.write("\r\n".getBytes());
fos2.write("windows下换行了".getBytes());
fos2.close();
```
> - 回车符 \r 和换行符 \n :
>   - 回车符:回到一行的开头(return)。
>   - 换行符:下一行(newline)。
> - 系统中的换行:
>   - Windows系统里,每行结尾是 回车+换行 ,即 \r\n
>   - Unix系统里,每行结尾只有 换行 ,即 \n
>   - Mac系统里,每行结尾是 回车 ,即 \r 。从 Mac OS X开始与Linux统一。


## 三、字节输人流【InputStream】顶级父类

先介绍字节输出流的超类`InputStream`字节输出流
`java.io.InputStream`抽象类 总超类 ,字节信息-\>读到内存,共性方法如下(省略public)
- `void close()` : 【**必须使用**】关闭此输入流并释放与此流相关联的任何系统资源。
- `abstract int read()` : 从输入流读取的下一个字节,
- `int read(byte[] b)`: 从输入流中读取一些字节数,并将它们存储大字节数组中。

> 必须使用**close**方法释放资源

### FileInputStream 字节输出流

`java.io.FileInputStream` 类 读取字节

**1.构造方法**(省略public)
- `FileInputStream(File file)`:通过打开一个与实际文件的连接 创建一个FileInputStream,该文件由文件由文件系统中的File对象file命名。
- `FileInputStream(String name)`: 通过打开与实际文件的连接来创建一个FileInputStream,该文件由文件系统中的路径名name命名。

> 创建一个流对象,必须传入一个文件路径,如果该路径下没有改文件(文件不存在),会抛出`FileNotFondException`

```java
// 使用File对象创建流对象
File file = new File(".\\day_09\\a.txt");
new FileOutputStream(file).write("还是写一点东西吧".getBytes());
FileInputStream fos = new FileInputStream(file);

// 使用文件名称创建流对象
FileInputStream fos2 = new FileInputStream(".\\day_09\\a.txt");
```

**2.读取字节数据**`int read()`方法,每次读取一个字节的数据提升为int类型,读到文件末尾,返回`-1`

```java
// 使用文件名称创建流对象
FileInputStream fos2 = new FileInputStream(".\\day_09\\a.txt");
int b;
while ((b = fos2.read()) != -1)
    System.out.println((char)b);

fos2.close();
```

> 1. 虽然读取了一个字节,但是会自动提升为int类型。
> 2. 流操作完毕后,调用**close**方法,必须释放系统资源。

**3.使用字节数组读取*****开发中建议的方式***】:`int read(byte[] b)` 每次读取b的长度个字节到数组中,返回读取代的**有效字节个数**,读取到末尾时,返回-1

```java
FileInputStream fos2 = new FileInputStream(".\\day_09\\a.txt");
int len;
byte[] b = new byte[7];
while ((len = fos2.read(b)) != -1) {
    System.out.println(new String(b).substring(0, len));
    // 也可以使用 new String(b, 0, len);
}

fos2.close();
```

> 使用数组读取,每次读取多少个字节,减少了系间的IO操作次数,从而提高了读写效率,建议开发中使用

*复制图片案例,带有多线程的进度条*

```java
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Random;

// Courier进行数据传递
class Courier {
    long size;
    long receive;
    public Courier(long size){
        this.size = size;
        receive = 0;
    }
}

public class Main {
    public static void main(String[] args) throws IOException, InterruptedException {

        int len; // 记录返回的字节大小。
        byte[] temp = new byte[1000]; // 记录字节数据。
        Random random = new Random();

        // 使用文件名称创建流对象
        File file = new File(".\\day_09\\feifei.jpg");
        FileInputStream in = new FileInputStream(file);
        FileOutputStream out = new FileOutputStream(".\\day_09\\newfeifei.jpg", true);

        // new 一个数据传递对象。
        Courier courier = new Courier(file.length());

        Runnable r = () -> {
            Courier cour = courier;
            int n = 25; // 进度条的长度
            while (true) {
                long computd = courier.receive;
                long exists = computd * n / courier.size;
                long i = 0;
                System.out.print("[");
                for(;i <= exists; i++) System.out.print(">");
                for(;i < n; i++) System.out.print("-");

                System.out.printf("]%5.2f%%",(computd * 1.0 / courier.size) * 100.0);
                if(courier.receive == courier.size) {
                    System.out.println("\n传输完毕");
                    break;
                }
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.print("\r");
            }
            
        };


        // 开启线程
        new Thread(r,"进度条").start();

        // 写入数据
        while ((len = in.read(temp)) != -1) {
            out.write(temp, 0, len);
            courier.receive += len;
            Thread.sleep(random.nextInt(100));
        }

    }
}
```
# 转换流

> 注意:JVM内部使用的编码方式为默认编码方式为系统的编码方式(当然可以自己设置),但是在IEDA中,会把默认当前运行的JVM的编码方式改成UTF-8!
> 而python解释器在内存中是使用的Unicode编码!

### 修改JVM默认编码方式!
1. 在系统的环境变量中添加一个变量,名为: JAVA_TOOL_OPTIONS, 值为:-Dfile.encoding=UTF-8
2. 在运行java程序的时候指定参数java -Dfile.encoding=UTF-8 Test【**注意:第2种方式每次运行时都需要指定**
3. 如果是web工程,可以在catalina.bat的最前面加入set JAVA_OPTS=-Dfile.encoding=UTF-8;设置了编码后使用java -help可能会出现乱码,这个时候可以使用-Duser.language=en设置一下语言


## 一、字符编码

- **字符编码`Character Encoding`**:一套自然语言与二进制数之间的对应规则。

> @@@@@@@@@@@@@@@@@@@@@@@@@@@   <---decoded解码---   1010101010
> 【**数字、英文、标点符号、汉字**】   (**编码规则**)    【**底层二进制**
> @@@@@@@@@@@@@@@@@@@@@@@@@@@   ---encoding编码--->  1010101010
> *如果解码的规则和编码的规则不同,就会出现乱码的现象!*

## 二、字符集

- **字符集`Charset`**: 编码表。系统支持的所有字符的集合,包括各国国家文字、标点符号、图形符号、数字等。

> 常见的字符集:ASCII、GBK、Unicode、UTF-8等。

@@@@@@@@@@@@@@@@@@@@@@@@@ 字符集与编码规则

- **ASCII:(*不超过1字节*)**
    - 基于拉丁字母、用于显示英文、包含控制字符(回车等)、显示字符
    - 基本的ASCII使用7bits表示一个字符(128个字符),拓展的使用8bits(256个字符)。

- **Unicode:(*统一4字节编码*)**
    - 标准万国码,国际标准、有三种优化编码方案:UTF-8【常用】、UTF-16、UTF-32
    - **UTF-8(*1-4个字节*):**: 一个压缩版的Unicode(*空间压缩,使用1-4个字节为每个字符编码,节省空间,根据编码规则及编码头的数字可以准确判断是对应的多少字节*)
        - 编码规则:
            - 128个US-ASCII字符:1个字节编码
            - 拉丁文等:2个字节编码
            - 大部分常用字(包含**中文**):3个字节编码
            - 极少使用的Unicode辅助字符:4个字节编码
- **GB(xxx)**:为中文显示而设计的
    - GB2312:简体中文码表。一个小于127的字符的意义与原来相同。但两个大于127的字符连在一起时,就表示一个汉字,这样大约可以组合了包含7000多个简体汉字,此外数学符号、罗马希腊的字母、日文的假名们都编进去了,连在ASCII里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的"全角"字符,而原来在127号以下的那些就叫"半角"字符了。
    - **GBK**: 最常用的(**一个汉字两个字节**),扩展了GB2312,双字节编码方法,共收录了21003个汉字,完全兼容GB2312标准,同时支持繁体汉字以及日韩汉字等
    - **GB18030** 最新的中文码表,收录汉字70244个,采用多字节编码,每个字可以由1个、2个或4个字节组成。支持中国国内少数民族的文字,同时支持繁体汉字以及日韩汉字等。
- ISO-8859-1:拉丁码表、Latin-1、欧洲使用、一个字节编码,兼容ASCII

## 三、InputStreamReader

回顾一下之前类的关系:

|抽象类|InputStream|OutputStream|Reader|Writer|
---|---|---|---|---|
抽象类的实现子类|FileInputStream|FileOutputStream|FileReaderInputStreamReader(InputStream 字节流到字符流的桥梁)|FileWriter、OutputStreamWriter(OutputStream 字符流到字节流的桥梁)
对应的增强类(无继承关系)|BufferedInputStream|BufferedOutputStream|BufferedReader|BufferedWriter

> 转换流`java.io.InputStreamReader``Reader`的子类、**字节流** **字符流** 的桥梁,读取字节,使用指定的字符集将其解码为字符。可指定名称、可接受系统默认编码方式。

### 1.构造方法(省略public)
- `InputStreamReader(InputStream in)` : 创建一个使用默认字符集的字符流,使用系统的编码方式。
- `InputStreamReader(InputStream in, String charsetName)` :创建一个指定字符集的字符流。
    - > InputStream抽象接口一般肯定是使用FileInputStream!

- 案例:使用指定字符集读取文件!`BufferedReader` 结合 `InputStreamReader` 再结合 `FileInputStream` 使用

```java
/**@指定字符级
* BufferedReader(需要传入Reader -- InputStreamReader)
* InputStreamReaderReader的子类,
* InputStreamReader(需要传入InputStream -- FileInputStream)
*/
InputStreamReader isr = new InputStreamReader(new FileInputStream("F:\\test\\GBK.txt"), "GBK");
BufferedReader br = new BufferedReader(isr);
System.out.println(br.readLine());
br.close();
isr.close();

// 我是GBK编码的,哈哈,必须的使用GBK编码方式才能读出来知道不!
```

## 四、OutputStreamWriter

> 转换流 `java.io.OutputStreamWriter` ,Writer子类,字符流到字节流的桥梁,可使用操作系统默认字符集将字符编码为字节。字符集也可以由名称指定。

### 1.构造方法(省略public)
- `OutputStreamWriter(OutputStream out)` : 创建一个使用默认字符集的字符流
- `OutputStreamWriter(OutputStream out, String charsetName)`: 创建一个指定字符集的字符流

**使用指定编码写出**

```java
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("F:\\test\\UTF-8.txt"), "UTF-8");
BufferedWriter bw = new BufferedWriter(osw);
osw.write("UTF-8编码方式");
osw.close();
osw.close();
```

转换流是字节与字符间的桥梁

@@@@@@@@@@@@@@@@@@@@@@@@@@@ 转换流是字节与字符间的桥梁

将GBK编码的文件转换为UTF-8的文本文件

```java
// GBK编码内容:我是GBK编码的,哈哈,必须得使用GBK编码方式才能读出来知道不!
String line;
InputStreamReader isr = new InputStreamReader(new FileInputStream("F:\\test\\GBK.txt"), "GBK");
BufferedReader br = new BufferedReader(isr);
line = br.readLine();
br.close();
isr.close();

OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("F:\\test\\UTF-8.txt"), "UTF-8");
BufferedWriter bw = new BufferedWriter(osw);
osw.write(line);
osw.close();
osw.close();
```


IDEA中,JVM运行使用的编码方式!

```
// GBK编码内容: 我
String line;
InputStreamReader isr = new InputStreamReader(new FileInputStream("F:\\test\\GBK.txt"), "GBK");
BufferedReader br = new BufferedReader(isr);
line = br.readLine();
br.close();
isr.close();

System.out.println("内容:‘" +  line + "’ 编码方式GBK  ,外存中大小2字节,JVM " +  line.getBytes().length + "字节");

// UTF-8编码内容:我
InputStreamReader isr2 = new InputStreamReader(new FileInputStream("F:\\test\\UTF-8.txt"), "UTF-8");
BufferedReader br2 = new BufferedReader(isr2);
line = br2.readLine();
br2.close();
isr2.close();

System.out.println("内容:‘" +  line + "’ 编码方式UTF-8,外存中大小3字节,JVM " +  line.getBytes().length + "字节");

/** IDEA
* 内容:编码方式GBK  ,外存中大小2字节,JVM 3字节
* 内容:编码方式UTF-8,外存中大小3字节,JVM 3字节
*/
```


中国境内的win环境下

```java
// GBK: One Character()
String line;
InputStreamReader isr = new InputStreamReader(new FileInputStream("GBK.txt"), "GBK");
BufferedReader br = new BufferedReader(isr);
line = br.readLine();
br.close();
isr.close();

System.out.println("content:" +  line + "GBK   OutMemory 2; In JVM: " +  line.getBytes().length + " bytes");

//GBK: One Character()
InputStreamReader isr2 = new InputStreamReader(new FileInputStream("UTF-8.txt"), "UTF-8");
BufferedReader br2 = new BufferedReader(isr2);
line = br2.readLine();
br2.close();
isr2.close();

System.out.println("content:" +  line + "UTF-8 OutMemory 3; In JVM: " +  line.getBytes().length + " bytes");

/*
content:GBK   OutMemory 2; In JVM: 2 bytes
content:UTF-8 OutMemory 3; In JVM: 2 bytes
*/
```
## 一、JDK之前的处理

> 实际开发中,建议使用`try...catch...finally`代码块处理异常部分。

```java
FileWriter fw = null;
try {
    fw = new FileWriter(".\\day_09\\a.txt");
    fw.write("我是一个兵,来自老百姓");
} catch (IOException e){
    e.printStackTrace();
} finally {
    try{
        if(fw != null)
            fw.close();
    } catch (IOException e){
        e.printStackTrace();
    }
   
}
```

## 二、JDK7的处理【拓展了解】

使用优化的`try-with-resource`语句,确保了每个资源在结束时关闭。

```java
try(创建流对象语句,如果多个,用';'隔开) {
    // 读写数据
} catch(IOException e)
    e.printStackTrace();
```

```java
try (FileWriter fw = new FileWriter("a.txt")){
    fw.write("哈哈");
} catch (IOException e) {
    e.printStackTrace();
}
```

## 三、JDK9的处理【拓展了解】

> JDK9中 `try-with-resource` 的改进,对于引入对象的方式,支持的更加简洁。被引入的对象,同样可以自动关闭,无需手动close

改进后的格式

```java
// final修饰的对象
final Resource re1 = new Resource("re1");
// 普通对象
Resource re2 = new Resource("re2");

try (re1;re2){
    // 读写
}
```

如下演示

```java
final FileReader fr = new FileReader("day_09\\a.txt");
FileWriter wr = new FileWriter("day_09\\b.txt");

try (fr;wr){
    int b;
    while ((b = fr.read()) != -1){
        wr.write(b);
    }
}
// 当然,也可以写catch
    try (fr;wr){
        int b;
        while ((b = fr.read()) != -1){
            wr.write(b);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
```
## Properties是啥?

> `java.util.Properties`继承于`Hashtable`,表示一个持久的属性集,使用键值对存储数据,每个键值对都是一对字符串。该类也被许多java类使用,比如获取系统属性,`System.getProperties`方法就是返回一个`Properties`对象

**1.构造方法**
- `public Properties()` :创造一个空的属性列表。

**2.基本方法(存储)**(省略public)
- `Object setProperty(String key, String value)` : 保存一对属性。
- `String getProperty(String key)` :使用此属性列表中指定的键搜索属性值。
- `Set<String> stringPropertyNames()` :获取所有键的名称的集合。

```java
// 创建
Properties pro = new Properties();

// 添加键值对
pro.setProperty("one", "a.txt");
pro.setProperty("two", "b.txt");
pro.setProperty("three", "c.txt");

System.out.println(pro);
System.out.println(pro.getProperty("one"));

Set<String> set = pro.stringPropertyNames();
```

**3.与流相关的方法**(省略public)
- `void load(InputStream inStream)` :从字节输入流中读取键值对.

参数中使用了字节输入流,通过流对象,可以关联到某文件上,这样就能够加载文本中的数据了

文本数据格式如下:

```txt
filename=a.txt
length=209385038
location=D:\a.txt
```


```java
Properties pro = new Properties();

pro.load(new FileInputStream("day_09\\a.txt"));

Set<String> set = pro.stringPropertyNames();

for (String key : set) {
    System.out.println(key + " : " +pro.getProperty(key));
}
```





















欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2