IO流详解

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

在 Java 中,文件的输入和输出是通过流(Stream)来实现的,流的概念源于 UNIX 中管道(pipe)的概念。在 UNIX 系统中,管道是一条不间断的字节流,用来实现程序或进程间的通信,或读写外围设备、外部文件等。

一个流,必有源端和目的端,它们可以是计算机内存的某些区域,也可以是磁盘文件,甚至可以是 Internet 上的某个 URL。对于流而言,不用关心数据是如何传输的,只需要从源端输入数据(读),向目的端输出数据(写)。

一、File类

常用方法及作用
/**
* /**
* File类的使用:
* -File类的一个对象,代表一个文件或一个文件目录(俗称文件夹)
* —File类声明在java.io包下
* -相对路径:相较于某个路径下,指明的路径
* -绝对路径:包含盘符在内的文件或文件目录的路径
* 路径分隔符和系统有关:
* -windows和Dos系统默认使用"“来表示
* -UNIX和URl使用”/"来表示
* 如何创建File类的实例:
* File(String filePath)
* File(String parentPath,String childPath)
* File(File parentFile,String childPath)
* File类中涉及到关于文件或目录的创建,删除,重命名,修改时间,文件大小等方法,并未涉及到写入或读取文件内容的操作。
* 如果需要读取写入文件内容,必须使用IO流来完成
*
* 后续File类的对象常会作为对象,传入到流的构造器中,指明读取或写入的终点。
*
* 注意:
* IDEA:如果使用单元测试方法,相对路径基于当前moudle
* 如果使用main方法,相对路径基于当前工程
* Eclipse:不管是单元测试方法还是main方法,相对路径都是基于当前工程
*/
* public String getAbsolutePath():获取绝对路径
* public String getpath():获取路径
* public String getName():获取名称
* public String getParent():获取上层文件目录路径,若无,返回null
* public long length():获取文件长度(即:字节数)不能获取目录的长度
* public long lastModified():获取最后一次修改文件的时间,毫秒值
* 如下两个方法适用于文件目录:
* public String[] list():获取指定目录下的所有文件或者文件目录的名称的数组
* public File[] listFiles():获取指定目录下的所有文件或者文件目录的File数组(返回以绝对路径的方式)
*
* public boolean isDirectory():判断是否是文件目录
* public boolean isFile():判断是否为文件
* public boolean exists():判断是否存在
* public boolean canRead():判断是否可读
* public boolean canWrite():判断是否可写
* public boolean isHidden():判断是否可隐藏
*
* 创建硬盘中对应的文件或文件目录
* File类的创建功能:
* 上层目录指的是多个目录,不止一个
* public boolean createNewFile():创建文件,若文件存在,则不创建,返回false
* public boolean mkdir():创建文件目录,如果此文件目录存在,就不创建,如果此文件目录的上层目录不存在,也不创建
* public boolean mkdirs():创建文件目录,如果上层文件目录不存在,则一并创建
* 注意:如果创建的文件或者文件目录没有写盘符路径,那么默认在相对路径下
* 删除磁盘中对应的文件或者文件目录
* File类的删除功能:
* public boolean delete():删除文件或者文件夹(要想删除成功,其当前文件不能有子目录或者文件)
* 注意:java中的删除不走回收站,要删除一个文件目录,请注意该文件目录内不能包含文件或者文件目录
*/
案例:
1.递归删除指定目录下的所有文件

package IO;

import java.io.File;
import java.util.Scanner;

public class Test {
    public static void del(File file){
        File[] listFiles = file.listFiles();//找出当前文件夹下的所有文件和文件加
        if(listFiles != null) {
            for (File f : listFiles) {
            //判断当前文件是文件夹还是文件,如果是文件则删除,否则,接着递归,找出所有的文件然后删除。
                if(f.isDirectory()){
                    if(f == null){
                        f.delete();
                        continue;
                    }
                    del(f);
                }
                f.delete();

            }
        }
    }
    public static void main(String[] args) {
        System.out.println("请输入一个目录(删除所有的文件):");
        String s = new Scanner(System.in).nextLine();
        del(new File(s));
    }
}

2.找出指定磁盘下后缀为.java的文件(递归)

package IO;

import java.io.File;
import java.util.Scanner;

public class Test {
    public static void find(File file){
        File[] list = file.listFiles();
        if(list != null) {
            for (int i = 0; i < list.length; i++) {
                if (list[i].isFile()) {
                    String s = list[i].getName();//获取文件名
                    if (s.endsWith(".java")) {//判断是否以.java结尾
                        System.out.println(s);
                    }
                }
                find(list[i]);
            }
        }
    }
    public static void main(String[] args) {
        String s = new Scanner(System.in).nextLine();
        find(new File(s));
    }
}

二、IO流介绍?

在 I/O 处理中,最常见的就是对文件的操作。java.io 包中所提供的文件操作类包括:

  1. 用于读写本地文件系统中的文件:FileInputStream 和 FileOutputStream
  2. 描述本地文件系统中的文件或目录:File、FileDescriptor 和 FilenameFilter
  3. 提供对本地文件系统中文件的随机访问支持:RandomAccessFile

流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两设备间的传输称为流,流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。

流一般分为输入流(Input Stream)和输出流(Output Stream)两类,但这种划分并不是绝对的。比如一个文件,当向其中写数据时,它就是一个输出流;当从其中读取数据时,它就是一个输入流。当然,键盘只是一个输入流,而屏幕则只是一个输出流。(其实我们可以通过一个非常简单的方法来判断,只要是向内存中写入就是输入流,从内存中写出就是输出流)。

三、流的分类

  • —操作数据单位:字节流,字符流
  • —数据的流向:输入流,输出流
  • —流的角色:节点流,处理流

1 .字节输入流输出流

案例:文件的复制,代码如下(示例):

	 @Test
    public void test2() throws IOException {
        //文件的复制(边读边写)
        FileInputStream fileInputStream = new FileInputStream("d:/桌面/概念.txt");
        FileOutputStream outputStream = new FileOutputStream("d:/桌面/概念copy.txt");
        int len;
        byte[] arr = new byte[1024];
        while((len = fileInputStream.read(arr)) != -1){//读取文件
            //向另一个文件中写入内容
            outputStream.write(arr,0,len);
           
        }
        outputStream.close();
        fileInputStream.close();
    }

注意:
1.对于文本文件(.txt .java .c .cpp)使用字符流处理
2.对于非文本文件(.jpg .doc .mp3 .mp4 .avi .ppt…),使用字节流处理
3.对于文本文件,使用字节流可以复制,但是不能在复制的过程中查看,要想在控 制台层面查看,必须使用字符流

2.字符输入输出流

读取指定文件的几种方式:
 @Test
    public void test() {
        //1.实例化File类的对象,指明要操作的文件
        File file = new File("hello.txt");//相较于当前moudle下
        //2.提供集体的流
        FileReader f = null;
        try{
        f = new FileReader(file);
        //3.数据的读入
        //read():返回读入的一个字符,如果到达文件末尾返回-1
        //说明:异常处理:为了保证流资源一定可以执行关闭操作,需要使用try - catch - finally结构
        //读入的文件一定要存在,否则就会报FileNotFoundException的异常。
        //方式一:
//        int data = f.read();
//        while(data != -1){
//            System.out.print((char) data);
//            data = f.read();
//        }
        //方式二:语法上的修改
        int data;
        while((data = f.read()) != -1){
            System.out.println(data);
        }}catch(IOException e){
            e.printStackTrace();
        }

        //4.流的关闭操作
        finally {
            try {
                if (f != null){
                    f.close();

                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
//对read()操作升级:使用read的重载方法
    @Test
    public void Test2(){
        FileReader f = null;
        try{
        //1.File类的实例化
        File file = new File("Test\\hehe.txt");
        //2.FileReader流的实例化
        f = new FileReader(file);
        //3.读入的操作
        //read(char cbuf):返回每次读入cbuf数组中的字符的个数,如果到达文件末尾返回-1
        char[] arr = new char[5];
        int len;
        while((len = f.read(arr)) != -1){
            //方式一:
//            for(int i = 0;i < len;i++){
//                System.out.print(arr[i]);
//            }
            //方式二:
            String s = new String(arr, 0, len);
            System.out.print(s);
        }
        }catch(IOException e){
            e.printStackTrace();
        }
        //4.资源关闭
        finally {
            try {
                if (f != null) {
                    f.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

文件的输出

/**
     * 从内存中写出数据到硬盘的文件里
     *
     * 说明:
     * 1.输出操作,对应的File可以不存在,如果不存在,在输出的过程中会自动创建此文件。
     * 2.File对应的硬盘的文件如果存在:
         如果流使用的构造器是:FileWriter(file,false)/FileWriter(file):就会覆盖掉原有的文件数据
     *   如果流使用的构造器是:FileWriter(file,true):不会对原有文件内容覆盖,而是追加在该文件后面
     *
     */
    @Test
    public void FileWriter(){
        FileWriter fileWriter = null;
        try {
            //1.File类的实例化
            File file = new File("Test\\hehe.txt");
            //2.FileWriter流的实例化
//            fileWriter = new FileWriter(file);
            //表示追加的方式
            fileWriter = new FileWriter(file,true);
            //3.写出的操作
            //方式一
            // fileWriter.write("万物皆对象!!!");
            //方式二
            fileWriter.write("\n人生苦短,我爱Python".toCharArray());
            //4.流资源的关闭
        }catch(IOException e){
            e.printStackTrace();
        }finally {
            try {
                if(fileWriter != null) {
                    fileWriter.close();
                }
            }catch(IOException e){
                e.getMessage();
            }
        }
    }

write和read方法的几种重载说明

在 InputStream 类中,方法 read() 提供了三种从流中读数据的方法:

  1. int read():从输入流中读一个字节,形成一个 0~255 之间的整数返回(是一个抽象方法)。
  2. int read(byte b[]):从输入流中读取一定数量的字节,并将其存储在缓冲区数组 b 中。
  3. int read(byte b[],int off,int len):从输入流中读取长度为 len 的数据,写入数组 b 中从索引 off 开始的位置,并返回读取得字节数。
    对于这三个方法,若返回 -1,表明流结束,否则,返回实际读取的字符数。

OutputStream 类方法:

方法说明
write(int b)throws IOException将指定的字节写入此输出流(抽象方法)
write(byte b[])throws IOException将字节数组中的数据输出到流中
write(byte b[], int off, int len)throws IOException将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此输出流
flush()throws IOException刷新此输出流并强制写出所有缓冲的输出字节
close()throws IOException关闭流

3.缓冲流

# 缓冲流的运行机制

  1. 类 BufferedInputStream 和 BufferedOutputStream 实现了带缓冲的过滤流,它提供了缓冲机制,把任意的 I/O 流“捆绑”到缓冲流上,可以提高 I/O 流的读取效率。
  2. 在初始化时,除了要指定所连接的 I/O 流之外,还可以指定缓冲区的大小。缺省时是用 32 字节大小的缓冲区;最优的缓冲区大小常依赖于主机操作系统、可使用的内存空间以及机器的配置等;一般缓冲区的大小为内存页或磁盘块等的整数倍。
  3. BufferedInputStream 的数据成员 buf 是一个位数组,默认为 2048 字节。当读取数据来源时例如文件,BufferedInputStream 会尽量将 buf 填满。当使用 read () 方法时,实际上是先读取 buf 中的数据,而不是直接对数据来源作读取。当 buf 中的数据不足时,BufferedInputStream 才会再实现给定的 InputStream 对象的 read() 方法,从指定的装置中提取数据。
  4. BufferedOutputStream 的数据成员 buf 是一个位数组,默认为 512 字节。当使用 write() 方法写入数据时,实际上会先将数据写至 buf 中,当 buf 已满时才会实现给定的 OutputStream 对象的 write() 方法,将 buf 数据写至目的地,而不是每次都对目的地作写入的动作。
    缓冲区

## 缓冲流的作用

  • 处理流之一:缓冲流的使用
  • 1.缓冲流:
  •  BufferedInputStream
    
  •  BufferedOutputStream
    
  •  BufferedReader
    
  •  BufferedWriter
    
  • 2.作用:提高流的读取写入效率
  • 速度提高的原因:内部提供了一个缓冲区
  • 3.处理流,就是套接在已有的流的基础上

案例:使用缓冲流复制一段文本

//复制一段文本
    @Test
    public void test2(){
        BufferedReader fr = null;
        BufferedWriter fw = null;
        try {
            fr = new BufferedReader(new FileReader("hello.txt"));
            fw = new BufferedWriter(new FileWriter("Test.txt"));
            //方式一:
//            char[] arr = new char[1024];
//            int len;
//            while ((len = fr.read(arr)) != -1) {
//                fw.write(arr, 0, len);
//            }
            //方式二:readLine():表示读取一行,没有换行符,可以使用 newLine(),增加一个换行符
            String data;
            while((data = fr.readLine()) != null){
                //方式一:
//                fw.write(data + "\n");
                //方式二:
                fw.write(data);
                fw.newLine();
            }


            System.out.println("复制成功!");
        }catch(IOException e){
            e.printStackTrace();
        }finally{
            if(fr != null){
                try {
                    fr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(fw != null){
                try {
                    fw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

4.转换流

1.转换流:
InputStreamReader:将一个字节的输入流转换为字符的输入流
OutputStreamWriter:将一个字符的输出流,转换为字节的输出流
2.作用:提供字节流与字符流之间的转换
3.解码:字节,字节数组 —> 字符数组,字符串
编码:字符数组,字符串 —> 字节,字节数组

示例代码:

package com.example;


import org.junit.jupiter.api.Test;

import java.io.*;
import java.nio.charset.StandardCharsets;

public class HandleTest {
    @Test
    public void test() throws Exception {
        //将字符流转换成字节流输出

    }
    public static void main(String[] args) throws Exception {

        File file = new File("D:\\桌面\\文件.txt");
        //以字节流的方式读入
        FileInputStream inputStream = new FileInputStream(file);
        //将字节流转换成字符流,同时设置字符集,以防止出现乱码
        InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
        //将字符流转换成字节流保存到text.txt文件中
        OutputStreamWriter outputStreamWriter = new OutputStreamWriter(new FileOutputStream("d:/桌面/text.txt"), StandardCharsets.UTF_8);
        char[] arr = new char[1024];
        int len;
        while((len = inputStreamReader.read(arr)) != -1){
            String s = new String(arr, 0, len);
            //输出到控制台
            System.out.println(s);
            //同时将文件内容写到text.txt中
            outputStreamWriter.write(arr,0,len);
        }
        //刷新缓冲区,关闭文件
        outputStreamWriter.close();
        inputStreamReader.close();
    }
}

5.数据流

  • 为了方便的操作java语言的基本数据类型和String类型数据,可以使用数据流。
    数据流有两个类,用于读取和写出基本数据类型,String类型的数据。
  • DataInputStream和DataOutputStream分别套接在InputStream和OutPutStream子类的流上。
  • 方法:
    boolean readBoolean()
    char readChar()
    double readDouble()
    long readLong()
    String readUTF()
    byte readByte()
    float readFloat()
    short readShort()
    int readInt()
    void readFully(byte[] b)
    DataOutPutStream中的方法只需要把read改为write即可。
    示例代码:
package com.example;

import java.io.*;

public class DataTest {
   

    public static void main(String[] args) throws IOException {
        DataOutputStream dataOutputStream = new DataOutputStream(new FileOutputStream("d:/桌面/数据流.txt"));
        //写入基本数据类型
        dataOutputStream.writeInt(1);
        dataOutputStream.writeDouble(1.1);
        //写入字符串
        dataOutputStream.writeUTF("人生苦短");
        //关闭流
        dataOutputStream.close();
        //读取该文件,注意要按顺序读取,否则会出现错误
        DataInputStream dataInputStream = new DataInputStream(new FileInputStream("d:/桌面/数据流.txt"));
        int i = dataInputStream.readInt();
        System.out.println(i);
        double v = dataInputStream.readDouble();
        System.out.println(v);
        String s = dataInputStream.readUTF();
        System.out.println(s);
    }
}

6. 对象流

/**

  • 对象流的使用:
  • 1.ObjectInputStream 和 ObjectOutputStream
  • 2.作用:用于存储和读取基本数据类型和对象的处理流。它的强大之处在于可以把java中的对象写入到数据源中,也能把对象从数据源中还原回来
  • 3.要想一个java对象是可序列化的,需要满足的要求,见Person.java
  • 注意:凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态变量:private static final long serialVersionUID,
  • 如果类没有显示定义这个静态变量,它的值是java运行时环境根据类的内部细节自动生成的,若类的实例变量作了修改,serialVersionUID可能发生变化,所以建议显示赋值
  • ObjectInputStream 和 ObjectOutputStream不能序列化static和transient修饰的成员变量
    */

## 示例代码:

public class ObjectInputOutputTest {
    /**
     * 序列化过程:将内存中的java对象保存到磁盘中或通过网络传输出去,使用ObjectOutputStream实现
     *
     */
    @Test
    public void test1(){
        ObjectOutputStream ops = null;
        try {
            ops = new ObjectOutputStream(new FileOutputStream("Object.dat"));
            ops.writeObject(new String("hello world"));
            ops.flush();
            ops.writeObject(new Person("laowang",15));
            ops.flush();
            ops.writeObject(new Person("xiaoming",24,new Account(15)));
            System.out.println("存储成功");
        }catch(IOException e){
            e.printStackTrace();
        }finally {
            if(ops != null){
                try {
                    ops.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    /**
     * 反序列化过程:将磁盘中的java对象输出到内存的层面,使用ObjectInputStream实现
     */
    @Test
    public void test2(){
        ObjectInputStream fr = null;
        try{
            fr = new ObjectInputStream(new FileInputStream("Object.dat"));
            Object str = fr.readObject();
            System.out.println((String) str);
            System.out.println((Person)fr.readObject());
            System.out.println((Person)fr.readObject());
        }catch (IOException e){
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            try {
                fr.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

## ```对自定义类使用序列化和反序列化

```java
package IO;

import java.io.Serializable;

/**
 *  person类需要满足以下要求,方可序列化
 *  1.需要实现serializable接口
 *  2.需要当前类提供一个常量:serialVersionUID
 *  3.除了当前类需要实现Seriavalizable接口之外,还必须保证其内部所有属性也必须是可序列化的。(默认情况下基本数据类型可序列化)
 */
public class Person implements Serializable {
    private String name;
    private int age;
    private Account count;
    //序列版本号
    public static final long serialVersionUID = 4651365245l;
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", 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;
    }

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

    public Person() {
    }

    public Person(String name, int age, Account count) {
        this.name = name;
        this.age = age;
        this.count = count;
    }
}
class Account implements Serializable{
    public static final long serialVersionUID = 13511531l;
    int banlance;
    public Account(int banlance){
        this.banlance = banlance;
    }

    public static long getSerialVersionUID() {
        return serialVersionUID;
    }

    public int getBanlance() {
        return banlance;
    }

    public void setBanlance(int banlance) {
        this.banlance = banlance;
    }

    @Override
    public String toString() {
        return "Account{" +
                "banlance=" + banlance +
                '}';
    }
}

序列化和反序列化

  1. 序列化就是在保存数据时,保存数据的值和数据类型
  2. 反序列化就是在恢复数据时,恢复数据的值和数据类型
  3. 需要让某个对象支持序列化机制,则必须让其类是可序列化的,为了让某个类是可序列化的,该类必须实现如下两个接口之一:
    1. Serializable//这是一个标记接口
    2. Externalizable//,一般使用上边的接口
  4. 序列化的类中建议添加 SerialVersionUID,为了提高版本的兼容性
  5. 序列化对象时,默认将里面所有属性都进行序列化,但除了static或transient修饰的成员
  6. 序列化对象时,要求里面属性的类型也需要实现序列化接口
  7. 序列化具备可继承性,也就是如果某类已经实现了序列化,则它的所有子类也已经默认实现了序列化
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值