java基础--IO

97 阅读7分钟

I/O

流:字节序列的抽象概念

流的分类:

  1. 按数据流的流向(站在程序的角度看)
    • 输入流
    • 输出流
  2. 按操作数据的单位
    • 字节流 一个字节一个字节的读取 1 byte
    • 字符流 一个字符一个字符的读取 2 byte

IO流应用场景:

  1. 纯文本文件,优先使用字符流
  2. 图片、视频、音频等二进制文件,优先使用字节流
  3. 不确定文件类型,优先使用字节流,字节流是万能的流[推荐]

字节流

有2个抽象基类 InputStreamOutputStream,分别处理字节流的输入和输出。

InputStream 提供的方法

方法签名描述备注
int available()返回可从此输入流读取(或跳过)而不会因下次调用此输入流的方法而阻塞的字节数的估计值。常用
void close()关闭此输入流并释放与该流关联的所有系统资源。常用;读取流占用系统资源,使用完必须关闭
abstract int read()从输入流中读取下一个字节的数据。
int read(byte[] b)从输入流中读取一定数量的字节,并将它们存储到缓冲区数组b中。常用
int read(byte[] b, int off, int len)将最多len字节的数据从输入流读入字节数组。

OutputStream 提供的方法

方法签名描述备注
void close()关闭此输出流并释放与此流关联的所有系统资源。
void flush()刷新此输出流并强制写出任何缓冲的输出字节。
void write(byte[] b)将b.length字节从指定的字节数组写入此输出流。
void write(byte[] b, int off, int len)将b.length字节从指定的字节数组写入此输出流。

InputStreamOutputStream 都是抽象类,要使用他们,必须用其子类。

inputstream类图.png

outputstream类图.png

FileInputStream 和 FileOutputStream

这2个类是最常用的了;

字节流读写文件标准写法:

public static void customBufferStream(){
        // 因为在finally语句中将关闭流,所以将其定义在try之外
        FileInputStream fileInputStream = null;
        FileOutputStream fileOutputStream = null;

        try {
            // 创建一个文件输入流
            fileInputStream = new FileInputStream(new File("/Users/hlw/Desktop/111.png"));
            // 创建一个文件输出流
            fileOutputStream = new FileOutputStream(new File("/Users/hlw/Desktop/111_copy.png"));
            // 读取的字节长度
            int len;
            // 定义缓冲区大小
            byte[] buffer = new byte[1024];

            long start = System.currentTimeMillis();
            // 将输入流读取到 buffer 中,如果读到的长度不是-1,
            while ((len = fileInputStream.read(buffer)) != -1){
                // 将buffer 写入输出流
                fileOutputStream.write(buffer, 0, len);
            }
            long end = System.currentTimeMillis();
            System.out.println("use time [ " + (end - start) + " ] ms");

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 关闭2个流
            if(fileInputStream != null){
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if(fileOutputStream != null){
                try {
                    fileOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }
    }

ObjectInputStream 和 ObjectOutputStream

读取对象,作为字节流。例如,将内存中的对象转换为二进制数据流的形式输出,保存到硬盘,这叫作对象的序列化。通过将对象序列化,可以方便地实现对象的传输和保存。


    static class Person implements Serializable{
        String name;
        int age;

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

        // ... 省略name age getter setter 方法
    }

    public static void objectSerilized(){
        // 对象序列化
        Person p = new Person("hlw", 12);
        System.out.println(p.hashCode());
        
        try {
            // 创建文件输出流
            FileOutputStream fileOutputStream = new FileOutputStream(new File("/Users/hlw/Desktop/person.dat"));
            // 包装成对象输出流
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
            // 写对象
            objectOutputStream.writeObject(p);
            objectOutputStream.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }


        // 反序列化

        try {
            // 创建文件输入流
            FileInputStream fileInputStream = new FileInputStream(new File("/Users/hlw/Desktop/person.dat"));
            // 包装成对象输入流
            ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
            // 读对象,并类型转换
            p = (Person) objectInputStream.readObject();

            System.out.println(p.hashCode() + "name:" + p.getName() + " age:" + p.getAge());

            objectInputStream.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

输出如下

1996181658
1327763628name:hlw age:12

Process finished with exit code 0
  • 序列化前和反序列化后,不是同一个对象。
  • 被存储和被读取的对象都必须实现 java.io.Serializable 接口,否则会报 NotSerializableException 异常。

DataInputStream和DataOutputStream

将对象中的一部分数据进行序列化和反序列化的类

    public static void testDateStream(){

        try {
            FileOutputStream fileOutputStream = new FileOutputStream(new File("/Users/hlw/Desktop/data.txt"));
            DataOutputStream dataOutputStream = new DataOutputStream(fileOutputStream);

            dataOutputStream.write(10);
            dataOutputStream.writeChar('a');
            dataOutputStream.writeInt(10);
            dataOutputStream.writeBoolean(true);
            dataOutputStream.writeUTF("你好");

            FileInputStream fileInputStream = new FileInputStream(new File("/Users/hlw/Desktop/data.txt"));
            DataInputStream dataInputStream = new DataInputStream(fileInputStream);

            System.out.println(dataInputStream.read());
            System.out.println(dataInputStream.readChar());
            System.out.println(dataInputStream.readInt());
            System.out.println(dataInputStream.readBoolean());
            System.out.println(dataInputStream.readUTF());

            dataOutputStream.close();
            dataInputStream.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

读取的时候顺序一定要和写入一致,否则报错

PrintStream

打印流
虽然见的很少,但却一直在用它,常见的 System.out.println(); 这个 out 就是 PrintStream

    public static void testPrintStream(){
        try {
            FileOutputStream fileOutputStream = new FileOutputStream(new File("/Users/hlw/Desktop/111.txt"), true);
            PrintStream out = new PrintStream(fileOutputStream);

            out.println("printStream 你好呀");
            out.println(1);
            out.println('a');
            out.println(1.2);
            out.println(true);
            out.println(1.20f);

            out.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }

字符流

也有两个抽象基类,分别是 ReaderWriter ,分别用来读取和写入字符

Reader 常用方法

方法签名描述备注
abstract void close()关闭流并释放与之关联的所有系统资源。
int read()读取单个字符。
int read(char[] cbuf)将字符读入数组。
boolean ready()告诉该流是否准备好被读取。

Writer 常用方法

方法签名描述备注
abstract void close()关闭这个流,先flush
abstract void flush()冲洗该流,将缓冲区数据刷新到磁盘(个人理解,有误感谢指出)
void write(char[] cbuf)写入字符数组。
void write(int c)写入单个字符
void write(String str)写入一个字符串

子类实现

reader类图.png

writer类图.png

FileReader和FileWriter

FileReaderFileWriter 用来从文件中读取字符,写入字符

    public static void testReader(){

        FileReader fileReader = null;
        FileWriter fileWriter = null;
        try {
            fileReader = new FileReader(new File("/Users/hlw/Desktop/111.txt"));
            fileWriter = new FileWriter(new File("/Users/hlw/Desktop/111_copy.txt"));

            int zhar;
            // 每次读取一个字符
            while ((zhar = fileReader.read()) != -1){
                System.out.println((char) zhar);
                // 写入一个字符
                fileWriter.write(zhar);
            }

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(fileReader != null){
                try {
                    fileReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
            if(fileWriter != null){
                try {
                    fileWriter.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }

        }
    }

BufferedReader 和BufferedWriter

带缓冲区的字符流,提高读写效率

    public static void testBufferedReader(){
        BufferedReader bufferedReader = null;
        BufferedWriter bufferedWriter = null;

        try {
            FileReader fileReader = new FileReader(new File("/Users/hlw/Desktop/111.txt"));
            bufferedReader = new BufferedReader(fileReader);

            FileWriter fileWriter = new FileWriter(new File("/Users/hlw/Desktop/111_copy.txt"));
            bufferedWriter = new BufferedWriter(fileWriter);

            String line;
            // 可以读一行字符
            while ((line = bufferedReader.readLine()) != null){
                System.out.println(line);
                // 写入字符
                bufferedWriter.write(line);
                // 换行
                bufferedWriter.newLine();
            }

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(bufferedReader != null){
                try {
                    bufferedReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(bufferedWriter != null){
                try {
                    bufferedWriter.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

LineNumberReader

可以跟踪行号的流

    public static void testLineNumberReader(){
        LineNumberReader lineNumReader = null;
        BufferedWriter bufferedWriter = null;

        try {
            FileReader fileReader = new FileReader(new File("/Users/hlw/Desktop/111.txt"));
            // 使用 LineNumberReader 包装流
            lineNumReader = new LineNumberReader(fileReader);

            FileWriter fileWriter = new FileWriter(new File("/Users/hlw/Desktop/111_copy.txt"));
            bufferedWriter = new BufferedWriter(fileWriter);

            String line;
            // 可以读一行字符
            while ((line = lineNumReader.readLine()) != null){
                System.out.println("当前行:" + lineNumReader.getLineNumber() + " : " + line);
                // 写入字符
                bufferedWriter.write(line);
                // 换行
                bufferedWriter.newLine();
            }

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(lineNumReader != null){
                try {
                    lineNumReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(bufferedWriter != null){
                try {
                    bufferedWriter.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

InputStreamReader 和 OutputStreamWriter

转换流: 实质就是输入对象类型是 InputStream ,前面的 fileReader ,输入对象类型是文件类型的。
在某种情况下,字节流需要与字符流互相转换,就用到这2个流

InputStreamReader:接收InputStream,读取到 字符流 Reader
OutputStreamWriter:接收OutputStream,输出到 字节流

            FileInputStream fileInputStream = new FileInputStream(new File("/Users/hlw/Desktop/111.txt"));
            // 也就是输入对象 是 InputStream
            // 这里接收文件流
            InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream);

            FileOutputStream fileOutputStream = new FileOutputStream(new File("/Users/hlw/Desktop/111_copy.txt"));
            OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream);

            int size;
            char[] cahrs = new char[1024];
            while ((size = inputStreamReader.read(cahrs)) != -1){
                outputStreamWriter.write(cahrs);
            }
            inputStreamReader.close();
            outputStreamWriter.close();

File

可代表文件,也可代表目录

file常见方法

方法签名描述备注
boolean delete()删除此抽象路径名表示的文件或目录。
boolean exists()测试此抽象路径名表示的文件或目录是否存在。
String getParent()返回此抽象路径名的父目录的路径名字符串,如果此路径名没有命名父目录,则返回null。
File getParentFile()返回此抽象路径名的父目录的抽象路径名,如果此路径名没有命名父目录,则返回null。
boolean isDirectory()测试此抽象路径名表示的文件是否为目录。
long length()返回由抽象路径名表示的文件长度。
String[] list()返回一个字符串数组,该字符串数组命名此抽象路径名所表示的目录中的文件和目录
File[] listFiles()返回一个抽象路径名数组,该数组表示由该抽象路径名表示的目录中的文件。
boolean mkdir()创建按此抽象路径名命名的目录。
boolean mkdirs()创建以此抽象路径名命名的目录,包括任何必要但不存在的父目录。
boolean renameTo(File dest)重命名这个抽象路径名表示的文件。
File[] listFiles(FilenameFilter filter)返回一个抽象路径名数组,表示此抽象路径名表示的目录中满足指定筛选器的文件和目录。

删除文件及目录

    public static boolean delDirOrFile(@NotNull File file){
        if(file.isDirectory()){
            File[] files = file.listFiles();
            if(files != null){
                for(File subFile : files){
                    delDirOrFile(subFile);
                }
            }
        }

        return file.delete();
    }

文件过滤

    public static boolean acceptFile(File dir, String name){
        String[] split = name.split("\\.");
        if(split.length == 2 && "sh".equals(split[1])){
            return true;
        }
        return false;
    }

    public static void testFileFilter(){
        File file = new File("/Users/hlw/Desktop");
        if(file.isDirectory()){
            String[] list = file.list(BufferStreamTest::acceptFile);

            for(String name : list){
                System.out.println(name);
            }
        }
    }

参考文献

  1. blog.csdn.net/m0_55755339…
  2. zhuanlan.zhihu.com/p/574292062
  3. docs.oracle.com/javase/8/do…