SE API第五天:Java IO流————字节/字符流、文件流、文件操作问题、缓冲流、对象流(序列化/反序列化)、转换流、缓冲字符流

一、Java Io

1、IO流(字节流结尾为stream----字符流结尾为read或writer)

在这里插入图片描述
例:
在这里插入图片描述
在这里插入图片描述

(1)学习方式:学习抽象父级的公共方法 学习子类流对象的创建方式

(2)流的分类
根据方向:输入流 输出流
根据操作单位:字节流 字符流

输入:用来读取数据的,是从外界到程序的方向,用于获取数据.

输出:用来写出数据的,是从程序到外界的方向,用于发送数据.

01.字节输入流(InputStream)和字节输出流(OutputStream)

(1)字节输入流InputStream:

InputStream--抽象父类--不能实例化
FileInputStream--文件字节输入流-FIS
BufferedInputStream--高效字节输入流-BIS

FIS in = new FIS(new File(路径));
FIS in = new FIS(路径)BIS in = new BIS( new FIS(new File(路径)));
BIS in = new BIS(new FIS(路径));

Java定义了两个超类(抽象类):
java.io.InputStream:所有字节输入流的超类,其中定义了读取数据的方法.因此将来不管读取的是什么设备(连接该设备的流)都有这些读取的方法,因此我们可以用相同的方法读取不同设备中的数据
java.io.OutputStream:所有字节输出流的超类,其中定义了写出数据的方法.

(2)字节输出流OutputStream:
OutputStream--抽象父类,不能实例化
FileOutputStream--文件字节输出流--FOS
BufferedOutputStream--高效字节输出流-BOS

FOS out = new FOS(new File(路径));  
FOS out = new FOS(路径);
BOS out = new BOS(new FOS(new File(路径))); 
BOS out = new BOS(new FOS(路径))

02.字符输入流Reader和字符输出流Writer

(1)字符输入流Reader:
Reader--抽象父类--不能实例化
FileReader--文件字符输入流-FR
BufferedReader--高效字符输入流-BR

FR in = new FR(new File(路径));
FR in = new FR(路径)BR in = new BR(new FR(new File(路径)))
BR in = new BR(new FR(路径));

(2)字符输出流Writer:
Writer--抽象父类,不能实例化
FileWriter--文件字符输出流--FW
BufferedWriter--高效字符输出流--BW

FW out = new FW(File/File,append/String pathname/String pathname,append);
BW out = new BW(Writer--所以传的是子类FW(上面那4));
注意:这里的append参数表示流向文件输出数据的时候是追加还是覆盖,如果不写,默认false是覆盖,如果改为true,表示追加

03.字节流和字符流的区别?

1)字节流-----------一个字节一个字节的去读取
1.字节流在操作的时候不会用到缓冲区(也就是内存)
2.字节流可用于任何类型的对象,包括二进制对象
3.字节流处理单元为1个字节,操作字节和字节数组。
InputStream是所有字节输入流的祖先,而OutputStream是所有字节输出流的祖先。
————————————————
2)字符流:
1.字符流在操作的时候会用到缓冲区
2.字符流只能处理字符或者字符串
3.字符流处理的单元为2个字节的Unicode字符,操作字符、字符数组或字符串。
注:Reader是所有读取字符串输入流的祖先,而writer是所有输出字符串的祖先。
在硬盘上的所有文件都是以字节形式存在的(图片,声音,视频),而字符值在内存中才会形成。
所以字符流是由Java虚拟机将字节转化为2个字节的Unicode字符为单位的字符而成的
!!!补充!!!- java将流按照读写单位划分为字节流与字符流.
- java.io.InputStreamOutputStream是所有字节流的超类
-java.io.ReaderWriter则是所有字符流的超类,它们和字节流的超类是平级关系.
- ReaderWriter是两个抽象类,里面规定了所有字符流都必须具备的读写字符的相关方法.
- 字符流最小读写单位为字符(char),但是底层实际还是读写字节,只是字符与字节的转换工作由字符流完成.
————————————————
————————————————
①简单点:
字节->解码->字符。
②复杂点:
若干字节->选择某种解码方式->解码->索引字符集->映射到一个字符。
————————————————
补充:
实际文本,又提出了字符流的概念,它是按虚拟机的编码来处理,也就是要进行字符集的转化,
这两个之间通过InputStreamReader,OutputStreamWriter(转换流)来关联,实际上是通过 byte[]String来关联的。

2、文件流(fos/fis):—>低级流;用来连接程序与文件之间的"管道",负责读写文件数据的流

2、文件流(fos/fis):一对低级流;用来连接程序与文件之间的"管道",负责读写文件数据的流

01.文件输出流:java.io.FileOutputStream—————>继承自OutputStream
   (1)文件输出流常用构造:————————>FileOutputStream(File file) 和 ②FileOutputStream(String path)
   (2)向文件中写入字节:——————————>void write(int d):,【内容是给定的int值对应的2进制的"低八位"】
      例:若第一次【fos.write(1);】写入1个字节,文件中的内容是1个字节对应的低八位:00000001
        若继续【fos.write(2);】  写入2个字节,文件中的内容是2个字节对应的低八位:00000010----会将之前的覆盖
   (3)写操作最终完毕后要关闭流: ———>fos.close()
   
02.文件输入流:java.io.FileInputStream——————>继承自InputStream
   (1)文件输入流常用构造:①FileInputStream(String path)
   (2)读取字节:int read()  【读取字节,返回的int值对应的2进制的低八位为读取到的字节数据】。
   (3)流使用完后要调close关闭:———>fis.close()
   
03.文件输入流、输出流综合练习题:
注:读写不同的设备java为我们准备了专门连接该设备的输入与输出流。他们都继承了InputStreamOutputStream,因此对我们来说读写操作是一样的,我们只需要在对不同设备进行读写时创建对应的流即可。

01.文件输出流:java.io.FileOutputStream——>继承自OutputStream

01.文件输出流:java.io.FileOutputStream—————>继承自OutputStream
   (1)文件输出流常用构造:————————>FileOutputStream(File file) 和 ②FileOutputStream(String path)
   (2)向文件中写入字节:——————————>void write(int d):,【内容是给定的int值对应的2进制的"低八位"】
      例:若第一次【fos.write(1);】写入1个字节,文件中的内容是1个字节对应的低八位:00000001
        若继续【fos.write(2);】  写入2个字节,文件中的内容是2个字节对应的低八位:00000010----会将之前的覆盖
   (3)写操作最终完毕后要关闭流: ———>fos.close()


练习题:
package apiday.day04._io;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
/**
 * 2、文件流(fos/fis):————>一对低级流;用来连接程序与文件之间的"管道",负责读写文件数据的流
 * 01.文件输出流:java.io.FileOutputStream—————>继承自OutputStream
 *    (1)文件输出流常用构造:————————>①FileOutputStream(File file) 和 ②FileOutputStream(String path)
 *    (2)向文件中写入字节:——————————>void write(int d):,【内容是给定的int值对应的2进制的"低八位"】
 *       例:若第一次【fos.write(1);】写入1个字节,文件中的内容是1个字节对应的低八位:00000001
 *         若继续【fos.write(2);】  写入2个字节,文件中的内容是2个字节对应的低八位:00000010----会将之前的覆盖
 *    (3)写操作最终完毕后要关闭流: ———>fos.close()
 */
public class IoFos {
    public static void main(String[] args) throws IOException {
        /** 01.文件输出流:java.io.FileOutputStream */
        /* 2、01.(1)文件输出流常用两种构造方法:①FileOutputStream(File file) 和 ②FileOutputStream(String path) */
        //2、02.(1)①:FileOutputStream(File file):
//        File file = new File("./test.dat");
//        FileOutputStream fos1 = new FileOutputStream(file);

        //2、01.(1)②:向文件fos.dat中写入一个字节
        FileOutputStream fos = new FileOutputStream("./test/fos.dat");
        /*
        void write(int d):向文件中写入1个字节,【内容是给定的int值对应的2进制的"低八位"】

        fos.write(1);
        int型整数1的2进制:           vvvvvvvv
        00000000 00000000 00000000 00000001
        fos.dat文件中的内容(低八位):  00000001

        fos.write(2);
        int型整数2的2进制:           vvvvvvvv
        00000000 00000000 00000001 00000010
        fos.dat文件中的内容(低八位):  00000010
         */
        fos.write(1);
        fos.write(2);
        System.out.println("写出完毕!");
        fos.close();//写操作最终完毕后要关闭流
    }
}

02.文件输入流:java.io.FileInputStream——>继承自InputStream

02.文件输入流:java.io.FileInputStream——————>继承自InputStream
   (1)文件输入流常用构造:①FileInputStream(String path)
   (2)读取字节:int read()  【读取字节,返回的int值对应的2进制的低八位为读取到的字节数据】。
   (3)流使用完后要调close关闭:———>fis.close()


练习题:
package apiday.day04._io;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
/**
 * 02.文件输入流:java.io.FileInputStream——————>继承自InputStream
 *    (1)文件输入流常用构造:①FileInputStream(String path)
 *    (2)读取字节:int read()  【读取字节,返回的int值对应的2进制的低八位为读取到的字节数据】。
 *    (3)流使用完后要调close关闭:———>fis.close()
 */
public class IoFIS {
    public static void main(String[] args) throws IOException {
        //02.1(2)将fos.dat文件中的数据读取回来
        FileInputStream fis = new FileInputStream("./test/fos.dat");
        /*
        int read():读取一个字节,并以int型返回。【返回的int值对应的2进制的低八位为读取到的字节数据】。
                   若返回值为int型的-1,则表示读取到了流的末尾。

        fos.dat:00000001 00000010

        int d = fis.read();
        变量d 2进制的表示:
        00000000 00000000 00000000 00000001
        |---------补24个0---------| 读取的字节

        d = fis.read();
        d:00000000 00000000 00000000 00000010
                                     读取的字节

        d = fis.read();
        d:11111111 11111111 11111111 11111111
          当读取当末尾是输出值为-1
         */
        int d = fis.read();
        System.out.println(d);//1
        d = fis.read();
        System.out.println(d);//2
        d = fis.read();
        System.out.println(d);//-1,读取到末尾,值为-1
        fis.close();//流在使用完毕后要调用close关闭
    }
}

03.文件输入流、输出流综合练习题:

package apiday.day04._io;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class Fis_Fos {
    public static void main(String[] args) throws IOException {
        /** 03.文件输入流、输出流综合练习题:*/
        //例:将a~z写入fod.dat文件中并读取成功?
        FileOutputStream fos1 = new FileOutputStream("./test/fos1.dat");
        for(int i=0;i<26;i++) {
            fos1.write(97+i);
        }
        fos1.close();

        FileInputStream fis1 = new FileInputStream("./test/fos1.dat");
        System.out.println(fis1.read());
        fis1.close();
        System.out.println("读取完毕");
    }
}

04.文件的复制两种方式:随机写/块读写

(1)文件的复制01(速度慢/毫秒级):——>随机读写(单字节读写)

01.文件的复制01(速度慢/毫秒级):——>随机读写(单字节读写)


练习题:
package Vdayio;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
//文件的复制
public class CopyDemo {
    public static void main(String[] args) throws IOException {
        long t1 = System.currentTimeMillis();
        //1.先获取要复制的文件的路径
        FileInputStream fis = new FileInputStream("./test/a/太阳.jpg");
        //2.将获取的文件复制到一个新的路径
        FileOutputStream fos = new FileOutputStream("./test/太阳_cp.jpg");
        /*
            假设太阳.jpg内容:
            01111110 11110000 00001111 10101010 00110011
            ^^^^^^^^

            int d = fis.read();
            d={00000000,00000000,00000000,01111110}

            fos.write(d);
            假设太阳_cp.jpg内容
            d={01111110}
         */
        //3.声明要复制的文件的内容:
        int d;
        //4.当文件读取到-1的时候就复制完成:
        while((d= fis.read())!=-1){//注意!——————>一定要在括号里初始化d=fis.read() 不然会成死循环!
            fos.write(d);
        }
        long t2 = System.currentTimeMillis();
        System.out.println("复制完毕,耗时:"+(t2-t1)+"毫秒");//复制完毕,耗时:2177毫秒
        //5.流在使用后要关闭:
        fis.close();
        fos.close();
    }
}

(2)文件的复制02(速度快):———>块读写(一组一组字节的读写)

02.文件的复制02(速度快):————————>块读写(一组一组字节的读写)
   块读写:通过提高每次读写的数据量,减少实际读写的次数,可以提高读写效率。
   注1:一次读多少个字节数取决于数组的长度
   注2:大多数的硬件块读写逗比随机读写性能好,尤其机械硬盘上体现最为明细。
   
   (1)块读:int read(byte[] data)
            一次性从文件中读取给定的字节数组总长度的字节量,并存入到该数组中。
            返回值为实际读取到的字节量。若返回值为-1则表示读取到了文件末尾。
            
   (2)块写:①void write(byte[] data):—————————————————————>不建议!!复制的文件会产生多余的垃圾数据
             一次性将给定的字节数组所有字节写入到文件中
           ②void write(byte[] data,int offset,int len)————> 建议!!复制的文件不会产生垃圾数据
             一次性将给定的字节数组从下标offset处开始的连续len个字节写入文件
             
   (3)补充:
         82进制: 00000000  1byte  1字节
         1024byte   1kb
         1024kb     1mb
         1024mb     1gb
         1024gb     1tb
         
   (4)内存中详细步骤:每次读取的字节数(:1个字节=00000000)为设置数组的长度,直到读到文件末尾时
                若不够数组的长度(即文件最后面的数据不够[调用.read()方法]一次性读取的字节数);
                把该不够长度的字节数在新的data中从前往后覆盖之前读取到的的字节数,最后读取到-1就结束了。
                
   (5)块读写步骤:(1)定义数组;
             (2)将数组传入:①【.write(byte[] data)-----不建议——————>会产生多余的垃圾数据
                        :②【byte[] data,int offset,int len】—————>不会产生垃圾数据



练习题:
package Vdayio;
import java.io.*;
public class CopyDemo02 {
    public static void main(String[] args) throws IOException {
    	/*
            假设:太阳.jpg内容:
            00001111 11110000 10101010 01010101 11001100 00110011
            byte[] data = new byte[4];//创建一个长度为4的字节数组
            data:[00000000,00000000,00000000,00000000]
            int len1;//记录每次读取到的字节数

            第一次调用:
            len1 = fis.read(data);
            从太阳.jpg文件中一次读取4个字节:
            00001111 11110000 10101010 01010101 11001100 00110011
            ^^^^^^^^ ^^^^^^^^ ^^^^^^^^ ^^^^^^^^
            data:{00001111,11110000,10101010,01010101}数组存入的是本次读取的数据
            len1:4 表示本次实际读取到了4个字节

            第二次调用:
            len1 = fis.read(data);
            从太阳.jpg文件中一次读取4个字节
            太阳.jpg内容:
            00001111 11110000 10101010 01010101 11001100 00110011 文件末尾了
                                                ^^^^^^^^ ^^^^^^^^ ^^^^^^^^ ^^^^^^^^
                                                实际只读取到了2个字节
            data:{11001100,00110011,10101010,01010101}数组存入的是本次读取的数据
                  |---本次新读取的--| |---上次的旧数据---|
            len:2 表示本次实际读取到了2个字节


            第三次调用:
            len = fis.read(data);
            从太阳.jpg文件中一次读取4个字节
            太阳.jpg内容:
            00001111 11110000 10101010 01010101 11001100 00110011 文件末尾了
                                                                  ^^^^^^^^ ^^^^^^^^ ^^^^^^^^ ^^^^^^^^
                                                                  没有数据可以读取了
            data:{11001100,00110011,10101010,01010101}数组没有变化
                  |----------上次的旧数据--------------|
            len:-1 表示流读取到了末尾
         */
        //1.先获取要复制的文件的路径:
        FileInputStream fis1 = new FileInputStream("./test/a/太阳.jpg");
        //2.将获取的文件复制到一个新的路径:
        FileOutputStream fos1 = new FileOutputStream("./test/a/太阳02.jpg");
        //3.定义一个可以存储10240字节即10kb的数组,表示一次可以读取10kb的数据(块读):
        byte[] data = new byte[1024*10];
        long start = System.currentTimeMillis();
        //4.每次实际读取到的字节数:
        int len1;
        //5.当文件读取到-1的时候就复制完成:
        while((len1=fis1.read(data))!=-1){
            //fos1.write(data);//①复制的文件会产生多余的垃圾数据----------------------------不建议!!
            fos1.write(data,0,len1);//②不会产生垃圾数据,将文件上的字节数写入到内存data中---建议!!
        }
        long end = System.currentTimeMillis();
        System.out.println("复制完毕,耗时:"+(start-end)+"毫秒");//复制完毕,耗时:0毫秒
        //6.流在使用后要关闭:
        fis1.close();
        fos1.close();
    }
}

05.向文件中写入文本数据(:字符串)

1、向文件中写入文本数据(字符串)
  (1)String提供了将字符串转换为一组字节的方法:byte[] getBytes(String charsetName)
     :参数指定的就是字符集。名字不区分大小写,但是拼写错误会引发异常:
      UnsupportedEncodingException
       不支持      字符集   异常
       
补充:支持中文的常见字符集有:
     (1)GBK:国标编码。英文每个字符占1个字节,中文每个字符占2个字节
     (2)UTF-8:内部是unicode编码,在这个基础上不同了少部分2进制信息作为长度描述
              英文每个字符占1字节  ;  中文每个字符占3字节



练习题:
package apiday.day04._io;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
 * 1、向文件中写入文本数据(字符串)
 * 补充:
 *     支持中文的常见字符集有:
 *             (1)GBK:国标编码。英文每个字符占1个字节,中文每个字符占2个字节
 *             (2)UTF-8:内部是unicode编码,在这个基础上不同了少部分2进制信息作为长度描述
 *                      英文每个字符占1字节  ;  中文每个字符占3字节
 *             String提供了将字符串转换为一组字节的方法:
 *             byte[] getBytes(String charsetName)
 *             参数指定的就是字符集。名字不区分大小写,但是拼写错误会引发异常:
 *             UnsupportedEncodingException
 *             不支持      字符集   异常
 */
public class WriteString {
    public static void main(String[] args) throws IOException {
        //1.向文件中写入数据
        FileOutputStream fos = new FileOutputStream("./test/fos.txt");
        //2.用UTF-8格式将字符串转换为2进制(即字节)的形式存在str数组里
        String str = "我是一个中国人,你是哪里的";
        byte[] data = str.getBytes(StandardCharsets.UTF_8);
        //3.流使用完毕后要关闭
        fos.write(data);
        System.out.println("写出完毕!");
        fos.close();
    }
}

06.文件流两种创建模式:覆盖和追加

2、文件流两种创建模式:覆盖和追加
   01.覆盖构造器:这种情况下文件输出流时如果连接的文件已经存在则会把文件之前的数据全部清除
      1)FileOutputStream(String path)
      2)FileOutputStream(File file)
      
   02.追加构造器:当第二个参数传入true时,文件流为追加模式,即:指定的文件若存在,则原有数据保留,新写入的数据会被顺序的追加到文件中
      1)FileOutputStream(String path,boolean append)
      2)FileOutputStream(File file,boolean append)
      注:——————>若想写一句追加一句,需要多次调用【.write()】


练习题:
package apiday.day04._io;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
 * 2、文件流两种创建模式:覆盖和追加
 *    01.覆盖构造器:这种情况下文件输出流时如果连接的文件已经存在则会把文件之前的数据全部清除
 *       1)FileOutputStream(String path)
 *       2)FileOutputStream(File file)
 *    02.追加构造器:当第二个参数传入true时,文件流为追加模式,即:指定的文件若存在,则原有数据保留,新写入的数据会被顺序的追加到文件中
 *       1)FileOutputStream(String path,boolean append)
 *       2)FileOutputStream(File file,boolean append)
 *       注:——————>若想写一句追加一句,需要多次调用【.write()】
 */
public class WriteString {
    public static void main(String[] args) throws IOException {
        /** 2、文件流两种创建模式:覆盖和追加 */
        /*
        文件流两种创建模式:覆盖和追加
        01.覆盖构造器:这种情况下文件输出流时如果连接的文件已经存在则会把文件之前的数据全部清除
        1)FileOutputStream(String path)
        2)FileOutputStream(File file)

        02.追加构造器:当第二个参数传入true时,文件流为追加模式,即:指定的文件若存在,则原有数据保留,新写入的数据会被顺序的追加到文件中
        1)FileOutputStream(String path,boolean append)
        2)FileOutputStream(File file,boolean append)
         */
        //例2:以下三句字符串:
        //加上true----会在之前字符串的基础追加下面输入的字符串:——————>若想写一句追加一句,需要多次调用【.write()】
        //不加true----下面的字符串则会把之前的覆盖:
        FileOutputStream fos1 = new FileOutputStream("./test/fos.txt",true);
        String str1 = "---------你好啊,我又来了";
        byte[] data1 = str1.getBytes(StandardCharsets.UTF_8);
        fos1.write(data1);

        str1 = "sfdewfwe";
        byte[] data5 = str1.getBytes(StandardCharsets.UTF_8);
        fos1.write(data5);

        str1 = "哈哈哈哈嗯嗯嗯";
        byte[] data6 = str1.getBytes(StandardCharsets.UTF_8);
        fos1.write(data6);
        System.out.println("写出完毕");
    }
}

07.将数据写入文件:实现简易记事本工具

练习题:
package apiday.day04._io;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
/**
 * 3、实现简易记事本工具
 */
public class WriteString {
    public static void main(String[] args) throws IOException {
        /**
         * 3、将数据写入文件—————————————>实现简易记事本工具
         *   要求:程序启动后,要求将控制台输入的每一行字符串都写入到文件note.txt中
         *        当在控制台单独输入exit(出口)时,程序退出
         */
        //0.创建扫描仪 并 创建可以写入的文件
        Scanner scan = new Scanner(System.in);
        System.out.println("请开始输入内容,单独输入exit退出");
        FileOutputStream fos2 = new FileOutputStream("./test/Test.txt");
        while(true){
            //1.先获取控制台输入的一行字符串
            String line = scan.nextLine();
            //2.判断用户输入的是否为exit
            if("exit".equals(line)){
                break;
            }
            //3.写入文件:①先将字符串转换为一组字节
            byte[] data2 = line.getBytes(StandardCharsets.UTF_8);
            //3.②将这组字节写入文件
            fos2.write(data2);
        }
        fos2.close();

        /*
        第(2)种方式:
        while(true){
            String line = scanner.nextLine();//扫描每一行给line
            if("exit".equals(line)){//如果输入出口则break退出
                break;
            }
            fos.write(line.getBytes(StandardCharsets.UTF_8));//用UTF-8把字符串转化为字节存在byte[]数组里
        }
         */

        /*
        //第(2)种进阶版:
        String line;//定义一个字符串line
        while(!"exit".equals(line = scanner.nextLine())){
            fos.write(line.getBytes(StandardCharsets.UTF_8));//用UTF-8把字符串转化为字节存在byte[]数组里
        }
        fos.close();//流在使用完毕后要调用close关闭
         */

        /*
        第(3)种方式:
        如果忽略大小写来比较字符串内容方法---------equalsIgnoreCase
        while(true){
            String line = scanner.nextLine();//扫描每一行给line
            //String提供了一个忽略大小写比较字符串内容的方法:equalsIgnoreCase
            if("exit".equalsIgnoreCase(line)){//如果输入出口则break退出
                break;
            }
            fos.write(line.getBytes(StandardCharsets.UTF_8));//反之用UTF-8把字符串转化为字节存在byte[]数组里
        }
         */
    }
}

08.从文件中读取文本数据:实际上是通过byte[]和String来关联。

4、从文件中读取文本数据————————>实际上是通过byte[]String来关联。
   (1)String提供了将字节数组转换为字符串的构造方法:
      ①String(byte[]data,String charsetName):
       将给定的字节数组中所有字节按照指定的字符集转换为字符串
     ②String(byte[]data,int offset,int len,String charsetName):
       将给定的字节数组从下标offset处开始的连续len个字节按照指定的字符集转换为字符串


练习题:
package apiday.day04._io;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
/**
 * 4、从文件中读取文本数据————————>实际上是通过byte[]和String来关联。
 *    (1)String提供了将字节数组转换为字符串的构造方法:
 *       ①String(byte[]data,String charsetName):
 *        将给定的字节数组中所有字节按照指定的字符集转换为字符串
 *      ②String(byte[]data,int offset,int len,String charsetName):
 *        将给定的字节数组从下标offset处开始的连续len个字节按照指定的字符集转换为字符串
 */
public class WriteString {
    public static void main(String[] args) throws IOException {
        /** 4、从文件中读取文本数据——————>实际上是通过byte[]和String来关联。 */
        FileInputStream fis = new FileInputStream("./test/fos.txt");
        byte[] data7 = new byte[1024*10];
        int len = fis.read(data7);
        System.out.println("实际读取到的字节:"+len);//实际读取到的字节:101
        /*
            String提供了将字节数组转换为字符串的构造方法:
            String(byte[]data,String charsetName)
            将给定的字节数组中所有字节按照指定的字符集转换为字符串

            String(byte[]data,int offset,int len,String charsetName)
            将给定的字节数组从下标offset处开始的连续len个字节按照指定的字符集转换为字符串
         */
        String line = new String(data7,0,len, StandardCharsets.UTF_8);
        System.out.println(line.length());//45
        System.out.println(line);//我是一个中国人,你是哪里的---------你好啊,我又来了sfdewfwe哈哈哈哈嗯嗯嗯
        fis.close();
    }
}

3、缓冲流(BufferedOutputStream):

  • 缓冲流:——————>高级流,作用是【加快读写效率】
    ①缓冲输入流:java.io.BufferedInputStream
    ②缓冲输出流:java.io.BufferedOutputStream。
  • 注1:缓冲流内部有一个字节数组,默认长度是8K。
  • 注2:缓冲流读写数据时一定是【将数据的读写方式转换为块读写】来保证读写效率.

(1)高级流 连接示意图:
在这里插入图片描述
(2)缓冲流:使用缓冲流完成文件复制操作:
在这里插入图片描述

1、java将流分为节点流和处理流:
  01.节点流:也称为低级流,是真实连接程序与另一端的“管道”,负责实际读写数据的流。
            读写一定是建立在节点流的基础上进行的。
  02.处理流:也称为高级流,过滤流。不能独立存在,必须连接在其他流上,目的是当数据流过它时
            对其数据进行某种加工处理,简化我们对数据的同等操作。
  注1:实际开发中经常会串联一组高级流最终连接到低级流上,在读写操作以流水线式的加工完成IO流操作。这个过程也称为“流的连接”
  
2、缓冲流:——————>高级流,作用是【加快读写效率】
  ①缓冲输入流:java.io.BufferedInputStream
  ②缓冲输出流:java.io.BufferedOutputStream。
  注1:缓冲流内部有一个字节数组,默认长度是8K。
  注2:缓冲流读写数据时一定是【将数据的读写方式转换为块读写】来保证读写效率.



练习题:
package apiday.day04.buffered_stream;
import java.io.*;
import java.nio.charset.StandardCharsets;
/**
 * 1、java将流分为节点流和处理流:
 *   01.节点流:也称为低级流,是真实连接程序与另一端的“管道”,负责实际读写数据的流。
 *             读写一定是建立在节点流的基础上进行的。
 *   02.处理流:也称为高级流,过滤流。不能独立存在,必须连接在其他流上,目的是当数据流过它时
 *             对其数据进行某种加工处理,简化我们对数据的同等操作。
 *   注1:实际开发中经常会串联一组高级流最终连接到低级流上,在读写操作以流水线式的加工完成IO流操作。这个过程也称为“流的连接”
 *
 * 2、缓冲流:——————>高级流,作用是【加快读写效率】
 *   ①缓冲输入流:java.io.BufferedInputStream
 *   ②缓冲输出流:java.io.BufferedOutputStream。
 *   注1:缓冲流内部有一个字节数组,默认长度是8K。
 *   注2:缓冲流读写数据时一定是【将数据的读写方式转换为块读写】来保证读写效率.
 */
public class four_Buffered_Stream_Copy {
    public static void main(String[] args) throws Exception {
        /*
        低级流:
        例:块读写方式————>文件的复制:
        //1.先获取要复制的文件的路径:
        FileInputStream fis1 = new FileInputStream("./test/a/太阳.jpg");
        //2.将获取的文件复制到一个新的路径:
        FileOutputStream fos1 = new FileOutputStream("./test/a/太阳02.jpg");
        //3.定义一个可以存储10240字节即10kb的数组,表示一次可以读取10kb的数据(块读):
        byte[] data = new byte[1024*10];
        long start = System.currentTimeMillis();
        //4.每次实际读取到的字节数:
        int len1;
        //5.当文件读取到-1的时候就复制完成:
        while((len1=fis1.read(data))!=-1){
            //fos1.write(data);//①复制的文件会产生多余的垃圾数据----------------------------不建议!!
            fos1.write(data,0,len1);//②不会产生垃圾数据,将文件上的字节数写入到内存data中---建议!!
        }
        long end = System.currentTimeMillis();
        System.out.println("复制完毕,耗时:"+(start-end)+"毫秒");//复制完毕,耗时:0毫秒
        //6.流在使用后要关闭:
        fis1.close();
        fos1.close();
         */


        /** 2、缓冲流:——————>高级流,作用是【加快读写效率】 */
        /* 用缓冲流复制文件:*/
        long t1 = System.currentTimeMillis();
        FileInputStream fis = new FileInputStream("./test/a/太阳.jpg");
        BufferedInputStream bis = new BufferedInputStream(fis);
        FileOutputStream fos = new FileOutputStream("./test/a/太阳_02.jpg");
        BufferedOutputStream bos = new BufferedOutputStream(fos);
        int d;
        while((d=bis.read())!=-1){
            bos.write(d);
        }
        long t2 = System.currentTimeMillis();
        System.out.println("复制完毕!耗时:"+(t2-t1)+"毫秒");//复制完毕!耗时:21毫秒
        bis.close();//关闭流时只需要关闭高级流即可,他会自动关闭他连接的流
        bos.close();
    }
}

01.缓冲流写出数据的缓冲区问题-----flush()

缓冲流的flush方法用于强制将缓冲区中已经缓存的数据一次性写出。

注:该方法实际上是在字节输出流的超类OutputStream上定义的,并非只有缓冲输出流有这个法。
   但是实际上只有缓冲输出流的该方法有实际意义,其他的流实现该方法的目的仅仅是为了在流连接过程中传递flush动作给缓冲输出流。



练习题:
package Vdayio;
import java.io.*;
import java.nio.charset.StandardCharsets;
/**
 * 缓冲输出流写出数据的时效性问题(缓冲区问题)
 */
public class VIdayBOS_flushDemo {
    public static void main(String[] args) throws IOException {
        /** 2、01.缓冲流写出数据的缓冲区问题-----flush() */
        FileOutputStream fos1 = new FileOutputStream("./test.bos.txt");
        BufferedOutputStream bos1 = new BufferedOutputStream(fos1);
        String line = "奥利给!";
        byte[] data = line.getBytes(StandardCharsets.UTF_8);
        //缓冲流内部默认有一个8k字节数组,写出的数据会先被存入数组中,直到数组装满才会写出
        bos.write(data);
        System.out.println("写出完毕");
        /*
        缓冲流的flush方法用于强制将缓冲区中已经缓存的数据一次性写出。
        注:该方法实际上是在字节输出流的超类OutputStream上定义的,并非只有缓冲输出流有这个方法。
           但是实际上只有缓冲输出流的该方法有实际意义,其他的流实现该方法的目的仅仅是为了在流连接
           过程中传递flush动作给缓冲输出流。
         */
        bos.flush();//冲   flush方法的作用:让缓冲输出流将其缓冲区中已经缓存的数据立即写出
        bos.close();
    }
}

4、对象流:java.io.ObjectOutputStream和ObjectInputStream

  • 对象流(高级流)--------在流连接中的作用:进行对象的序列化与反序列化。
对象流:—————————>java.io.ObjectOutputStreamObjectInputStream
 (1)①对象输入流——————>java.io.ObjectInputStream(InputStream的子类)
    ②对象输出流——————>java.io.ObjectOutputStream(OutputStream的子类)
 (2)概述:是一对高级流,在流连接中完成对象与字节的转换(即:对象序列化和反序列化操作)
 (3)这两个类存在的意义: ①有了它们我们可以轻松读写任何java对象.
                     ②用于序列化对象的操作。用于存储和读取对象的输入输出流类。
 (4)注意:当使用对象流写入或者读取对象的时候,必须保证该对象是序列化的;
         这样是为了保证对象能够正确的写入文件,并能够把对象正确的读回程序。
         
	  例:
        /*
        将一个Person对象写入文件
        ①先将Person对象转换为一组字节------序列化
        ②将字节写入文件------------------对象反序列化

        流连接:           序列化                   持久化
                           ↓                       ↓
        对象----转换为---->对象流(字节)----转换为---->文件流---->
         */

01.对象序列化(ObjectOutputStream)——>将给定的对象转换为一组字节(二进制数据流)并写出的过程

1、什么是序列化与反序列化?
①序列化:把内存中的对象通过序列化流输出到磁盘中(比如文件里),使用的流是ObjectOutputStream【把数据写出到文件】
②反序列化:通过反序列化流将磁盘中的数据恢复成对象,使用的流是ObjectInputStream【把之前写到文件里的数据读到程序中】
	注意1:一个类的对象如果想被序列化,那么这个类必须实现可序列化接口
		  实现这个接口的目的是相当于给这个类做了一个标记,标记可以序列化
	注意2:序列化时会自动生成一个UID,表示当前序列化输出的对象的版本信息
		  反序列化时会拿着当前的UID与之前序列化输出的UID做比较,一致,反序列化成功,不一致,报错
	注意3: 所以,标准操作是一次序列化对应一次反序列化
		  如果目标对象所在的类没有做任何修改,一次序列化也可以对应多次反序列化(根本原因是UID没变)


2、为什么要做序列化?
①、在分布式系统中,此时需要把对象在网络上传输,就得把对象数据转换为二进制形式,需要共享的数据的 JavaBean 对象,都得做序列化。
②、服务器钝化:如果服务器发现某些对象好久没活动了,那么服务器就会把这些内存中的对象持久化在本地磁盘文件中(Java对象转换为二进制文件);如果服务器发现某些对象需要活动时,先去内存中寻找,找不到再去磁盘文件中反序列化我们的对象数据,恢复成 Java 对象。这样能节省服务器内存。

3Java 怎么进行序列化?
①、需要做序列化的对象的类,必须实现序列化接口:Java.lang.Serializable 接口(这是一个标志接口,没有任何抽象方法),Java 中大多数类都实现了该接口,比如:StringInteger
②、底层会判断,如果当前对象是 Serializable 的实例,才允许做序列化,Java对象 instanceof Serializable 来判断。
③、在 Java 中使用对象流来完成序列化和反序列化:()
ObjectOutputStream:通过 writeObject()方法做序列化操作
ObjectInputStream:通过 readObject() 方法做反序列化操作

在这里插入图片描述

对象序列化的流连接操作原理图:
在这里插入图片描述

01.对象序列化(ObjectOutputStream)———————>将给定的对象转换为一组字节(二进制数据流)并写出的过程
   (1)对象输出流提供的序列化方法:void writeObject(Object obj)————>将一个对象obj写入到一个文件
   (2)要求:序列化时要求写出的对象所属的类必须实现可序列化接口【Serializable】,
           否则会抛出不可序列化异常:java.io.NotSerializableException
       注:Serializable:标识接口[这个接口里面什么内容都没有(来进行对象序列化和反序列化的,
          里面只有一个版本号,用来进行标志识别版本号的)];


练习题:
(0)使用当前类测试对象流的序列化和反序列化操作:
package apiday.day05.object_serialization;
import java.io.Serializable;
import java.util.Arrays;
//01.①:序列化时要求写出的对象所属的类必须实现可序列化接口(Serializable),
//     否则会抛出异常:java.io.NotSerializableException
public class Person implements Serializable {
    /*
    Serializable手册详细介绍:原文:
    如果可序列化类没有显式声明 serialVersionUID,则序列化运行时将根据类的各个方面为该类计算
    默认的 serialVersionUID 值,如 Java(TM) 对象序列化规范中所述。【但是,强烈建议所有可序
    列化的类都显式声明 serialVersionUID 值,因为默认的 serialVersionUID 计算对类细节高
    度敏感,这些细节可能因编译器实现而异,因此可能在反序列化期间导致意外的 InvalidClassExceptions。】
    因此,【为了保证在不同的 java 编译器实现中具有一致的 serialVersionUID 值,可序列化的类必须声明一
    个显式的 serialVersionUID 值。】还强烈建议显式 serialVersionUID 声明尽可能使用 private修饰符,
    因为此类声明仅适用于立即声明的类——serialVersionUID 字段不能用作继承成员。
    数组类不能显式声明 serialVersionUID,因此它们始终具有默认计算值,但数组类无需匹配 serialVersionUID 值。

    自己总结:
    实现序列化接口最好主动定义序列化版本号这个常量,这样一来对象序列化时就不会根据类的结构
    生成一个版本号,而是使用该固定值。那么反序列化时,只要还原的对象和当前类的版本号一致就可以进行还原。

    如下:声明一个序列版本号:
     */
    public static final long serialVersionUID = 42L;//只要定义一个版本号,反序列化(可以进行还原)时可以避免异常

    private String name;
    private int age;
    private String gender;//性别
    /*
    当一个属性被关键字transient修饰后,那么当进行对象序列化时,该属性的值会被忽略。
    忽略不必要的属性可以达到对象“瘦身”的目的,减少资源开销。

    未加transient运行后该Person.obj文件较大:289字节
    加了transient运行后该Person.obj文件较小:144字节

    当一个属性被关键字transient修饰后,那么当进行对象序列化时,该属性的值会被忽略。
     */
    private transient String[] otherInfo;//其他信息

    /*
    若再重新声明一个工资变量:
    此时直接执行【OOS_OIS.java】会报无效的类异常:InvalidClassExceptions
    因为对象序列化时会根据类的结构生成另一个版本号;
    当反序列化时,需要还原的对象和当前类的版本号不一致了就无法进行还原。
     */
    private int salary;//工资


    public Person(){ }
    public Person(String name, int age, String gender, String[] otherInfo) {
        this.name = name;
        this.age = age;
        this.gender = gender;
        this.otherInfo = otherInfo;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String getGender() {
        return gender;
    }
    public void setGender(String gender) {
        this.gender = gender;
    }
    public String[] getOtherInfo() {
        return otherInfo;
    }
    public void setOtherInfo(String[] otherInfo) {
        this.otherInfo = otherInfo;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", gender='" + gender + '\'' +
                ", otherInfo=" + Arrays.toString(otherInfo) +
                '}';
    }
}

(2)对象序列化:
package apiday.day05.object_serialization;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
/**
 * 01.对象序列化(ObjectOutputStream)———————>将给定的对象转换为一组字节(二进制数据流)并写出的过程
 *    (1)对象输出流提供的序列化方法:void writeObject(Object obj)————>将一个对象obj写入到一个文件
 *    (2)要求:序列化时要求写出的对象所属的类必须实现可序列化接口【Serializable】,
 *            否则会抛出不可序列化异常:java.io.NotSerializableException
 *        注:Serializable:标识接口[这个接口里面什么内容都没有(来进行对象序列化和反序列化的,
 *           里面只有一个版本号,用来进行标志识别版本号的)];
 */
public class OOS {
    public static void main(String[] args) throws IOException {
        /*
        将一个Person对象写入文件
        ①先将Person对象转换为一组字节------序列化
        ②将字节写入文件------------------对象反序列化

        流连接:           序列化                   持久化
                           ↓                       ↓
        对象----转换为---->对象流(字节)----转换为---->文件流---->
         */
        
        /** 01.对象序列化(ObjectOutputStream):将给定的对象转换为一组字节(二进制数据流)并写出的过程 */
        //①先将Person对象转换为一组字节------序列化
        String name = "苍老师";
        int age = 18;
        String gender = "女";
        String[] otherInfo = {"是一名演员", "来自霓虹", "爱好书法", "广大男性同胞的启蒙老师"};
        Person p1 = new Person(name, age, gender, otherInfo);
        System.out.println(p1);//输出p.toString()返回值 在Person.java中调用该构造方法

        //将该Person对象写入文件person.obj中:①因此程序可以实现把组件写入输出流,
        FileOutputStream fos = new FileOutputStream("./test/person.obj");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        /*
        对象输出流提供的序列化方法:void writeObject(Object obj)
        将给定的对象转换为一组字节并写出。
        但要注意:序列化时要求写出的对象所属的类必须实现可序列化接口(Serializable),
        否则会抛出异常:java.io.NotSerializableException
        */
        oos.writeObject(p1);
        System.out.println("写出完毕");
        oos.close();
    }
}

02.对象反序列化(ObjectInputStream)——>将一组字节还原为java对象(前提是这组字节是一个对象序列化得到的字节)

02.对象反序列化(ObjectInputStream)——————>将一组字节还原为java对象(前提是这组字节是一个对象序列化得到的字节)
   (1)对象输入流提供的反序列化方法:readObject()——————————————————>读取一个对象。
   (2)①:实现序列化接口最好主动定义序列化版本号这个常量,这样一来对象序列化时就不会根据类的结构生成一个版本号,
           而是使用该固定值。那么反序列化时,只要还原的对象和当前类的版本号一致就可以进行还原。
      ②:为什么进行序列化时最好主动定义序列化版本号这个常量?
         答:因为对象序列化时会根据类的结构生成另一个版本号;当反序列化时,需要还原的对象和当前类的版本号不一致了就无法进行还原。
         
   (3)transient————>让对象“瘦身”的目的,减少资源开销
      概述:当一个属性被关键字【transient】修饰后,那么当进行对象序列化时,该属性的值会被忽略。
           忽略不必要的属性可以达到对象“瘦身”的目的,减少资源开销。
      例子:【private transient String[]otherInfo;//其他信息】
          未加transient运行后该Person.obj文件较大:289字节
          加了transient运行后该Person.obj文件较小:144字节



练习题:
(1)使用当前类测试对象流的序列化和反序列化操作:
package apiday.day05.object_serialization;
import java.io.Serializable;
import java.util.Arrays;
//01.①:序列化时要求写出的对象所属的类必须实现可序列化接口(Serializable),
//     否则会抛出异常:java.io.NotSerializableException
public class Person implements Serializable {
    /*
    Serializable手册详细介绍:原文:
    如果可序列化类没有显式声明 serialVersionUID,则序列化运行时将根据类的各个方面为该类计算
    默认的 serialVersionUID 值,如 Java(TM) 对象序列化规范中所述。【但是,强烈建议所有可序
    列化的类都显式声明 serialVersionUID 值,因为默认的 serialVersionUID 计算对类细节高
    度敏感,这些细节可能因编译器实现而异,因此可能在反序列化期间导致意外的 InvalidClassExceptions。】
    因此,【为了保证在不同的 java 编译器实现中具有一致的 serialVersionUID 值,可序列化的类必须声明一
    个显式的 serialVersionUID 值。】还强烈建议显式 serialVersionUID 声明尽可能使用 private修饰符,
    因为此类声明仅适用于立即声明的类——serialVersionUID 字段不能用作继承成员。
    数组类不能显式声明 serialVersionUID,因此它们始终具有默认计算值,但数组类无需匹配 serialVersionUID 值。

    自己总结:
    实现序列化接口最好主动定义序列化版本号这个常量,这样一来对象序列化时就不会根据类的结构
    生成一个版本号,而是使用该固定值。那么反序列化时,只要还原的对象和当前类的版本号一致就可以进行还原。

    如下:声明一个序列版本号:
     */
    public static final long serialVersionUID = 42L;//只要定义一个版本号,反序列化(可以进行还原)时可以避免异常

    private String name;
    private int age;
    private String gender;//性别
    /*
    当一个属性被关键字transient修饰后,那么当进行对象序列化时,该属性的值会被忽略。
    忽略不必要的属性可以达到对象“瘦身”的目的,减少资源开销。

    未加transient运行后该Person.obj文件较大:289字节
    加了transient运行后该Person.obj文件较小:144字节

    当一个属性被关键字transient修饰后,那么当进行对象序列化时,该属性的值会被忽略。
     */
    private transient String[] otherInfo;//其他信息

    /*
    若再重新声明一个工资变量:
    此时直接执行【OOS_OIS.java】会报无效的类异常:InvalidClassExceptions
    因为对象序列化时会根据类的结构生成另一个版本号;
    当反序列化时,需要还原的对象和当前类的版本号不一致了就无法进行还原。
     */
    private int salary;//工资


    public Person(){ }
    public Person(String name, int age, String gender, String[] otherInfo) {
        this.name = name;
        this.age = age;
        this.gender = gender;
        this.otherInfo = otherInfo;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String getGender() {
        return gender;
    }
    public void setGender(String gender) {
        this.gender = gender;
    }
    public String[] getOtherInfo() {
        return otherInfo;
    }
    public void setOtherInfo(String[] otherInfo) {
        this.otherInfo = otherInfo;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", gender='" + gender + '\'' +
                ", otherInfo=" + Arrays.toString(otherInfo) +
                '}';
    }
}

(2)对象序列化:
package apiday.day05.object_serialization;
import java.io.*;
/**
 * 02.对象反序列化(ObjectInputStream)——————>将一组字节还原为java对象(前提是这组字节是一个对象序列化得到的字节)
 *    (1)对象输入流提供的反序列化方法:readObject()——————————————————>读取一个对象。
 *    (2)①:实现序列化接口最好主动定义序列化版本号这个常量,这样一来对象序列化时就不会根据类的结构生成一个版本号,
 *            而是使用该固定值。那么反序列化时,只要还原的对象和当前类的版本号一致就可以进行还原。
 *       ②:为什么进行序列化时最好主动定义序列化版本号这个常量?
 *          答:因为对象序列化时会根据类的结构生成另一个版本号;当反序列化时,需要还原的对象和当前类的版本号不一致了就无法进行还原。
 *    (3)transient————>让对象“瘦身”的目的,减少资源开销
 *       概述:当一个属性被关键字【transient】修饰后,那么当进行对象序列化时,该属性的值会被忽略。
 *            忽略不必要的属性可以达到对象“瘦身”的目的,减少资源开销。
 *       例子:【private transient String[]otherInfo;//其他信息】
 *           未加transient运行后该Person.obj文件较大:289字节
 *           加了transient运行后该Person.obj文件较小:144字节
 */
public class OIS {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        /** 02.对象反序列化(ObjectInputStream):将一组字节还原为java对象(前提是这组字节是一个对象序列化得到的字节) */
        FileInputStream fis = new FileInputStream("./test/Person.obj");
        ObjectInputStream ois = new ObjectInputStream(fis);

        Person p2 = (Person) ois.readObject();
        System.out.println(p2);
        ois.close();
    }
}

5、转换流(OSW/ISR):是一对常用的字符流实现类:

java IO将流按照读写单位划分为字节流(stream结尾的)和字符流(read和writer结尾)
一、字节流:——————>stream结尾的
java.io.InputStreamOutStream是所有字节输入/输出流的超类,读写最小单位是字节

二、字符流(character  stream):——————>read和writer结尾
java.io.ReaderWriter则是所有字符输入流和输出流的超类,它们是平级关系,读写单位最小为字符。
:
- java将流按照读写单位划分为字节流与字符流.
- java.io.InputStreamOutputStream是所有字节流的超类
-java.io.ReaderWriter则是所有字符流的超类,它们和字节流的超类是平级关系.
- ReaderWriter是两个抽象类,里面规定了所有字符流都必须具备的读写字符的相关方法.
- 字符流最小读写单位为字符(char),但是底层实际还是读写字节,只是字符与字节的转换工作由字符流完成.


1、转换流(OSW/ISR):——————>是一对常用的字符流实现类:
01.java.io.OutputStreamWriter(OSW):高级转换流;创建转换流时,可以通过第二个参数来指定字符集【new OutputStreamWriter(fos,StandardCharsets.UTF_8);(1).write()方法:——————>可以直接写出字符流,无需再手动转换为字节
02.java.io.InputStreamReader(ISR):高级转换流;创建转换流时,可以通过第二个参数来指定字符集【new InputStreamReader(fis,StandardCharsets.UTF_8);(1)int read()方法:————>读取字符,返回的int值对应2进制的"低16位"为读取到的字符数据(char);
                          如果int的值为-1,表示读取到了末尾
注1:实际应用中我们不直接操作这一对流,但是在读写文本数据而组件流连接时他们是非常重要的一环。
注2:作用:①在流连接中衔接其他高级字符流与下面的字节流(这也是转换流名字的由来)。
         ②负责将字符与对应的字节按照指定的字符集自动转换方便读写操作。
因此注意:字符流仅适合读写文本数据(字符或字符串)。读写文件仅读写文本文件。
       字符流底层本质还是读写字节,只是字符与字节的转换由字符流自行完成。

转换流的意义:
实际开发中我们还有功能更好用的字符高级流.但是其他的字符高级流都有一个共通点:
不能直接连接在字节流上.而实际操作设备的流都是低级流同时也都是字节流.因此不能
直接在流连接中串联起来.转换流是一对可以连接在字节流上的字符流,其他的高级字符
流可以连接在转换流上.在流连接中起到"转换器"的作用(负责字符与字节的实际转换)

01.转换字符输出流:java.io.OutputStreamWriter(OSW)

使用转换输出流向文件中写入文本数据:
在这里插入图片描述

package apiday.day05.character_stream;
import java.io.*;
import java.nio.charset.StandardCharsets;
/**
 * 1、转换流(OSW/ISR):——————>是一对常用的字符流实现类:
 * 01.java.io.OutputStreamWriter(OSW):高级转换流;创建转换流时,可以通过第二个参数来指定字符集:
 *                                    【new OutputStreamWriter(fos,StandardCharsets.UTF_8);】
 *    (1).write()方法:——————>可以直接写出字符流,无需再手动转换为字节
 */
public class OSW {
    public static void main(String[] args) throws IOException {
        /** 1、01.OutStreamWriter:高级转换流 */
        //例:使用转换输出流向文件中写入文本数据:
        FileOutputStream fos = new FileOutputStream("./test/osw.txt");
        //创建转换流时,可以通过第二个参数来制定字符集
        OutputStreamWriter osw = new OutputStreamWriter(fos, StandardCharsets.UTF_8);
        String line = "铃铛~铃铛,响不响";
//        byte[] data = line.getBytes(StandardCharsets.UTF_8);
//        fos.write(data);

        //字符流的write方法可以直接写出字符流,无需再手动转换为字节
        osw.write(line);
        System.out.println("写出完毕");
        osw.close();  
    }
}

02.转换字符输入流:java.io.InputStreamReader(ISR)

使用转换流测试读取文本数据:

package apiday.day05.character_stream;
import java.io.*;
import java.nio.charset.StandardCharsets;
/**
 * 02.java.io.InputStreamReader(ISR):高级转换流;创建转换流时,可以通过第二个参数来指定字符集【new InputStreamReader(fis,StandardCharsets.UTF_8);】
 *    (1)int read()方法:————>读取字符,返回的int值对应2进制的"低16位"为读取到的字符数据(char值);
 *                           如果int的值为-1,表示读取到了末尾
 * 注1:实际应用中我们不直接操作这一对流,但是在读写文本数据而组件流连接时他们是非常重要的一环。
 * 注2:作用:①在流连接中衔接其他高级字符流与下面的字节流(这也是转换流名字的由来)。
 *          ②负责将字符与对应的字节按照指定的字符集自动转换方便读写操作。
 */
public class ISR {
    public static void main(String[] args) throws IOException {
        //例:使用转换流测试读取文本数据:将osw.txt中的所有内容读取出来并输出到控制台
        FileInputStream fis = new FileInputStream("./test/osw.txt");
        InputStreamReader isr = new InputStreamReader(fis,StandardCharsets.UTF_8);
        /*
        字符流:int read()
        读取一个字符,返回的int值对应2进制的"低16位"为读取到的字符数据(char值);
        00000000 00000000 10101010 10101010
        如果int的值为-1,表示读取到了末尾
         */
        int d;
        while((d=isr.read())!=-1){
            System.out.println((char) d);
        }
        isr.close();
    }
}

6、缓冲字符流(br/bw/pw):是一对高级的字符流,作用是块写文本数据加速

2、缓冲字符流(br/bw/pw)————————>是一对高级的字符流,作用是块写文本数据加速
   (1)缓冲字符流内部维护一个数组,可以块读写文本数据来进行读写性能的提升。
   (2)缓冲字符流内部也有一个缓冲区,读写文本数据以块读写形式加快效率.并且缓
      冲流有一个特别的功能:可以按行读写文本数据.
   
01.(1)java.io.BufferedReader:高级的字符流 ; 块读文本数据,并且可以按行读取字符串。
      ①String readLine():连续读取若干字符,到换行符为止,然后将换行符之前的内容以一个字符串形式返回。
        注1:返回的字符串中不含有最后的换行符。如果单独读取了空行(此行内容只有一个换行符,比如当前源代码中的第二行就是空行),那么会返回一个空字符串。
        注2:当方法返回值为null时,表示流读取到了末尾。
        
02.(1)java.io.BufferedWriter:高级的字符流;PrintWriter包含在内。-------------------不是很常用!
   (2)java.io.PrintWriter:具有自动行刷新功能的缓冲字符输出流,【内部总是连接BufferWriter------更常用!!!】
      ①例:自行完成流连接创建PrintWriter
      ②例:完成简易记事本
        注:【equals:区分大小写<——————>equalsIgnoreCase:不区分大小写】

在这里插入图片描述

01.缓冲字符输入流:java.io.BufferedReader:根据读取的路径不同可以读取任意的程序源代码输出到控制台上

------高级的字符流 ; 块读文本数据,并且可以按行读取字符串。

01.(1)java.io.BufferedReader:高级的字符流 ; 块读文本数据,并且可以按行读取字符串。
      ①String readLine():连续读取若干字符,到换行符为止,然后将换行符之前的内容以一个字符串形式返回。
        注1(1)这是内存操作,因为第一次调用readLine时,缓冲流会将数据先一次性读取到内部的char数组中(8k的字符)(2)返回的字符串中不含有最后的换行符。如果单独读取了空行(此行内容只有一个换行
                符,比如当前源代码中的第二行就是空行),那么会返回一个空字符串。
        注2:当方法返回值为null时,表示流读取到了末尾。



练习题:《根据读取的路径不同可以读取任意的程序源代码输出到控制台上》:
package apiday.day05.character_stream;
import java.io.*;
/**
 * 2、缓冲字符流(br/bw/pw)————————>是一对高级的字符流,作用是块写文本数据加速
 *    :缓冲字符流内部维护一个数组,可以块读写文本数据来进行读写性能的提升。
 * 01.(1)缓冲字符输入流:java.io.BufferedReader:——————>高级的字符流 ; 块读文本数据,并且可以按行读取字符串。
 *       ①String readLine():连续读取若干字符,到换行符为止,然后将换行符之前的内容以一个字符串形式返回。
 *         注1:返回的字符串中不含有最后的换行符。如果单独读取了空行(此行内容只有一个换行符,比如当前源代码中的第二行就是空行),那么会返回一个空字符串。
 *         注2:当方法返回值为null时,表示流读取到了末尾。
 */
public class BR {//————————>《根据读取的路径不同可以读取任意的程序源代码输出到控制台上》:
    public static void main(String[] args) throws IOException {
        /** 01.(1)缓冲字符输入流:java.io.BufferedReader:高级的字符流 ; 块读文本数据,并且可以按行读取字符串。 */
        //例:读取本程序(BR.java)的源代码输出到控制台上:
        //文件字节输入流,低级流,字节流。功能:从文件中读取字节:
        FileInputStream fis = new FileInputStream("./src/apiday/day05/character_stream/BR.java");
        //转换输入流,高级流,字符流。功能:1衔接字节与其他字符流  2:将读取的字节转换为字符
        InputStreamReader isr = new InputStreamReader(fis);
        //缓冲字符输入流,高级流,字符流。功能:1块读文本数据加速 2:按行读取字符串
        BufferedReader br = new BufferedReader(isr);
        
        /*
        BufferedReader提供了一个读取一行字符串的方法:
      【String readLine()】:该方法会连续读取若干字符,当遇到换行符停止,然后将换行符之前的内容以一个字符串形式返回。
        注:这是内存操作,因为第一次调用readLine时,缓冲流会将数据先一次性读取到内部的char数组中(8k的字符)。
           返回的字符串中不含有最后的换行符。如果单独读取了空行(此行内容只有一个换行符,比如当前源代码中的第二行就是空行),那么会返回一个空字符串。
           如果流读取到了末尾,会返回null
         */
        //(1)------>只输出这两句就调用.close()会只读package包名那一行:
//        String line = br.readLine();
//        System.out.println(line);

        //(2)------>如下返回值!=null时,会在控制台读取当前程序的所有源代码。
        String line;//实际读取到的字符串
        while((line= br.readLine())!=null){
            System.out.println(line);//控制台输出此【BR.java】所有源代码!
        }

        br.close();
    }
}

02.缓冲字符输出流(BufferedWriter/PrintWriter: 带自动行刷新):

(1)缓冲字符输出流:java.io.PrintWriter:
java.io.PrintWriter是具有自动行刷新功能的缓冲字符输出流,内部总是连接BufferedWriter;实际开发中缓冲字符输出流我们都使用它。
②缓冲字符流内部也有一个缓冲区,读写文本数据以块读写形式加快效率.并且缓冲流有一个特别的功能:可以按行读写文本数据.

(2)PrintWriter特点:
1:可以按行读写字符串
2:可以自动行刷新
3:可以提高写出字符的效率(实际由内部连接的BufferedWriter完整)

在这里插入图片描述
在这里插入图片描述

02.(1)java.io.BufferedWriter:高级的字符流;PrintWriter包含在内。-------------------不是很常用!
   (2)java.io.PrintWriter:具有自动行刷新功能的缓冲字符输出流,【内部总是连接BufferWriter------更常用!!!】
      ①例:连续调用.println()写入文本数据
      ②例:在流连接中使用PW
      ③例:PrintWriter的自动行刷新功能---完成简易记事本:
           如果实例化PW时第一个参数传入的是一个流,则此时可以再传入一个boolean型的参数,此值为true时就打开了自动行刷新功能。
           即:每当我们用PW的println方法写出一行字符串后会自动flush.
        注:【equals:区分大小写<——————>equalsIgnoreCase:不区分大小写】



练习题:
package apiday.day05.character_stream;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
/**
 * 02.(1)java.io.BufferedWriter:高级的字符流;被PrintWriter包含在内。-------------------不是很常用!
 *    (2)java.io.PrintWriter:具有自动行刷新功能的缓冲字符输出流,【内部总是连接BufferWriter------更常用!!!】
 *       ①例:连续调用.println()写入文本数据
 *       ②例:在流连接中使用PW
 *       ③例:PrintWriter的自动行刷新功能---完成简易记事本:
 *            如果实例化PW时第一个参数传入的是一个流,则此时可以再传入一个boolean型的参数,此值为true时就打开了自动行刷新功能。
 *            即:每当我们用PW的println方法写出一行字符串后会自动flush.
 *         注:【equals:区分大小写<——————>equalsIgnoreCase:不区分大小写】
 */
public class PW {
    public static void main(String[] args) throws IOException {
        /** 2、02.(2)java.io.PrintWriter:具有自动行刷新功能的缓冲字符输出流,【内部总是连接BufferWriter------更常用!!!】 */

        /** ①例:连续调用.println()写入文本数据---向文件pw.txt中写入文本数据*/
        /*
        PrintWriter提供了直接对文件做操作的构造器
        PrintWriter(String fileName)
        PrintWriter(File file)
         */
        PrintWriter pw = new PrintWriter("./test/pw.txt","UTF-8");
        pw.println("你是风儿~我是沙");
        pw.println("你是月亮~我是沙");
        System.out.println("写出完毕");
        pw.close();



        /** ②例:在流连接中使用PW */
        //文件字节输出流(是一个低级流),向文件中写入字节数据
        FileOutputStream fos1 = new FileOutputStream("./test/pw2.txt",true);
        //转换输出流(是一个高级流,且是一个字符流)。1:衔接字符与字节流 2:将写出的字符转换为字节
        //创建转换流时指定字符集
        OutputStreamWriter osw1 = new OutputStreamWriter(fos1, StandardCharsets.UTF_8);
        //缓冲输出流(是一个高级流,且是一个字符流)。块写文本数据加速
        BufferedWriter bw1 = new BufferedWriter(osw1);
         /*
        PrintWriter提供的构造器中,当第一个参数为一个流时,就支持再传入一个boolean
        型的参数表示是否自动行刷新。当该值为true时则打开了自动行刷新功能。这就意味着
        每当我们调用println方法后会自动flush一次。
         */
        //具有自动行刷新的缓冲字符输出流:
        PrintWriter pw1 = new PrintWriter(bw1,true);//autoFlush:true————>[相当于pw.flush();自动行刷新功能]
        pw1.println("你好啊哥哥们,我是***,你是谁啊0");
        pw1.println("....hhh 不告诉你");
        System.out.println();
        pw1.close();



        /** ③:PrintWriter的自动行刷新功能---完成简易记事本。控制台输入的每行字符串都按行写入文件。单独输出exit时退出。*/
        PrintWriter pw2 =new PrintWriter("./test/pw.txtt");
        Scanner scanner = new Scanner(System.in);
        while(true){
            String line2 = scanner.nextLine();
//            if("exit".equals(line2)){//————————————>equals:区分大小写
            if("exit".equalsIgnoreCase(line2)){//————>equalsIgnoreCase:不区分大小写
                break;
            }
            pw2.println(line2);//连续追加字符串
//            pw.flush();//自动行刷新功能
        }
        pw2.close();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值