I/O流(Input、Output)

1. 概念

Input,从硬盘读取数据到内存

Output,将内存中的数据输出到硬盘

  • 代表数据源对象或者接收数据的接收端对象
  • 本质是数据传输,根据传输的特性将流抽象为各种类,方便直观的进行数据操作
  • 作用是建立数据源与目的地的数据输送通道

2. 分类

根据数据流向分为输入流、输出流

按照数据单位分为字节流 8bit(非文本)、字符流 16bit

根据角色分为节点流(直接作用于文件上也称为文件流)、处理流(作用在已有流的基础上,对已存在的流进行包装)

  • 四个抽象基类,子类根据具体情况分派角色
    在这里插入图片描述
  1. 处理数据单位划分
名称特性实例
字节流每次读写一个字节,有中文时会出现乱码InputStream/OutputStream
字符流每次读写两个字节(一个字符),可正确显示中文Reader/Write
  1. 功能划分
名称特性实例
节点流从特定节点读写数据FileInputStream
处理流对已存在的流的连接与封装,通过封装的流的功能进行读写BufferReader

3. 应用实例

流的操作一般分为4步

  1. File类的实例化
  2. 相应流的实例化
  3. 读入/写出操作
  4. 资源关闭

3.1 文件流

  • File类
  • FileInputStream/FileOutputStream以字节方式读写文件
  • FileReader/FileWrite以字符方式读写文件
  • 将硬盘文件以字符流形式读入内存
File file = new File("hello.txt");
try {
    if (!file.exists()){
        file.createNewFile();
    }
} catch (IOException e) {
    e.printStackTrace();
}

//创建字符读取流
FileReader reader = null;
try {
    reader = new FileReader(file);
    //数据读入,若到文件末尾返回-1
    int data = reader.read();

    while (data!=-1){
        System.out.print((char) data);
        //类似迭代器循环
        data = reader.read();
    }
} catch (FileNotFoundException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
} finally {
    //关闭流,必须手动关闭,未关闭会导致内存泄漏等问题
    try {
        if (reader!=null) {
            reader.close();
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}
FileReader fileReader = null;
File file = new File("hello.txt");

fileReader = new FileReader(file);

char[] ch = new char[5];

int len;
while ((len = fileReader.read(ch)) != -1) {
//这里注意 i<len 否则数组五个空间覆盖时只会覆盖前三个,后面输出还是五个,会带着上一个数组中的后两个元素
    for (int i = 0; i < len; i++) {
        System.out.print(ch[i]);
    }
}
if (fileReader!=null){
    fileReader.close();
}
  • 将内存文件写出到硬盘,纯文本文件使用字符流,非文本文件使用字节流
   /*
         * 写出的话文件不存在会自动帮我们创建
         * 存在的话会对原有文件进行覆盖,因为默认追加append是false
         */
        FileWriter fileWriter = null;
        File file = new File("hello.txt");
//        fileWriter = new FileWriter(file);
        //表示是否在原有文件内容后进行追加写入
        fileWriter = new FileWriter(file,true);
        String str = "abcdefg";
        fileWriter.write(str + "\n");
        fileWriter.write(str);

        if (fileWriter != null) {
            fileWriter.close();
        }
//图片视频是字节数据,需要使用字节流处理,字符流转为字节流即将char 改为 byte
//复制图片
//使用字节流复制文本文件是可以的,但也只是单纯的复制,在内存中读的时候会出现乱码

File img1 = new File("1.jpg");
File img2 = new File("2.jpg");

FileInputStream inputStream = new FileInputStream(img1);
FileOutputStream outputStream = new FileOutputStream(img2);

byte[] b = new byte[10];
int len;
while ((len=inputStream.read(b))!=-1){
    for (int i = 0; i < len; i++) {
        System.out.println(b[i]);
        outputStream.write(b[i]);
    }
}
inputStream.close();
outputStream.close();

3.2 缓冲流

  • BufferedInputStream/BufferedOutputStream字节缓冲流
  • BufferedReader/BufferedWrite字符缓冲流
  • flush()方法,缓冲区数据写入流,先调用flush再调用close
  • 处理流的一种是缓冲流Buffered…

真实开发时不会采用基本的文件节点流,因为效率较差,往往采用缓冲流进行处理,提高流的读取写入速度

原因是内部提供了一个8192字节缓冲区常量

   File file1 = new File("1.jpg");
        File file2 = new File("2.jpg");


        FileInputStream inputStream = new FileInputStream(file1);
        FileOutputStream outputStream = new FileOutputStream(file2);

        //不可直接作用于文件上,只可以作用于已存在的流上
        BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
        BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream);

        //具体读取写入过程
        byte[] b = new byte[10];
        int len;
        while ((len=bufferedInputStream.read(b))!=-1){
            for (int i = 0; i < len; i++) {
                bufferedOutputStream.write(b[i]);
            }
        }
        //关闭时先关闭外层流,再关闭内层的流,因为处理流是包着节点流的
        bufferedInputStream.close();
        bufferedOutputStream.close();
        //其实在关闭外层流的同时,内层流也会自动关闭,所以可以省略不写
//        inputStream.close();
//        outputStream.close();

3.3 转换流

  • InputStreamReader字节流中读取字符
  • OutputStreamWrite字符写入字节流
  • 转换流(处理流的一种)

提供字节流和字符流之间的一个转换

解码:字节—>字符(看不懂—>看得懂)

  /*
         * 转换流使用,属于字符流,操作 char 型数组
         * InputStreamReader 将字节的输入流转换为字符的输入流
         * OutputStreamWriter 将字符的输出流转换为字节的输出流
         */
        File file = new File("hello.txt");
        File outhello = new File("outhello.txt");

        FileInputStream fileInputStream = new FileInputStream(file);
        FileOutputStream fileOutputStream = new FileOutputStream(outhello);
        //系统默认字符集看当前IDE设定,实际编码应该按照文件存储时的字符集编码来设定参数
        InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream, "UTF-8");

        OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream,"gbk");
        char[] ch = new char[20];
        int len;
        while ((len = inputStreamReader.read(ch)) != -1) {
//           for (int i = 0; i <len ; i++) {
//               System.out.println(ch[i]);
//           }

            String str = new String(ch, 0, len);
            System.out.print(str);
            outputStreamWriter.write(str);

        }
        inputStreamReader.close();
        outputStreamWriter.close();

3.4 标准输入输出流

System.in//键盘输入
System.out//控制台输出

3.5 打印流

打印只能是输出

PrintStream
PrintWriter

3.6 数据流

用于读写基本数据类型的变量或字符串

DataInputStream
DataOutputStream

3.7 对象流

对象序列化机制:允许把内存中的Java对象转换成与平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。当其它程序获取了这种二进制流,就可以恢复成原来的Java对象

序列化的好处在于可将任何实现了 Serializable接口的对象转化为字节数据,使其在保存和传输时可被还原
序列化是RMI(Remote Method Invoke-远程方法调用)过程的参数和返回值都必须实现的机制,而RMI是 JavaEE的基础。因此序列化机制是JavaEE平台的基础

如果需要让某个对象支持序列化机制,则必须让对象所属的类及其属性是可序列化的 (基本数据类型都是可序列化的),为了让某个类是可序列化的,该类必须实现如下两个接口之一。否则,会抛出 NotSerializableEXception异常

Serializable
Externalizable

凡是实现 Serializable接口的类都有一个表示序列化版本标识符的静态变量serialVersionUID用来表明类的不同版本之间的兼容性。简言之,其目的是以序列化对象进行版本控制,有关各版本反序列化时是否兼容。

如果类没有显示定义这个静态变量,它的值是Java运行时环境根据类的内部细节自动生成的,一旦类原始类进行调整,则无法再反序列化。

若类的实例变量做了修改,serialVersionUID可能发生变化。故建议,进行显式声明。

简单来说,Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的 serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常。(InvalidCastException)

/*
 * 序列化时一定要先实现Serializable接口(标识接口)
 * 必须实现进入的一个全局常量属性,序列版本号,设定唯一标识防止还原识别时混乱,详情见Serializable源码说明
 */
public class Person implements Serializable {

    //序列版本号,自定义一个long类型值
    public static final long serialVersionUID = 4752525455642L;

    private String name;
    private int age;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    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;
    }

    @Override
    public String toString() {
        return "Person{" + "name='" + name + '\'' + ", age=" + age + '}';
    }
}
 /*
         * 将对象读出写入到数据源中
         * 序列化:ObjectOutputStream保存内存中的Java对象或基本数据类型到磁盘中
         * 反序列化:ObjectInputStream从磁盘中读取Java对象或基本数据类型到内存中
         * 对于声明为 static 和 transient无效
         */

        //序列化操作
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("data.dat"));

//        objectOutputStream.writeObject("你好世界");
//        //刷新操作
//        objectOutputStream.flush();

        objectOutputStream.writeObject(new Person("tylt", 18));
        objectOutputStream.flush();

        objectOutputStream.close();

        //反序列化操作
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("data.dat"));
        Object readObject = objectInputStream.readObject();

//        String str = (String) readObject;
//        System.out.println(str);

        Person p = (Person) readObject;
        System.out.println(p);

        objectInputStream.close();

3.8 随机存取文件流

RandomAccessFile

直接继承Object,既可作为输入流也可输出流

/*
 * r:只读(不会创建文件,只会读取已经存在的文件)
 * rw:读写
 * rwd:读写+同步内容更新
 * rws:读写+同步内容更新+元数据更新
 */

File img = new File("1.jpg");
File outimg = new File("3.jpg");
RandomAccessFile accessFile = new RandomAccessFile(img,"r");
RandomAccessFile accessOutFile = new RandomAccessFile(outimg,"rw");

byte[] b = new byte[1024];
int len;
while ((len=accessFile.read(b))!=-1){
    for (int i = 0; i < len; i++) {
        System.out.println(b[i]);
        accessOutFile.write(b[i]);
    }

}
accessFile.close();
accessOutFile.close();

4. Java NIO

Java NIO(New IO,Non-Blocking IO) NIO将以更加高效的方式进行文件的读写操作,因为IO是面向流的,而NIO是面向缓冲区的

Java API中提供了两套NIO,一套是针对标准输入输出NIO,另一套就是网络编程NIO

早期的Java只提供了一个File类来访问文件系统,但File类的功能比较有限,所提供的方法性能也不高。大多数方法在出错时仅返回失败,并不会提供异常信息。

NIO.2为了弥补这种不足,引入了Path接口,代表一个平台无关的平台路径,描述了目录结构中文件的位置。Path可以看成是File类的升级版本,实际引用的资源也可以不存在。

/*
 * Files、Paths工具类
 * k
 */
Path path = Paths.get("hello.txt");
System.out.println(path);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天宇龙腾

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值