2022-08-09 第4组 蒋萍 IO流的简单使用

“ 把今天过好 ”

最近是怎么了??
我总是感到莫名其妙的累,效率也不高,脑子也不想动

IO流

File类不能操作文件内容

1、分类

1、按流向分

  • 输入流(读):从物理内存读取数据到运行内存

  • 输出流(写):从内存写出数据到硬盘


2、按操作单元分

  • 字节流:一个字节一个字节的操作(二进制操作),可以操作任意类型的文件

  • 字符流:一个字符一个字符的操作,一个字符是两个字节,主要用于处理文本文件,只能是纯文字(.txt、.java、.xml、.py、.properties……)


3、按角色分

  • 节点流:直接操作一个特定的IO设备(麻烦),是二进制操作!!!

  • 处理流:在节点流的基础上做进一步处理 (写法简单,性能高);缓冲流


2、在Java中IO流常用流:

字节输入流 字节输出流 字符输入流 字符输出流
抽象基类 InputStream OutputStream Reder Writer
访问文件(节点流) FileInputStream FileOutputStream FileReder FileWriter
缓冲流(处理流) BufferedInputStream BufferedOutputStream BufferedReder BufferedWriter
操作对象 ObjectInputStream ObjectOutputStream

3、流的使用

1.字节输入流

往内存读数据

image-20220809131936909

1、创建字节输入流

image-20220809100256396

2、开始读的操作,read,返回值是int,返回值是-1时,说明文件读取到了末尾

字节流:一个一个字节的读所以用到循环读取

用啥循环??

while(){}

并且现在打印出来的是ASCII码,怎么转成我们看得懂的??

用一个构造器:new String(buf) 但不好控制,得控制字节数组的大小

字节流读的操作,目的也不是让你看懂,哈哈哈哈哈

@Test
public void test01() {
    InputStream inputStream = null;
    try {
        // 创建字节流对象
        inputStream = new FileInputStream("f:/aaa.txt");
        // 做个标记   ----- 操作的是二进制,所以用 int
        int read;
        // 缓冲 ??? 
        byte[] buf = new byte[1024];
        // 开始读取 ----- read() 要处理异常
        while((read = inputStream.read(buf)) != -1){
            System.out.println(Arrays.toString(buf) + " ");
        }
    } catch (IOException e) {
        throw new RuntimeException(e);
    }finally {
        try {
            // 关闭流 ---- close() 也要处理异常
            inputStream.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

总结:

字节输入流读数据步骤:

1、创建字节输入流对象

2、定义标记控制输入流的读取

3、循环读取,若读到了-1,说明读到了末尾,循环结束

4、关闭字节流(只能手动关闭)

注意异常处理 !!!(最好处理掉,不要抛出)

一些注意点

感觉写起来有点臃肿 ? ? ?

可以封装一个工具类~~

  • 能不能再读一次??---------->不可以

当一个流读完之后,会默认调用markreset来记录和重置,这个流就已经重置到了上次读完的位置,所以就无法再次读取,并不是读完一次后就关闭了流。

2. 字节输出流

写入文件,原文件里有东西 ??那就追加or覆盖

FileOutputStream构造器中

boolean append参数:true:追加;false或无参:覆盖

使用和输入流类似

@Test
    public void test02() {
        OutputStream outputStream = null;
        try {
            outputStream = new FileOutputStream("e:/aaa.txt",true);  // 追加 !
            // 一个字节一个字节的写
            outputStream.write("\r\n".getBytes());  // 换行
            outputStream.write("八月正午的阳光都没你耀眼".getBytes());
            System.out.println("数据写出成功...");
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            IOUtil.closeIO(null,outputStream);
/*            try {
                if(Objects.nonNull(outputStream)){
                    outputStream.close();
                }
            } catch (IOException e) {
              throw new RuntimeException(e);
            }
*/
        }
  • image-20220809134020933

注意点

1、一个字节一个字节地写 write() (费劲)括号里传的不是字符串

​ 如果文件不存在还没报错???----> 会自动给新建一个然后写入

2、write()如果传入的是字符串,就成字节型数组" xxx ".getBytes()

换行就是"\r\n"

3、关闭流的操作注意判断下,这么麻烦,整个工具类!!

*关闭流的工具类

/**
 * 关闭流
 * 注意保证所有的流都关闭!! 抓取异常的最大父类 Exception
 */
public class closeIOUtil {
     public static void closeIO(Reader reader, Writer writer){
        if(Objects.nonNull(reader)) {
            try {
                reader.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        if(Objects.nonNull(writer)){
            try {
                writer.close();
            } catch (Exception e) {
               e.printStackTrace();
            }
        }
    }
   
    public static void closeIO(InputStream inputStream, OutputStream outputStream){ 
        // 避免空指针异常
        if (Objects.nonNull(inputStream)) {
            try {
                inputStream.close();
            } catch (Exception e) {    
                //  保证出了异常也能让所有的流关闭
                throw new RuntimeException(e);
            }
        }
        if (Objects.nonNull(outputStream)) {
            try {
                outputStream.close();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }
}

了解即可:

在JDK1.7之后,很多资源类实现了AutoCloseable接口

包括常见的流:FileInputStream,FilOutputStream……

可以直接在try中定义资源,它会主动释放资源,不用手动关闭

*案例:文件复制

/**
 * 两个流:
 * FileInputStream:把对应文件的内容读出来
 * FileOutputStream:把读到的内容写出去
 *
 * 复制粘贴
 * 复制:从哪复制,读 read(byte[])
 * 粘贴:粘贴到哪,写 write(byte[])
 *
 * 多个流同时使用时,流的关闭顺序??从后往前关 (先用的流后关,)
 *
 */
public class StreamCopy {

    @Test
    public void copy(){
        FileInputStream inputStream = null;
        FileOutputStream outputStream = null;
        try {
            inputStream = new FileInputStream("f:/aaa.txt");
            outputStream= new FileOutputStream("f:/bbb.txt");
            int len;
            byte[] buf = new byte[3]; //一次读三个字节
            // 循环读进来
            while ((len = inputStream.read(buf)) != -1){
                // 写出去
                outputStream.write(buf,0,len);
                // outputStream.write(buf,0,len); 读几个写几个
            }
            System.out.println("文件复制完毕。。。");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }finally {
            CloseIOUtil.closeIO(inputStream,outputStream);
        }
    }
}

3、字符流操作

很少用FileReader这个节点流,简单了解即可~

Reader reader = new FileReader("e:/aaa.txt")

一个中文就当一个字符;

读出来是汉字就汉字,读到的是字符型数组,输出可以转成字符串new String(buf ,0,len)

字符处理流(用的最多)

只能处理纯文本文件,最好用缓冲流

缓冲字符输入流

// BufferedRead buffer = new BufferedRead(这里传节点流)

缓冲字符输出流

 @Test
    public void test02() {
        Writer writer = null;
        try {
            writer = new FileWriter("e:/bbb.txt");
            writer.write("阿里巴巴规约手册...");
            System.out.println("数据写出成功...");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }finally {
            IOUtil.closeIO(null,writer);
        }
    }

    @Test
    public void test01() {
        Reader reader = null;
        try {
            reader = new FileReader("e:/aaa.txt");
            int len;
            char [] buf = new char[10];
            while((len = reader.read(buf)) != -1){
                System.out.println(new String(buf,0,len));
            }
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            IOUtil.closeIO(reader,null);
        }
    }

*案例:缓冲字符流做文件复制

public static void main(String[] args) {
    BufferedReader bufferedReader = null;
    BufferedWriter bufferedWriter = null;

    try {
        bufferedReader = new BufferedReader(new FileReader("e:/aaa.txt"));
        bufferedWriter = new BufferedWriter(new FileWriter("e:/bbb.txt"));

        String str;
        while((str = bufferedReader.readLine()) != null){
            bufferedWriter.write(str);
            bufferedWriter.newLine();  // 换行
        }
        System.out.println("文件复制成功!!!");

    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        IOUtil.closeIO(bufferedReader,bufferedWriter);
    }
}

缓冲流是处理流

序列化与反序列化(可能面试)

操作的是对象

序列化:将对象写入IO流中,是把对象转换成有序字节流,以便在网络上传输或者保存在本地文件中。 核心作用是对象状态的保存与重建

反序列化:从IO流中恢复对象,客户端从文件中或网络上获得序列化后的对象字节流,根据字节流中所保存的对象状态及描述信息,通过反序列化重建对象

使用??

所有可在网络传输的对象都必须可序列化,所有存在硬盘上的对象也必须可序列化,否则报错

如何实现序列化??

实现接口:Serializable;将对象写入文件

1、先锁定一个文件(用到IO流)(output

2、创建对象流

3、造对象

4、写出writeObject(造的对象)

5、关流

@Test
public void test01() {
    ObjectOutputStream objectOutputStream = null;
    // 先锁定一个文件
    try {
        objectOutputStream = new ObjectOutputStream(new FileOutputStream("e:/user.txt"));
        User user = new User("张三",25,1);
        objectOutputStream.writeObject(user);
        System.out.println("对象写出成功...");
    } catch (FileNotFoundException e) {
        throw new RuntimeException(e);
    } catch (IOException e) {
        throw new RuntimeException(e);
    } finally {
        IOUtil.closeIO(null,objectOutputStream);
    }
}

实现反序列化:

对象读入文件

1、先锁定一个文件(用到IO流)input

2、创建对象流

3、写出readObject() 注意强转

4、关流

@Test
public void test02() {
    ObjectInputStream objectInputStream = null;
    // 先锁定一个文件
    try {
        objectInputStream = new ObjectInputStream(new FileInputStream("e:/user.txt"));
        User user = (User) objectInputStream.readObject();
        System.out.println("对象读取成功:" + user);
    } catch (FileNotFoundException e) {
        throw new RuntimeException(e);
    } catch (IOException e) {
        throw new RuntimeException(e);
    } catch (ClassNotFoundException e) {
        throw new RuntimeException(e);
    } finally {
        IOUtil.closeIO(objectInputStream,null);
    }
}

关于序列化版本号错误:

反序列化必须拥有class文件,但随着项目的升级,class文件也会升级

所以序列化保证升级前后的兼容性

  • 所以在类里给个版本号就行,这个版本号可以自由指定,但不能不指定,不然JVM会自己计算一个,就不能匹配,就报错

如果不指定版本号:不利于JVM移植,而且不同JVM计算规则不同,导致无法反序列化

如果只修改了方法,反序列化不受影响,如果修改的是静态变量static或瞬态变量transient反序列化也不受影响,无序修改序列化

其他

image

image

总结:

1、所有需要网络传输的对象都要实现序列化接口

2、对象的类名、实例变量都会被序列化,但方法、类变量、瞬态变量不会被序列化

3、如果想让某个变量不被序列化,可以用transient修饰

4、序列化对象的引用类型的成员变量必须是可序列化的,否则报错

5、反序列化时,必须有序列化对象的class文件

6、同一个对象被序列化多次,只有第一次序列化为二进制流,以后都只是保存序列化的版本号

7、建议所有可序列化的类

posted @ 2022-08-09 18:10  来日可追  阅读(25)  评论(0编辑  收藏  举报