IO流(高级流)

IO体系结构

image-20240525211131785

一,缓冲流

1.1 缓冲流体系结构

image

1.2 字节缓冲流

原理:底层自带了长度为8kb的缓冲区提高性能。利用缓冲区可以一次读取8kb数据提高了缓冲效率。

构造方法:

方法名 说明
public BufferedInputStream(InputStream is) 把基本流装成高级流,提高读取数据的性能
public BufferedOutputStream(OutputStream os) 把基本流装成高级流,提高写出数据的性能

缓冲流本身是不能直接操作文件中的数据的,是在创建对象的时候关联了基本流,真正的读写数据的还是被关联的那个基本流。只不过通过缓冲流让这个基本流读写效率更高了。

这个构造方法的参数是抽象类,使用字节输入流字节输出流对应的实现类用多态即可。

字节缓冲流的写入和读取都是和字节流一模一样的,只是额外提供了读取性能而已。

  • 字节缓冲流读写原理

    缓冲流实际上还是由基本流进行数据的读取的。

    但是基本流会一次性读取8192个数据交给缓冲流中的缓冲区。

    缓冲输入流的缓冲区和缓冲输出流的缓冲区不是同一个缓冲区,而是各自有一个缓冲区。

    当右边的缓冲区读取完了8192个数据,左边的缓冲区就会继续读取8192个数据。

    变量b就是来回搬运数据的。

    image

    虽然两个缓冲区之间也是1个字节1个字节的交换,但是这是在内存中进行的交换!内存的运输速度极快。

    缓冲流节约的是内存和硬盘之间交互浪费的时间。

    当然变量b也可也是数组b,如果使用数组的话,就一次性一共数组的最大长度进行交换缓冲区间数据了。速度更加快了。

1.3 字符缓冲流

原理:底层自带了长度为8kb的缓冲区提高性能。但是字符流的底层本身就带了8kb的缓冲区,故提高的效率不是很明显。

字符缓冲流构造方法:

方法名 说明
public BufferedReader(Reader r) 把基本流变为高级流
public BufferedWriter(Writer w) 把基本流变为高级流

字符缓冲输入流特有的方法

方法名 说明
public String readLine() 读取一行数据,如果没有数据可读了,返回null

遇到 \r \n的时候才会停止读取。即这个虽然一次读取一行数据,但是不会读取到回车换行!

字符缓冲输出流特有的方法

方法名 说明
public void newLine() 跨平台的换行

因为不同的平台换行符号是不一样的。

细节 :缓冲流的续写功能是在Writer里面开启的!BufferedWriter是没有续写功能的!!!

二,转换流

转化流是字符流的高级流。属于字符流。

转换流是字符流和字节流的桥梁。

  • InputStreamReader:

    • 解决不同编码时,字符流读取文本内容乱码的问题。
    • 解决思路:先获取文件的原始字节流,再将其按真实的字符集编码转成字符输入流,这样字符输入流中的字符就不乱码了,
  • OutputStreamWriter:

    • 作用:可以控制写出去的字符使用什么字符集编码
    • 解决思路:获取字节输出流,再按照指定的字符集编码将其转换成字符输出流,以后写出去的字符就会用该字符集编码了

构造方法:

方法名 说明
public InputStreamReader(InputStream e,String charsetName) 创建对象并指定字符编码
public OutputStreamWriter(OutputStream e,String charSetName) 创建对象并指定字符编码

注:转换流虽然可以指定字符编码,但是在jdk11的时候可以直接使用字符流进行替代了。

三,数据流

数据流属于字节流的一部分,他们是高级流,用来对数据和数据的类型进行写入和读取操作的。

3.1 数据输入流

image-20240525211054599

3.2 数据输出流

image-20240525211008578

四,序列化流

序列化流是字节流的高级流,属于字节流。

分别为:

  • ObjectInputStream(反序列化流)
  • ObjectOutputStream(序列化流)

4.1 ObjectOutputStream(序列化流)

作用:可以把java中的对象写到本地文件中。

写出的数据有个特点:人无法读取,需要通过反序列化流来读取。

构造方法:

构造方法 说明
public ObjectOutputStream(OutputStream out) 把基本流包装成高级流

方法:

方法名 说明
public final void writeObject(Object obj) 把对象序列化(写出去)到文件中

小细节:如果直接使用序列化流去把对象序列化会报异常:NotSerializableException

  • 解决方法:需要让被序列化的那个javabean实现Serializable接口

注:Serializable这个接口是没有抽象方法的,这种接口叫标记接口,一旦实现了这种接口,那么就表示当前的类可以被序列化。

如果需要指定字段不进行序列化就在对应的属性前面加上关键字transient

4.2 ObjectInputStream(反序列化流)

作用:把序列化到本地文件中的对象,读取到程序中。

构造方法:

构造方法 说明
public ObjectInputStream(InputStream out) 把基本流变成高级流

方法:

方法名 说明
public Object readObject() 把序列化到本地文件中的对象,读取到程序中

细节:这个返回值是Object类型的,如果想要获得对象真正的类型,需要进行强制类型转换。

4.3 注意要点

如果一个javabean类继承了Serializable接口。那么java会对这个类所有内容进行计算获得一个序列号

如果已经序列化了这个类后,再对这个类进行修改,此时会得到新的版本号。如果此时使用反序列化。那么由于序列号不一致会导致异常的出现。

解决办法:固定这个序列号

public class Student implements Serializable{
    private static final long serialVersionUID=1L;//这样就可以固定序列号,必须这么写不能改
    private String name;
    ...
}

问题:如果对象中有一个成员属性不想序列化到本地

解决方法:加上transient 关键字即可

public class Student implements Serializable{
    private static final long serialVersionUID=1L;//这样就可以固定序列号,必须这么写不能改
    private transient String name;//加上这个关键字这个属性就不会序列化到本地文件中
    ...
}

问题:将多个对象序列化,解决如何多个反序列化。

public class a {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Student s1=new Student(23,"张三");
        Student s2=new Student(13,"李四");
        Student s3=new Student(43,"王五");
        ArrayList<Student> list=new ArrayList<>();//将要序列化的对象存入集合中,序列化这个集合即可解决
        list.add(s1);
        list.add(s2);
        list.add(s3);
        ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("3.txt"));
        oos.writeObject(list);
        oos.close();

        ObjectInputStream ois=new ObjectInputStream(new FileInputStream("3.txt"));
        ArrayList<Student> l1=(ArrayList<Student>)ois.readObject();
        System.out.println(l1);
        ois.close();
    }
}

五,打印流

打印流是高级流,打印流不能读只能写,打印流属于输出流。

打印流分为:PrintStream(字节打印流),PrintWriter(字符打印流)

打印流特点:

  • 打印流只能写不能读

  • 打印流有特有的写出方法,可以实现数据的原样写出

  • 特有的写出方法可以实现,自动刷新,自动换行

    打印一次数据=写出+换行+刷新

5.1 字节打印流

字节打印流是默认自动刷新的

构造方法:

构造方法 说明
public PrintStream(OutputStream/File/String) 关联字节输出流/文件/文件路径
public PrintStream(String fileName, Charset charset) 指定字符编码
public PrintStream(OutputStream out,boolean autoFlush) 自动刷新
public PrintStream(OutputStream out,boolean autoFlush, String encoding) 指定字符编码并且自动刷新

成员方法:

成员方法 说明
public void write(int b) 常规方法:规则和之前一样,将指定的字节写出
public void println(Xxx xx) 特有方法:打印任意数据,自动刷新,自动换行
public void print(Xxx xx) 特有方法:打印任意数据,不换行
public void printf(String format,Object... args) 特有方法:带有占位符的打印语句,不换行

举例:

public class a {
    public static void main(String[] args) throws FileNotFoundException, UnsupportedEncodingException {
        PrintStream ps=new PrintStream(new BufferedOutputStream(new FileOutputStream("1.txt",true)),true,"GBK");
        //这里创建方法中因为是父类OutputStream故参数可以写这个父类里面的所有子类
        //最里面的true,是字节输出流的true,表示开启续写功能
        //之所以写BufferedOutputStream是因为这个让字节打印流有缓冲区,从而可以实现自动刷新功能
        ps.printf("%s helllo world","你好");
        ps.print(11);//直接打印的就是11
        ps.println(true);//直接打印的就是true
        ps.close();

    }
}

5.2 字符打印流

字符打印流和字节打印流的区别就是,字符打印流底层多了缓冲区,想要刷新需要开启。

构造方法:

构造方法 说明
public PrintWriter(OutputStream/Writer/File/String) 关联字节输出/文件/文件路径
public PrintWriter(String fileName, Charset charset) 指定字符编码
public PrintWriter(Write w, boolean autoFlush) 自动刷新
public PrintWriter(OutputStream out, boolean autoFlush, Charset charset) 指定字符编码并自动刷新

成员方法:和字节打印流一模一样。。。

PrintStream和PrintWriter的区别

  • 打印数据的功能上是一模一样的:都是使用方便,性能高效(核心优势)
  • PrintStream继承自字节输出流OutputStream,因此支持写字节数据的方法。
  • PrintWriter继承自字符输出流Writer,因此支持写字符数据出去。

5.3 打印流和输出语句的关系

输出语句里面的System.out.printknout是一个字节打印流类型,即public static final PrintStream out=null这样的形式在System类中展示的。这样打印流默认指向控制台。

举例:

//获取打印流对象,此打印流在虚拟机启动时,由虚拟机创建,默认指向控制台。
//特殊的打印流:系统中的标准输出流
//标准输出流是不能关闭额,在系统中是唯一的!
PrintStream os=System.out;
//调用打印流中的方法
//写出数据,自动换行,自动刷新
ps.println("123");
ps.close();//关闭了系统标准输出流后!下面的方法就不能执行了
System.out.println("123");//就是上面的方法,用链式编程搞出来的

PrintStream os=new PrintStream("/1.txt");
System.setOut(ps);//输出语句重定向

六,解压缩流与压缩流

应用场景:

  1. 如果传输的数据较大,可以先压缩再传输。
  2. 对压缩包的解压

解压缩流属于:InputStream,字节输入流

压缩流属于:OutputStream,字节输出流

6.1 解压缩流

解压缩直接看下面的步骤即可,所有的压缩通用这样方法即可,简单过一遍,要用再查。。。

这样压缩包必须是Zip压缩的,其他的rar,7zip需要使用Maven导入相关依赖解决,暂不考虑。

压缩包里面的每一个文件都是ZipEntry对象

解压的本质:把每一个ZipEntry按照层级拷贝到本地的另一个文件夹中。

可以把解压缩流当作FileInputStream,只不过这个FileInputStream是读取压缩包的即可。

构造方法:

构造方法 说明
public ZipInputStream(InputStream in) 创建一个解压缩流对象
public ZipInputStream(InputStream in, Chartset charset) 创建一个解压缩流对象,使用指定编码

使用解压缩流的时候,默认使用UTF-8打开,而中国这边的windows操作系统默认是GBK。

需要在创建对象的时候指定编码方式,要不然会报非法参数异常。

细节:

  1. 调用一次getNextEntry()就获取一个文件对应得ZipEntry对象,如果获取了全部的ZipEntry对象,再次获取就返回null

解压步骤:

public class a {
    public static void main(String[] args) throws IOException {
        //1.创建一个File表示要解压的压缩包
        File src=new File("C:\\Users\\WDADWA\\Desktop\\learn_file\\java.zip");
        //2.创建一个File对象表示要解压的目的地
        File dest=new File("C:\\Users\\WDADWA\\Desktop\\learn_file\\新建文件夹");
        unzip(src,dest);
    }
    //定义一个方法来解压
    public static void unzip(File src,File dest) throws IOException {
        //解压的本质就是把压缩包里面的每一个文件读取出来,按照层级拷贝到目的地中

        //创建一个解压缩流用来读取压缩包中的数据
        ZipInputStream zip=new ZipInputStream(new FileInputStream(src),Charset.forName("GBK"));
        //表示当前在压缩包获取到的文件/文件夹
        ZipEntry entry;
        while((entry=zip.getNextEntry()) != null){
            //zip.getNextEntry()获取压缩包内文件,执行一次获取一次,获取完毕就返回null
            if(entry.isDirectory()){
                //文件夹:需要在目的地dest同样创建一共文件夹
                File file=new File(dest,entry.toString());
                file.mkdirs();
            }else{
                FileOutputStream fos=new FileOutputStream(new File(dest,entry.toString()));
                //文件:需要读取到压缩包中的文件,并把它按照层级目录放到目的地desk中
                int b;
                while((b=zip.read()) != -1){
                    fos.write(b);
                }
                fos.close();
                //表示压缩包中的一个文件处理完毕
                zip.closeEntry();
            }
        }
        zip.close();
    }
}

6.2 压缩流

压缩流和解压缩流的创建方法差不多,了解一下常用的压缩办法即可,要压缩的时候直接查。

  1. 压缩单个文件
import java.io.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class a {
    public static void main(String[] args) throws IOException {
        //创建File对象表示要压缩的文件
        File src=new File("a.txt");
        //创建file对象表示压缩包位置
        File dest=new File("C:\\Users\\WDADWA\\Desktop\\learn_file\\java");
        //调用方法来压缩
        toZip(src,dest);
    }
    /*
    作用:压缩
    参数一:表示要压缩的文件
    参数二:表示压缩包位置
     */
    public static void toZip(File src,File dest) throws IOException {
        //1.创建压缩流关联压缩包
        ZipOutputStream zos=new ZipOutputStream(new FileOutputStream(new File(dest,"a.zip")));
        //2.创建ZipEntry对象,表示压缩包里面每一个文件夹
        ZipEntry entry=new ZipEntry("a.txt");
        //3.把ZipEntry对象放到压缩包当中
        zos.putNextEntry(entry);
        //4.把src文件中的数据写入压缩包
        FileInputStream fis=new FileInputStream(src);
        int b;
        while((b=fis.read()) != -1){
            zos.write(b);
        }
        zos.closeEntry();
        zos.close();
    }
}
  1. 压缩多个文件
import java.io.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class a {
    public static void main(String[] args) throws IOException {
        //1.创建File对象表示要压缩的文件夹
        File src=new File("C:\\Users\\WDADWA\\Idea_coding\\Array_text");
        //2.创建file对象表示压缩包位置
        File dest=new File("C:\\Users\\WDADWA\\Desktop\\learn_file",src.getName()+".zip");
        //3.创建压缩流关联压缩包
        ZipOutputStream zos=new ZipOutputStream(new FileOutputStream(dest));
        //4.获取src里面的每一个文件,变成ZipEntry对象,放入压缩包当中
        toZip(src,zos,src.getName());//src.getName()放入的是Array_text
        //5.释放资源
        zos.close();
    }
    /*
    作用:获取到src里面的每一个文件,变成ZipEntry对象,放入压缩包中
    参数一:数据源
    参数二:压缩流
    参数三:压缩包内部的路径
     */
    public static void toZip(File src,ZipOutputStream zos,String name) throws IOException {
        //1. 进入src文件夹
        File[] files=src.listFiles();
        //2.遍历数组
        for (File file : files) {
            if(file.isFile()){
                //3.判断-文件,变成ZipEntry对象放入压缩包
                ZipEntry entry=new ZipEntry(name+"\\"+file.getName());//难点在这Array_text\file.getName()
                zos.putNextEntry(entry);//把entry对象放入压缩包中
                //读取文件中的数据,写入压缩包中
                FileInputStream fis=new FileInputStream(file);
                int b;
                while((b=fis.read())!=-1){
                    zos.write(b);
                }
                fis.close();
                zos.closeEntry();//代表这个文件处理完毕
            }else{
                //4.判断-文件夹,递归
                toZip(file,zos,name+"\\"+file.getName());
            }
        }
    }
}
posted @ 2024-06-23 13:46  wdadwa  阅读(19)  评论(0)    收藏  举报