Java的IO流总结

一、集合的流式处理

在JDK8以后,提供对集合的流式操作,对集合的元素可以像“流水”一样,依次访问,遍历,排序等,它是“不可逆的”(访问后面元素之后不能再次返回前面元素),根据流的处理方式,可以分为串行流和并行流,串行流表示同一时间只能有一个流式操作,而并行流可以与多个流式操作

​ 流返回的结果包括中间操作和最终操作

​ 中间操作:它的返回值依然是流对象,例如排序、过滤、去重

​ 最终操作:返回值是特定结果类型,例如遍历、取最大值、最小值或返回新的集合

常用方法:

stream():将一个集合流式化

filter():按条件过滤,里面使用lambda表达式

sort():排序集合元素

distinct():过滤重复元素

reduce():将集合的所有元素累加或拼接

map():映射一个新的集合,对集合元素变更输出

collect():返回一个新的集合

min():找到最小值

max():找到最大值

get():获取集合计算的结果

forEach():遍历每个元素

public static void main(String[] args) {
        List<Integer> list = new ArrayList();
        for(int i = 0 ;i<7;i++){
              list.add(i+1);
        }
         // 1、过滤 filter()   过滤掉偶数
        list.stream().filter( param ->param%2==1 )
                    .forEach(System.out::println); //遍历元素

        // 2、排序 sort()   降序
        list.stream().sorted((o1,o2)->o2-o1).forEach(System.out::println);

        //  3 map() 映射一个新的集合 , 如果是奇数 输出奇数 ,否则偶数
        list.stream().map(
                param -> param%2==1?"这个元素是奇数":"这是偶数"
                ).forEach(System.out::println);

        list.add(1);
        list.add(1);
        System.out.println("去重元素");
        // 4 distinct()  去除重复元素
        list.stream().distinct().forEach(System.out::println);

        // 5 reduce() 将集合的所有元素 累加(或拼接)

        int sum =  list.stream().reduce((o1,o2)->o1+o2).get();
        System.out.println("总和:"+sum);

        // 6 collect 返回一个新的集合
        List<Integer> list2= list.stream().filter(param->param%2==1).collect(Collectors.toList());
        System.out.println("遍历新集合");
        list2.stream().forEach(System.out::println);

        // 7、最大和最小
        int max = list2.stream().max((o1,o2)->o1-o2 ).get();
        System.out.println("最大值:"+max);
        int min = list2.stream().min((o1,o2)->o1-o2 ).get();
        System.out.println("最小值:"+min);
    }

二、Java的I/O流

2.1、File类

File类用于表示文件类,可以操作与文件相关的功能,File类属于java.IO,提供了大量的文件操作方法。

  • java.io.File类:文件和文件目录路径的抽象表示形式,与平台无关
  • File 能新建、删除、重命名文件和目录,但 File 不能访问文件内容本身。如果需要访问文件内容本身,则需要使用输入/输出流。
  • 想要在Java程序中表示一个真实存在的文件或目录,那么必须有一个File对象,但是Java程序中的一个File对象,可能没有一个真实存在的文件或目录。
  • File对象可以作为参数传递给流的构造器

创建一个文件类/文件路径类

File file = new File();

pathname 可以是文件路径: d://aaa.txt ,也可以写目录的路径 : d://myfile

常用构造器:

  • public File(String pathname):以pathname为路径创建File对象,可以是绝对路径或者相对路径,如果pathname是相对路径,则默认的当前路径在系统属性user.dir中存储。

  • 绝对路径:是一个固定的路径,从盘符开始

  • 相对路径:是相对于某个位置开始

  • public File(String parent,String child):以parent为父路径,child为子路径创建File对象。

  • public File(File parent,String child):根据一个父File对象和子文件路径创建File对象

  • 路径中的每级目录之间用一个路径分隔符隔开。

  • 路径分隔符和系统有关:

  • windows和DOS系统默认使用“\”来表示

  • UNIX和URL使用“/”来表示

  • Java程序支持跨平台运行,因此路径分隔符要慎用。

  • 为了解决这个隐患,File类提供了一个常量:public static final String separator。根据操作系统,动态的提供分隔符。

    举例:

     File file1 = new File("d:\\atguigu\\info.txt");
     File file2 = new File("d:" + File.separator + "atguigu" + File.separator + "info.txt");
     File file3 = new File("d:/atguigu");
    

常用方法:

craeteNewFile() : 创建新文件,要求构造的file对象指定的是文件路径。

delete() :删除文件或文件目录 删除成功返回true

exists() :测试此文件或目录是否存在。 存在返回true 不存在返回false

getAbsoluteFile(): 返回绝对路径的文件对象,返回值File

getAbsolutePath() : 返回绝对路径的字符串地址 ,返回

String getName() :获取文件或目录的文件名

getParent() : 返回此文件对象 的 父目录(上一级)的字符串地址。返回值String

getParentFile(): 返回次文件的父目录的文件对象, 返回值File

getPath() : 返回文件对象的相对路径

isDirectory() :判断该对象是否为目录, 目录: true 否则: false

isFile() :判断该对象是否为文件: 文件: true: 否则 false

lastModified() : 获取该文件的后修改实际 ,返回毫秒数

length : 获取该文件的 字节长度 list() : 获取该目录下的所有清单(文件和文件夹) ,返回字符串数组 , 清单地址

listFile(): 同上: 获取该目录下的所有清单(文件和文件夹),返回文件对象的数组

mkdirs : 创建多层目录, 根据文件对象(目录)创建有层级关系的目录 。

字节流(Stream) 字符流(Reader/Writer)

输入流(In) InputStream Reader

输出流(Out) OutputStream Writer

mkdir:创建一层目录。

2.2、什么是I/O?

​ 在生活中,你需要将U盘的文件,拷贝到电脑或者将电脑的文件拷贝到其它设备,文件是通过数据流的方式一次到达另一个设备中,文件的靠背就是一个输入(Input)和输出(Output)的过程

​ Java中提供对应的API支持对文件的输入和输出,java.io.*

什么是流?

​ 生活中也存在流的概念,例如管道中的流水,从管道的入口到达管道的出口,一滴水可以从入口流到出口,可以将“水”比作“字节数据或字符数据”,数据也可以从一端流到另一端。

​ 输入(Input):Java中,以“应用程序(内存)”为中心,将磁盘文件(设备端)到达内存中的过程,称为输入

​ 输出(Output):以“应用程序(内存)”为中心,将数据从内存到达磁盘文件(设备端)的过程称为输出

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-S9dwtqyy-1604833819131)(D:\ziliao\java2008\01 java基础\笔记\assets\1604295414903.png)]

根据文件操作方式不同可以将IO分为三类

1、按照读写的方向不同:输入流(InputStream)和输出流(OutputStream)

2、按照数据类型不同:字节流(Byte)和字符流(Char)

3、按照读写效率不同:单一流和包装流(Buffered等缓冲流)

关于流的分类Java提供四个顶级抽象类,分布构建它们的子类

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

常见的流

1、文件字节输入流和文件字节输出流:FileInputStreanm和 FileOutputStream

2、文件字符操作流:FileReader和FileWriter

3、缓冲字节输入流和缓冲字节输出流:BufferedInputStream 和 BufferedOutputStream

4、缓冲字符输出流和缓冲字符输出流: BufferedReader 和 BufferedWriter

5、数据输入流和数据输出流:DataInputStream 和 DataOutputStream

6、字节数组输入流和字节数组输出流: ByteArrayInputStream 和 ByteArrayOutputStream

7、字符数组输入流和字符数组输出流: BufferedReader 和 BufferedWriter

8、转换流:(字节流转成字符流):InputStreamReader和OutputStreamWrite

9、对象流(序列化流):ObjectInputStream和ObjectOutputStream

10、随机访问流(这个流既可以读,也可以写):RandomAccessFile

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mUFWlKAU-1604833819143)(D:\ziliao\java2008\01 java基础\笔记\assets\22-1604295486965.png)]

字节流

定义:文件的输入输出以一个“字节”为单位,进行流处理

FileInputStream和FileOutputStream

读入:将文件中的数据读到内存中

常用方法:

int read():一个字节的读取,返回字节的ASCII码,对于汉字会分3次读取

int read(byte):按一个数组长度读取,返回实际读取的字节长度,数据存放在数组中

int read(byte,offset,len):读取流中指定长度的数据,并存放在指定位置

available():返回流中剩余的字节长度,如果已读完则返回0

skip(long n):丢弃指定的字节长度,从下一个开始读取

`**available**()` 
File file = new File("d:/aaa.txt");
        FileInputStream fis = new FileInputStream(file);
       //
		byte [] b= new byte[10];
        StringBuffer sb = new StringBuffer();
            //每次读取的长度,  b: 存放数据的数组
          int len = 0;
          while((len = fis.read(b)) !=-1){
              sb.append( new String(b,0,len));
          }

        System.out.println(sb);
        fis.close();
public static void read2() throws IOException {
        // InputStream是抽象类
        InputStream is = new FileInputStream("d:/aaa.txt");
        //丢弃前两个字节
        is.skip(2);
        System.out.println((char)is.read());
        System.out.println("还剩下多少个字节:"+ is.available());
        // 将后面的字节继续使用字节数组读
        byte [] b = new byte[10];
        int len = is.read(b,1,4);
        // 显示数组中读取的所有数据
        System.out.println(Arrays.toString(b));
        //将数组的内容转成字符串  对于空内容不会转换
        System.out.println(new String(b));

        is.close();


    }

文件写出:将内存的数据写出到磁盘中

构造方法:

new FileOutputStream(File/String):构造文件对象的写出流,默认覆盖写出

new FileOutputStream(File/String,append):构造文件对象的写出流

append:表示在原有文件上追加数据, false:覆盖

常用方法:

void write(int):写出一个字节

void write(byte []):写出一个字节数组,这里需要知道数组的编码格式“UTF-8”

void write(byte [], offerset,len):写出一个字节数组,指定数组的长度和下标。 从数组的下标开始写出,len表示写出长度

flush() :清空缓存,对于使用缓冲流时,将缓冲强制清空。

//将内存的数据写出到文件   如果文件不存在,会自动创建, 默认覆盖写入  true:追加
        FileOutputStream fos = new FileOutputStream("d://aaa.txt" ,true);
        String str="今天天气还不错";
        fos.write(99);
        //写出一个字符串    字符串可以转成字节数组 或字符数组
        fos.write(str.getBytes("UTF-8"));
        // 写出指定长度
        fos.write(str.getBytes("UTF-8"),0,3); // 写出这个数组的前2个字节
        // 清空缓存
        fos.flush();
        // 关闭流
        fos.close();
        System.out.println("写出成功");

文件复制:

将文件(图片,文本,视频)从一个目录复制到另一个目录, 其中数据长度不变,通过文件读写的方式完成复制

复制过程:从源文件读取数据,然后将数据再出到目标文件中。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0BJ2tsaz-1604833819145)(D:\ziliao\java2008\01 java基础\笔记\assets\1604295753095.png)]

/**
     * 单个字节复制
     * @param srcFile 源文件
     * @param disFile 目标文件
     */
    public static void copyFile(File srcFile, File disFile){
        FileInputStream fis=null;
        FileOutputStream fos =null;
        try {
            // 源文件输入流
             fis = new FileInputStream(srcFile);
            // 目标文件输出流
             fos = new FileOutputStream(disFile);
            int n=0;
            while( (n =fis.read()) !=-1){
                 //将读到的n写出到 目标文件中
                 fos.write(n);
             }
            System.out.println("复制成功。。");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally{
            //无论是否发生异常 都会关闭流
            try {
                fos.close();
                fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }

    }

    /**
     * 一个字节数组的赋值
     * @param src  源地址
     * @param disc 目标地址
     */
    public static void copyFile(String src,String disc){
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            //创建 字节输入流
            fis=new FileInputStream(src);
            fos = new FileOutputStream(disc);
            int len=0;
            byte [] b = new byte[1024];
            while( (len= fis.read(b)) !=-1){
                // 写出 实际读取的长度 ,为了避免在最后一次写出时出现多余字节
                fos.write(b,0,len);
            }

            System.out.println("复制成功");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally{
            try {
                fos.close();
                fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
    }

字符流

​ 字符流用于读写存储字符的文件,以一个字符为单位,依次读取字符文件,常用以Reader或Writer为父类,对文件的操作使用java.io.FileReader和java.io.FileWriter

读文件:FileReader

常用方法:

  • new FileReader(path):通过文件路径构建字符输入流
  • new FileReader(File):通过文件对象构建字符输入流
  • int read():读取一个字符,返回字符的int类型
  • int read(char):读取字符数组长度的数据,返回实际读取字符长度,数据存放在字符数组中
  • int read(char offerset len):读取指定字符长度的数组,返回实际读取字符的长度,数据存放在字符数组中
  • mark(int):标记流中当前位置(读取到哪里了)
  • markSupported():判断次流是否支持mark操作
  • reset():重置数据流,(又可从头开始读取)
  • skip(long):丢弃指定长度字符

读字符文件

// 1、创建字符输入流
        try {
            FileReader reader = new FileReader("d:/myfile.txt");
            // 丢弃字符
            reader.skip(1);
            //读一个字符
            System.out.println((char)reader.read());
            System.out.println((char)reader.read());
            //读一个字符数组长度
            char [] c = new char[10];
            System.out.println("实际长度:"+reader.read(c));
            System.out.println(new String(c));

            //继续读
            int len =  reader.read(c,0,5);
            System.out.println("字符数组:"+ Arrays.toString(c));
            System.out.println("读指定长度字符个数:"+new String(c,0,len));

            // 将字符流重置
            // reader.reset();
            // System.out.println("重置后继续读:"+ reader.read());

            //System.out.println("是否支持标记字符:"+reader.markSupported());
            //关闭流,后面就不能使用该对象
            reader.close();

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

写文件:将内存数据写出到文件中,再写出过程中可以覆盖写出也可以追加写出,FileWriter类创建对象过程

new FileWriter(String):指定写出文件地址

new FileWriter(String,append):指定写出文件地址,设置是否追加写出,true表示追加,false表示覆盖

new FileWriter(File):指定写出文件对象

new FileWriter(File,append):指定写出文件对象,设置是否可追加

常用方法:

write(int):写出一个字符

write(String):写出一个字符串

write(char []):写出一个字符数组

write(char [] offerset len):写出一个指定长度的字符数组

flush():刷新缓冲

close():关闭缓冲

append©:将指定字符添加到此流中

// 1、创建文件写出流 FileWriter
        try {
            // 文件不存在,可自动创建,但是不会创建目录
            File file = new File("d://myabc/aaa.txt");
            //判断文件目录不存在, 先创建目录
            if(!file.getParentFile().exists()){
                //创建该目录
                file.getParentFile().mkdirs();
            }

            FileWriter writer = new FileWriter("d://myabc/aaa.txt");
            // 写一个字符的asci
            writer.write(98);
            //写字符串
            writer.write("hello");
            //写指定长度的字符串
            writer.write("abcdef",0,3); //写abc
            char [] c = {'L','O','L'};
            //写字符数组
            writer.write(c);
            System.out.println("写出成功");
            writer.flush();
            writer.close();

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

关闭和刷新:

对于带有缓冲功能的写出流,需要先刷新缓冲区,才能将数据写出,如果不刷新则最后不能正常写出。写出流如果刷新后还可以继续写,而关闭了则不能继续写

面试题:flush和close的区别?

flush:刷新缓冲,流可以继续使用

close:先刷新缓冲区,然后再释放系统资源,关闭后不能继续使用

try {
            FileWriter writer = new FileWriter("1.txt");
            writer.write("刷");
            writer.flush();
            writer.write("新");
            writer.flush();

            writer.write("关");
            writer.close();
            writer.write("闭"); // 这里抛出异常 , Stream closed
            writer.close();


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

关于换行符

回车符 \r 和换行符 \n :

回车符:回到一行的开头(return)。

换行符:下一行(newline)。

系统中的换行:
Windows系统里,每行结尾是 回车+换行 ,即 \r\n ;
Unix系统里,每行结尾只有 换行 ,即 \n ;
Mac系统里,每行结尾是 回车 ,即 \r 。从 Mac OS X开始与Linux统一。

2.3、包装流

定义:在原始字节流或字符流的基础上,为了提高读写效率进行再次处理的流,称为包装流/处理流

1、缓存字节流 BufferedInputStream、BufferedOutputStream

​ 由于原始流在文件读写时效率比较低(操作文件本身占用资源比较多),可以通过创建缓冲区的方式,提高读写效率,将读取/写出的数据先放入缓冲区,到达一定数量后再次从缓冲区读写/取出

mark(readLimit)和reset()用法

用法参考链接:https://blog.csdn.net/u010120301/article/details/49741757

其中reset不能单独使用,必须mark(readLimit),readLimit表示标记后最多读取的上限,但是这里标记后读取的内容与BufferedInputStream的缓冲大小有关,由上限决定,也就是说读取的内容超出上限可以继续重置到mark的位置

public static void main(String[] args) throws IOException {
        //创建缓冲流
        InputStream is = new FileInputStream("d:/myfile.txt");
        BufferedInputStream bis = new BufferedInputStream(is);
        //是否支持mark  或 reset
        System.out.println(bis.markSupported());
        System.out.println((char)bis.read());//97
        //重置
        bis.mark(3);  // pos标记往后退三个  最多可以读取字节上限

        System.out.println("再次读取:"+(char)bis.read());
        System.out.println("再次读取:"+(char)bis.read());
        System.out.println("再次读取:"+(char)bis.read());
        System.out.println("再次读取:"+(char)bis.read());

       bis.reset(); // 这里 重置后 退回到3个以前的位置

        // 重置后输出
        int n =0;
        while( (n = bis.read()) !=-1){
            System.out.println("重置后;"+(char)n);
        }
        //关闭流
        bis.close();
        is.close();

    }

2、缓存字符流 (BufferedReader 、BufferedWriter)

 public static void main(String[] args) throws IOException {
        // 缓冲字符流 可以一行一行读取   、写出
        BufferedReader br = new BufferedReader(new FileReader("d:/小众网站.txt"));
        //读一行
//        System.out.println(br.readLine());
//        System.out.println(br.readLine());
//        System.out.println(br.readLine());
        String s = null;  //读的数据为空 则不需要读
        while( (s = br.readLine()) !=null){
            System.out.println(s);
        }

        br.close();

        //缓冲写出流
        FileOutputStream   pw =  new FileOutputStream("d:/abcd.txt");
        //由于字节流不能直接放入 字符缓冲区,需要将它转成字符流  使用转换流并可以指定编码格式
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(pw));
        bw.newLine();// 开启新一行(换行)
        bw.write("这是测试转换流的方式");

        bw.close();
    }

3、打印流(输出流) PrintWriter 、PrintStream

public static void main(String[] args) throws FileNotFoundException {
        // 打印流 ,提供一些打印输出方法

        PrintWriter pw = new PrintWriter("d:/abcd.txt");
        pw.print(100);
        pw.println('a');//换行打印
        pw.print("hello");


        pw.close();

        //System.out   字节打印流 PrintStream

4、数据字节流(DataInputStream、DataOutrutStream)

它们用于读入写出Java基本数据类型的数据到文件或其它设备端

DataOutputStream常用方法:

  • writeByte(byte):写一个字节到设备或文件
  • writeChar(char):写一个字符到设备或文件
  • writeInt(int):写一个4个字节的int到设备或文件
  • writeBoolean(boolean):写一个boolean类型到设备或文件
  • writeDouble(double):写一个double类型到设备或文件
  • WriteFloat(float):写一个float类型到设备或文件
  • writeLong(long):写一个long类型到设备或文件
  • writeShort(short):写一个short类型到设备或文件
  • wroteUTF(string):写一个字符串类型到设备或文件

DataInputStream:读指定文件的数据,可以读数据类型

  • int readInt():读一个int类型

  • short readShort():读一个short类型

  • readByte():读一个byte类型

  • read():读一个字节类型

  • readDouble():读一个double类型

  • readFloat():读一个float类型

  • readChar():读一个char类型

  • readBoolean():读一个boolean类型

  • readLong():读一个long类型

    public static void main(String[] args) throws IOException {
            //创建数据写出流
            DataOutputStream dos = new DataOutputStream(
                    new FileOutputStream("d:/data.txt"));
            //写一个int类型 依次写出4个字节
            dos.writeInt(100);
            dos.writeBoolean(true);
            //关闭
            dos.close();
         
         //读取文件 创建数据读入流  ,需要按写的顺序读进来
            DataInputStream dis = new DataInputStream(
                    new FileInputStream("d:/data.txt"));
            //读一个int类型 (依次读4个字节)
            int num = dis.readInt();
            System.out.println("读取的数据:"+ num);
            System.out.println("读的数据:"+dis.readBoolean());
            dis.close();
    
    
        }
    

2.4、转换流

转换流是将字符流转成字符流的桥梁,也可以在转换时指定编码格式。InputStreamReader和OutputStreamReader

public static void main(String[] args) throws IOException {
         // 字节流转成字符流
         InputStream is = new FileInputStream("d://小众网站.txt");
         InputStreamReader isr = new InputStreamReader(is);
         //缓冲流 读取数据
         BufferedReader br = new BufferedReader(isr);
         //读一行
        String str =null;
        while( (str= br.readLine()) !=null){
            System.out.println(str);
        }
        //关闭流
        br.close();
        isr.close();
        is.close();

    }
public static void main(String[] args) throws IOException {
        // 创建 字节转成字符的 写出流  FileOutputStream os  =
        FileOutputStream fos = new FileOutputStream("d://data.txt");
        //指定编码  GBK 格式一个汉字占2个字节   UTF-8 格式一个汉字占3个字节
        OutputStreamWriter osw = new OutputStreamWriter(fos,"UTF-8");
        //缓冲形式的
        BufferedWriter bw = new BufferedWriter(osw);

        bw.write("你好");
        bw.newLine();
        bw.write("我不好");

        bw.close();


    }

2.5、随机字节流

RandomAccessFile是随机字节流,它是一个可读可写的流,在文件操作时指定该对象的模式(mode)后,可以读数据或写数据

实现 DataInputStream和DataOutputStream类

构造器:

RandomAccessFile rm = new RandomAccessFile(File,mode);

RandomAccessFile rm = new RandomAccessFile(String,mode);

mode:表示对象的模式

r:表示该对象只能读 不能写

rw/rws/rwd:表示该对象是可读可写的;

public static void main(String[] args) throws IOException {
        //创建可读 的流
        RandomAccessFile reader = new RandomAccessFile("d://data.txt","r");
        //创建可读可写的 的流
        RandomAccessFile writer = new RandomAccessFile("d://data-1.txt","rw");
        // 读和写的过程和之前一样
        byte [] b= new byte[10];
        int len=0;
        while( (len = reader.read(b)) !=-1){
            writer.write(b , 0 , len);
        }
        System.out.println("复制成功");
        //关闭流
        writer.close();
        reader.close();

    }

skipByte 和 seek的区别

// 跳字节读取
        RandomAccessFile raf = new RandomAccessFile("d:/data.txt","rw");
        // 跳过2个字节
        raf.skipBytes(2);
        System.out.println((char)raf.readByte()); //3
        System.out.println("当前偏移量:"+raf.getFilePointer());//3
        // 又丢弃1个字节   从当前位置 往后偏移1位
        raf.skipBytes(1);
        System.out.println("修改后的偏移量"+raf.getFilePointer());//4
        System.out.println("偏移后的读取数据:"+(char)raf.readByte()); //5
        raf.close();


        // seek用法
        RandomAccessFile raf2 = new RandomAccessFile("d:/data.txt","rw");
        //  设置当前读取的位置  ,从0开始计算  ,指定n ,就从n的下一个字节 读取
        raf2.seek(2);
        System.out.println("seek后的数据:"+(char)raf2.readByte());//3
        raf2.seek(1); // 又从0开始 设置偏移量为1  
 		System.out.println("修改后的偏移量"+raf.getFilePointer());//1
        System.out.println("seek后的数据:"+(char)raf2.readByte())//2
        raf2.close();

2.6、对象序列化流

​ 对象流也称为序列化流,用于存储对象和读取对象的字节流,也是属于包装流

序列化和反序列化

​ 将内存中的对象(Object,集合类等)保存到磁盘、网络介质、其他设置的过程,并在合适的时间能获取磁盘文件/网络的数据,这个过程就是对象的序列化和反序列化

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mI39bH4y-1604833819147)(D:\ziliao\java2008\01 java基础\笔记\assets\44.png)]

为什么需要序列化和反序列化呢?

​ 在之前文件中存储的文本信息,这样不便于对数据的分类和操作,如果可以做到直接对对象的读和写,这样可大大提高编程效率,并最大程度保证对象的完整性

Java-IO中实现对象序列化的两种方式:

​ 1、实现Serializable接口

​ 2、实现Externalizable接口

2.6.1、Serializable接口

​ 对象需要实现该接口,但是他没有任何需要实现的方法,只有一个用于标记该类可序列化的唯一标识。任何类需要序列化都必须标记该变量

 public class User implements Serializable {
       // 对于能区分其他类的唯一表示
       private static final long serialVersionUID = 1L;
   
        private int uid;
        private String name;
        private String password;
        //  有一部分属性不能序列化
   
       public int getUid() {
           return uid;
       }
   
       public void setUid(int uid) {
           this.uid = uid;
       }
   
       public String getName() {
           return name;
       }
   
       public void setName(String name) {
           this.name = name;
       }
   
       public String getPassword() {
           return password;
       }
   
       public void setPassword(String password) {
           this.password = password;
       }
   
       @Override
       public String toString() {
           return "User{" +
                   "uid=" + uid +
                   ", name='" + name + '\'' +
                   ", password='" + password + '\'' +
                   '}';
       }
   }
 //创建序列化的对象流  从内存到文件
           ObjectOutputStream oos = new ObjectOutputStream(
                   new FileOutputStream("d:/user.txt"));
           User user= new User();
           user.setUid(1001);
           user.setName("admin");
           user.setPassword("123456");
           //序列化对象
           oos.writeObject(user);
           //关闭流
           oos.close();
   
           // 反序列化:  将文件中的数据 再读入到内存中 ,需要一个读的流 ObjectInputStream
           ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d://user.txt"));
           //  反序列化尽量只读一次 (也可以读多次, 如何写出就如何读入)
           Object obj = ois.readObject();
           if(obj instanceof  User){
               User u = (User)obj;
               System.out.println("反序列化的结果:"+u);
           }
           //关闭流
           ois.close();
   

问题: 能否自定义序列化的属性 ,这里可以采用方式二,实现Externalizable,并重写两个方法 接口继承而来,在其基础上新增了两个未实现方法:readExternal(ObjectInputStream)和 writeExternal(ObjectOutputStreawm) ,自定义需要序列化的属性

public interface Externalizable extends java.io.Serializable

2.6.2、Externalizable接口

public class Student implements Externalizable {
    private  int id;
    private String name;
    private String sex;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    // 自定义可序列化的属性
    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeInt(this.id);
        out.writeUTF(this.name);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        this.id = in.readInt();
        this.name = in.readUTF();
    }

    public Student(int id, String name, String sex) {
        this.id = id;
        this.name = name;
        this.sex = sex;
    }

    public Student( ) {

    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", sex='" + sex + '\'' +
                '}';
    }
}
 public static void main(String[] args) throws IOException, ClassNotFoundException {
         // 创建序列化类
        ObjectOutputStream oos = new ObjectOutputStream(
                new FileOutputStream("d:/stu.txt"));
        //创建学生
        List<Student> list = new ArrayList<>();
        list.add(new Student(1001,"张飞","男"));
        list.add(new Student(1002,"刘备","男"));
        list.add(new Student(1003,"小乔","女"));
        // 将集合序列化
        oos.writeObject(list);
        //关闭
        oos.close();

        // 反序列化
        ObjectInputStream ois = new ObjectInputStream(
                new FileInputStream("d:/stu.txt"));
        //读
        Object obj = ois.readObject();
        if(obj instanceof  List){
            List<Student> list2 = (List<Student>)obj;
            for(Student s : list2){
                System.out.println(s);
            }
        }
        //关闭流
        ois.close();



    }

问题:哪些属性不能序列化?

1、类中的static修饰的属性不能序列化

2、类中属性被transient修饰的不能序列化,例如transient private Integer age = null;

3、实现Externalizable接口的类的属性不能全部序列化,必须手动写可序列化的属性

2.7、文件的压缩流和解压流

文件压缩使用场景:在文件上传或下载中需要操作多个文件时,如果一个一个文件复制需要花较长时间,而且比较繁琐,javaAPI提供一种压缩/解压文件方式,可以将多个文件打包成一个文件(.zip)

包:java.util.zip

常用类:ZipEntry:表示压缩文件中的每一个实体文件

​ ZipFile:表示压缩文件对象

​ ZipOutputStream:表示压缩文件输出流,用于将普通文件写出到zip文件中

​ ZipInputStream:表示解压文件的输入流,用于读zip文件中的每一个实体ZipEntry

2.7.1、压缩文件的步骤

a、创建需要压缩文件的输入流(InputStream)

b、创建压缩包所在的路径,并指定压缩文件名,同时创建ZipOutputStream输出流

c、将文件对象添加到ZipOutputStream中的实体中(也可以指定压缩后的实体名称)

d、文件复制

e、关闭流

public static void main(String[] args) throws IOException {
         // 1、创建文件对象
        File file = new File("d:/小众网站.txt");
        // 2、创建文件的输入流
        FileInputStream fis  = new FileInputStream(file);
        // 3、创建文件压缩流(输出流)
        ZipOutputStream zos = new ZipOutputStream(
                new FileOutputStream("d:/myfile.zip"));

        // 给压缩包中添加文件,并可自定义文件名
        zos.putNextEntry(new ZipEntry("小众网站.txt "));
        // 给压缩包设置注释
        zos.setComment("这是压缩包的注释。。。。");
        // 文件复制
        int len = 0;
        byte [] b = new byte[1024];
        while( (len = fis.read(b)) !=-1){
            zos.write(b,0,len);
        }
        System.out.println("文件压缩成功");

        zos.close();
        fis.close();

    }

压缩多个文件

/**
     * 压缩一个文件夹  myfile
     * @param args
     */
    public static void main(String[] args) throws IOException {

        //构建压缩包的输出流
        ZipOutputStream zos = new ZipOutputStream(
                        new FileOutputStream("d:/myfile.zip"));
        File file=new File("d:/myfile");
        File [] files =  file.listFiles();

        for(File f : files){
             //构造每一个文件的输入流
            FileInputStream fis  = new FileInputStream(f);
            putZipFile(fis, zos ,f.getName());
            System.out.println(f.getName()+"文件压缩成功" );
        }

        //关闭压缩流
        zos.flush();
        zos.close();


    }

    /**
     * 将文件放入压缩流中
     * @param fis
     * @param zos
     * @param entryName
     * @throws IOException
     */
    public static void putZipFile(InputStream fis ,
                                  ZipOutputStream zos,
                                  String entryName) throws IOException {
        // 给压缩包中添加文件,并可自定义文件名
        zos.putNextEntry(new ZipEntry(entryName));
        // 给压缩包设置注释
        zos.setComment("这是压缩包的注释。。。。");
        // 文件复制
        int len = 0;
        byte [] b = new byte[1024];
        while( (len = fis.read(b)) !=-1){
            zos.write(b,0,len);
        }
        System.out.println("文件压缩成功");


        fis.close();
    }

2.7.2、解压文件的步骤:

解压文件是将一个.zip文件的内容,复制到文件下,需要使用ZipInputStream

解压文件的关键点: 获取解压文件的每一个条目ZipEntry的输入流 ,将输入流写出到指定位置。

如何获取输入流: ZipFile对象 表示一个zip文件

在这里插入图片描述

步骤:

a、根据文件路径创建ZipInputStream

b、根据文件路径创建ZipFile对象,用于获取输入流

c、循环遍历每一条目,得到它的ZipEntry

d、获取ZipEntry的输入流

e、将文件复制到指定位置

public static void main(String[] args) throws IOException {
          //1、创建ZipInputStream
        ZipInputStream zis = new ZipInputStream(
                new FileInputStream("d:/myfile.zip"));
        // 2、创建ZipFile对象
        ZipFile zipFile = new ZipFile("d:/myfile.zip");
         // 3、获取zip中的实体
        ZipEntry en = null;
        while(  (en= zis.getNextEntry())!=null){
            System.out.println(en.getName()+"--"+en.isDirectory());
            //4、获取每一个en的输入流 (关键)
            InputStream is =   zipFile.getInputStream(en);
            copyFile(is ,"d:/my",en.getName());
        }
    }

    /**
     * 通过输入流 复制文件到指定的目录下
     * @param is  输入流
     * @param path  存放文件的路径
     * @param fileName  文件名
     */
    public static void copyFile(InputStream is , String path , String fileName) throws IOException {
          File file = new File(path);
          //判断目录是否存在, 不存在就 创建
          if(!file.exists()){
              file.mkdirs();
          }

        FileOutputStream fos = new FileOutputStream(path+File.separator+fileName);
        int len = 0 ;
          byte [] b = new byte[1024];
          while( (len = is.read(b)) !=-1){
                fos.write(b,0,len);
          }
          System.out.println("解压成功");
          fos.close();
          is.close();
    }
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值