IO-字符流

字符流

说在前面:每个类的构造和方法查API即可

原则:

先创建的流后关闭,后创建的流先关闭,否则可能会报错

为什么要有字符流?

因为世界语言的多样性

学会字符流有啥用?

字符流更加便利在读字方面,字节流读出来的字节,不好进行处理,容易乱码。

特点:

  • 所有的字符流,自带(小)缓冲区,是在编解码的时候来使用的
  • 字符流的read()方法,会一次多次读入一个或者字节(看编码的符号位)。存入int中。(也是字节流和字符流的根本区别)

引例:

从文件中按每次两个字节读出中文:

   public static void main(String[] args) throws IOException {
        FileInputStream inputStream = new FileInputStream("E:\JavaProjectStation\JavaBaseHomeworkFile\123.txt");
       // 123.txt里面是“爱我中国” 
       // 按每两个字节去读出
       byte[] bytes = new byte[2];
       // byte[] bytes = new byte[1024];
        int readCount;
        while ((readCount = inputStream.read(bytes)) != -1){
            String s = new String(bytes, 0, readCount);
            System.out.println(s);
        }

        inputStream.close();
   }

// 按每两个字节去读出 输出为:������������ 一堆乱码。
// 按1024个字节读出 输出为:爱我中国

从文件中按每次两个字节读出,然后写入到另一个文件中:

// 读入到另一个文件试试
FileInputStream in = new FileInputStream("E:\JavaProjectStation\JavaBaseHomeworkFile\123.txt");
FileOutputStream out = new FileOutputStream("E:\JavaProjectStation\JavaBaseHomeworkFile\copy123.txt");

int readCount;
byte[] bytes1 = new byte[2];
while ((readCount = in.read(bytes1)) != -1){
    out.write(bytes1, 0, readCount);
}

in.close();
out.close();

// 两个文件内容一样
// 那么问题来了 读出和写入 是否都采用了utf-8的编码解码方式?

如何将字符串指定编码集,指定解码集 ?

  1. 解码:字符串用getBytes(编码格式)方法转化为一个字节数组
  2. 编码: 以字符数组创建字符串对象 new String(字符数组,编码格式)
  3. 输出字符串,或者其他操作

例子:

String str = "不要忘记你的目标";
// 默认为utf-8解码 然后放入字节数组
byte[] bytes = str.getBytes();

// 编码码 指定为:以GBK编码方式编码码。
String gbk = new String(bytes, "GBK");
System.out.println(gbk);

// 默认utf-8 GBK编码:正常输出
// 指定GBK编码,乱码

字符流 = 字节流 + 编码表,为什么要这么说?

image-20210119122332018

字符转化流:

主要就是可以指定解码和编码格式,且也只有它能

        // OutputStreamWriter(OutputStream out)
        // 创建使用默认字符编码的 OutputStreamWriter。
        FileOutputStream fileOutputStream = new FileOutputStream("b.txt");
        OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream);
        //OutputStreamWriter(OutputStream out, String charsetName)
        // 创建使用指定字符集的 OutputStreamWriter。
        //OutputStreamWriter outputStreamWriter = new OutputStreamWriter(
        //        new FileOutputStream("b.txt"),"GBK");

        //String getEncoding()
        // 返回此流使用的字符编码的名称。
        String encoding = outputStreamWriter.getEncoding();
        System.out.println(encoding);

字符简化流:

输入:

通过简化字符输入流对象将从文件读出数据并打印出来:

// 新建一个字符输入流对象
FileReader in = new FileReader("E:\JavaProjectStation\JavaBaseHomeworkFile\123.txt");
// 注意:用字符数组来存储 同样也需要while循环
char[] chars = new char[1024];
int readCount;
while ((readCount = in.read(chars)) != -1){
    System.out.println("读出内容为:" + new String(chars, 0, readCount));
}
in.close();

输出:

将字符串写入文件:

File file = new File("E:\JavaProjectStation\JavaBaseHomeworkFile\123.txt");
// 父类引用指向子类对象
Writer out = new FileWriter(file);
String str = "hello world";
// 字符输出流 不用像之前字节输出流一样,用必须一个个字节输出,或者用字节数组输出
// 直接写入字符串就行了
out.write(str);
out.close();

将字符串追加写入文件

File file = new File("E:\JavaProjectStation\JavaBaseHomeworkFile\123.txt");
Writer out = new FileWriter(file, true);
String str = "追加写入成功";
out.write(str);
out.close();

通过简化流对象将文件中读出以后输入到另一个文件中去:

// 新建一个字符输入输出流对象
Reader in = new FileReader("E:\JavaProjectStation\JavaBaseHomeworkFile\123.txt");
Writer out = new FileWriter("E:\JavaProjectStation\JavaBaseHomeworkFile\copy123.txt", true);
// 注意:用字符数组来存储 同样也需要while循环
char[] chars = new char[1024];
int readCount;
while ((readCount = in.read(chars)) != -1){
    out.write(new String(chars, 0, readCount));
}

// 关闭
in.close();
out.close();

字符缓冲流

输入:

字符缓冲输入流操作步骤

  1. 创建简化输入流
  2. 创建缓冲输入流
  3. 循环读出数据,readline时候,读完文件返回null

题:从磁盘上读取一个文本文件(如某个java源代码),分别统计出文件中英文字母

/* 
思路:
 * 1.创建缓冲字符输入流对象
 * 2.一个一个的读出来,读出来的得用int型接收
 * 3.if判断计数
 *
 * @modified By:
 */
public class Homework1 {
    public static void main(String[] args) throws IOException {
        BufferedReader in = new BufferedReader(new FileReader("E:\JavaProjectStation\JavaBaseHomeworkFile\123.txt"));

        // 存储读出的数据
        int readData;

        // 记录各种类型字符的个数
        int countLetter  = 0; // 字母
        int countSpace = 0; // 空格
        int countNumber = 0;  // 数字

        while ((readData = in.read()) != -1){
//            char readDataChar = (char) readData;
            if (Character.isLetter(readData)) {
                countLetter++;
            }
            if (Character.isDigit(readData)) {
                countNumber++;
            }
            if (Character.isWhitespace(readData)) {
                countSpace++;
            }
        }
        System.out.println("该文件中数字有" + countNumber + "个	字母有" + countLetter + "个	空格有" + countSpace);
    }
}

输出:

字符缓冲输出流操作步骤

  1. 创建简化输出流
  2. 再创建缓冲输出流
  3. write写入数据
  4. 关闭

一道题看懂:在一个磁盘的文件里保存26个英文小写字母(乱序),将他们读入内存中,进行排序,把排好顺序的数再重新追加写到磁盘的该文件中。

/*
 * 思路:
 * 1.创建字符缓冲输入流 输出流
 * 2.按字符数组读出
 * 3.char数组做个Arrays.sort排序 直接将char[]里面的字符按照ascll表排好。
 * 4.排完以后就写入。
 * @modified By:
 */
public class Homework2 {
    public static void main(String[] args) throws IOException {
        BufferedReader in = new BufferedReader(
                new FileReader("E:\JavaProjectStation\JavaBaseHomeworkFile\123.txt"));

        char[] chars = new char[1024];
        int readCount = in.read(chars);
        // Arrays.sort直接按照 字符的ascll码值排序完毕
        Arrays.sort(chars, 0, readCount);

        // 写入
        BufferedWriter out = new BufferedWriter(
                new FileWriter("E:\JavaProjectStation\JavaBaseHomeworkFile\123.txt"));
        out.write(chars, 0, 26);

        in.close();
        out.close();

    }

}

注意:

字符流在对图像操作的过程中

图像,视频文件有自己的编码。

各种流的对比:

框架:

image-20210119164157971

各种流的优缺点对比

简化流:

  1. 无法指定解码字符集。
  2. 使用简单,因为可以直接传入文件名作为构造参数

转化流

  1. 使用麻烦,得新建一个字节流对象作为构造参数

缓冲流

  1. 可以设置缓冲区大小

  2. 使用相对麻烦,得新建一个简化流对象作为构造参数,也可以用转化流

  3. 输入流有个readLine方法读出来返回一个字符串,结束返回null

字节流和字符流的区别:

  1. 字符流read()方法一次读出一个,或者多个字节。

一个困扰很深的问题:

为什么两个字节char型数组能够存储3个字节的字符 比如中文

答:utf-8解码后的汉字确实是三个字节,但是其中有8个符号位,当该数据存储形式从以字节数组转化为,字符数组时,符号位会被去掉,再拼接,组成两个字节存储于字符数组。

补充知识:utf-8是怎么取

UTF-8不同,它定义了一种“区间规则”,这种规则可以和ASCII编码保持最大程度的兼容:
它将Unicode编码为00000000-0000007F的字符,用单个字节来表示 0111 1111 = 7F
它将Unicode编码为00000080-000007FF的字符用两个字节表示
它将Unicode编码为00000800-0000FFFF的字符用3字节表示
1字节 0xxxxxxx
2字节 110xxxxx 10xxxxxx
3字节 1110xxxx 10xxxxxx 10xxxxxx

细枝末节:

  • 换行

    换行符

    • System.lineSeparator() :得到系统换行符
    • windos:" "
    • linux:" "
    • mac:" "
  • 字符流,有缓冲区的字节流都要close,或者刷新

  • utf-8变长 可以兼容ascll

其他流

数据流:

DataIn/OutputStream

创建形式:

还是包装流,包装普通字节流

为啥要有数据流:

普通的字节流无法写入很大的整数,或者小数。

数据输入流读出来的是啥玩意啊

要知道文件中,当时写入数据类型的顺序,才能正确的读出,不讲武德的读出来当然有问题。

打印流

  • 字节打印流 PrintStream

  • 字符打印流PrintWriter

特点:

  • 也是一个包装流(包装字节流)
  • 可以打印各种类型的数据
  • 无法改变数据来源,只能操作目的地
  • 能够自动刷新

字节打印流 PrintStream

方法

void print(String str): 输出任意类型的数据,

void println(String str): 输出任意类型的数据,自动写入换行操作

例子:

 PrintStream ps = new PrintStream(new FileOutputStream(("E:\JavaProjectStation\JavaBaseHomeworkFile\123.txt")));
// 就啥玩意都可以打印到这个文件中去
        ps.print(100);
        ps.print("hello world");

字节打印流 PrintStream

PrintWrite

构造方法摘要
PrintWriter(OutputStream out, boolean autoFlush) 通过现有的 OutputStream 创建新的 PrintWriter。
PrintWriter(Writer out, boolean autoFlush) 创建新 PrintWriter

例子:

PrintWriter pw = new PrintWriter(new FileWriter("E:\JavaProjectStation\JavaBaseHomeworkFile\123.txt"), true);
pw.println("ni好啊");	// 自带刷新 

自动刷新:

有自动刷新,和手动刷新两种构造器

自动刷新的触发:

打印方式只有是println printf format 才能触发刷新

标准输入输出流

重定向

输出重定向:

实现步骤:

  1. 以打印输出流包装字节输出流
  2. System.setOut(打印流对象) 将标准输出重定向
  3. 随便打印一点什么
        try( PrintStream out = new PrintStream(
                new FileOutputStream("E:\JavaProjectStation\JavaBaseHomeworkFile\Out.txt"));)
        {
            // 重定向输出到out.txt文件中
            System.setOut(out);


            // 向标准输出输出一个字符串
            System.out.println("重定向成功");

            // 向标准输出输出一个对象
            System.out.println(new ChangeOutDirection());

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }

输入重定向:

实现步骤:

  1. 以普通字节输入流创建对象
  2. System.setIn(字节输入流对象)
  3. 新建Scanner对象
  4. Scanner.useDelimiter(分隔模式)
  5. sc.hasNext() 返回值是true 意思是接下来要扫描的一行不是末尾就返回true
  6. sc.next() 读出下一个分割符之间的内容
try(
     FileInputStream fis =
             new FileInputStream("E:\JavaProjectStation\JavaBaseHomeworkFile\src\Day20\PrintHelloWorld.java");
     )
{
 // 重定向到fis输入流
 System.setIn(fis);

 //
 Scanner sc = new Scanner(System.in);

 // 增加下面一行只把回车作为分隔符  设置每次读取以什么为终点
 sc.useDelimiter("
");

 // sc只要遇到
 while (sc.hasNext()){
     System.out.println("键盘输入的内容时:" + sc.next());
 }
} catch (FileNotFoundException e) {
 e.printStackTrace();
} catch (IOException e) {
 e.printStackTrace();
}

有啥用:

将某个操作的数据,输入到日志文件中保存

序列化与反序列化流

输出对象的啥玩意?

对象信息,普通人看起来就是乱码。我看起来就是乱码。

为啥要有序列化?

实例对象的信息需要永久保存的时候,需要序列化,将信息存入磁盘。

序列化操作步骤:

  1. 创建字节流对象
  2. 包装字节流对象创建ObjectOutputStream对象
  3. 调用.writeObject()方法写入
  4. 关闭ObjectOutputStream对象
  5. 注意 写入的对象,必须实现一个接口Serializable
// 序列化
        try(
                ObjectOutputStream SerOut = new ObjectOutputStream(
                        new FileOutputStream("E:\JavaProjectStation\JavaBaseHomeworkFile\Person.tmp"))
                )
        {
            // 注意这里序列化的时候transient int ID是赋予了初值的
            SerOut.writeObject(new Person(18, "熊孩子", true, 11));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

反序列化步骤:

  1. 创建字节流对象(该文件应该存储了一个类的信息)
  2. 包装字节流对象创建ObjectInputStream对象
  3. 调用readObject方法反序列化,读出该对象的信息
  4. 输出
 // 反序列化
        try(
                ObjectInputStream deserIn = new ObjectInputStream(new FileInputStream("E:\JavaProjectStation\JavaBaseHomeworkFile\Person.tmp"));

        )
        {
            // 将反序列化生成的对象输出
            // 反序列化出来发现transient int ID; 变为了默认值 0;
            System.out.println(deserIn.readObject());


        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

transient关键字

  • 类的声明中用该关键字修饰的成员,不加入序列化。
  • 静态修饰的成员也不会序列化
  • 对象的信息被反序列化后的内容为默认值。

学了这么多流,实际开发到底怎么用的?

IO概括:

 文件的读写 ,在java中是非常常用。

 而设计者设计读写控制的时候,也整理的颇为详细,可能的结果就是,给调用者带来很大的困惑,这么多类,咋用。

 其实完全不用担心:java写文件只有三步,百变不离其宗

    1:我找到三样东西: 铲子(IO工具类); 沙子(我要读取的数据); 篓子(我要放的东西)

    2:用铲子把沙子放到篓子里

    3:我把铲子还给人家

至于我用 何种铲子,我要铲的是沙子 还是面粉,我要放到篓子还是框子里。先别管,也别上来就看那个javaIIO的类示意图   

按铲子区分:一般单一的铲子不适用,需要组合多种功能

  java中有读写字符的(reader/writer) 读写字节的(inputstream,outputstream)。自由选择

  java按照功能 还会有 filereader xmlreder imgio buffereader 等

按照沙子来看:

  字符串,byte[] , List,数据库查询的结果集

按照篓子来看

  可以放到Map String 图片 文件 xml

补充学习:

https://www.cnblogs.com/yyy-blog/p/7003693.html

talk is cheap,write and optimize my code。
原文地址:https://www.cnblogs.com/xiongzk/p/14304026.html