【java基础】IO流

137 阅读10分钟

I\O流

一、流的概念

流是指内存和存储设备之间建立的数据传输的通道。

数据通过该通道进行传输,所以也可以说数据通过流传输。

二、分类

2.1 按版本分类

按照JDK的版本不同,把流分为三个阶段的内容:

  • BIO:基本的二进制流(JDK1.0)同步阻塞
  • NIO:新的流
  • AIO:异步流
2.2 按方向分类

按方向:基础分类,针对于内存。

  • 输入(Input)流:把数据从存储设备传输到内存。读
  • 输出(Output)流:把数据从内存传输到存储设备。写
2.3 按单位分类

按单位:

  • 字节流:以字节为单位,适合所有的文件类型。
  • 字符流:以字符为单位,适合于文本文件。封装了一些适合于文本的操作方法。
2.4 按功能分类

按功能:

  • 节点流:普通的数据传输
  • 过滤流:在节点流的基础上增强型数据传输

三、字节流的使用

字节流的基本父类:(抽象类)

  • InputStream:read()
  • OutputStream:write()

节点流:

  • FileInputStream:
  • FileOutputStream:
public class Test1 {
    public static void main(String[] args) throws IOException {
        // 读取文件内容
        // 1、建立输入通道
        FileInputStream fis = 
                new FileInputStream("C:\Users\Desktop\1.txt");
        // 2、读取文件
        // 不合理,不知道长度,应该使用while判断是否-1
//      for (int i = 0; i < 10; i++) {
//          int b = fis.read(); // 读取一个字节
//          System.out.println(b);
//      }
        // 当读取到最后时,再读取会得到-1
        // read不仅会读取一个字节,而且还会向前移动一位
        // 注意:一般情况下,流的操作会从第一个字节开始读取,且仅向前
        int b;
        while((b = fis.read()) != -1) {
            System.out.print((char)b);
        }
        
        // 3、关闭通道
        fis.close();// 正确写法,应该写在finally中
    }
}

public class Test2 {
    public static void main(String[] args) throws IOException {
        // 写入文件
        // 1、创建通道
        FileOutputStream fos = 
                new FileOutputStream("C:\Users\Desktop\2.txt");
        // 2、准备要写入的内容
        String content = "java";
        // 3、写入内容
        fos.write(content.getBytes());
        // 清空缓冲区
        fos.flush();
        // 4、关闭通道
        fos.close();
    }
}

public class Test3 {
    public static void main(String[] args) throws IOException{
        // 使用字节流复制图片
        // 1、创建读写的通道
        FileInputStream fis = 
                new FileInputStream("C:\Users\Desktop\img\8.jpg");
        FileOutputStream fos = 
                new FileOutputStream("C:\Users\Desktop\8.jpg");
        // 2、读写内容
        // 每次读写一个字节,性能不好
//      long s = System.currentTimeMillis();
//      int b;
//      while((b = fis.read()) != -1) {
//          fos.write(b);
//      }
//      long e = System.currentTimeMillis();
//      System.out.println(e-s);
        // 读取多个字节再写入
        long s = System.currentTimeMillis();
        byte [] buffer = new byte[1024]; // 创建一个字节数组,1kb大小
        int len; // 每次读取有效长度
        while((len = fis.read(buffer)) != -1) {
            fos.write(buffer, 0, len); // 写入有效内容
        }
        long e = System.currentTimeMillis();
        System.out.println(e-s);
        // 3、关闭通道
        fis.close();
        fos.close();
    }
}

四、异常在流中的处理方式

当需要在流中处理异常,并在finally中关闭流时,可以使用以下方式:


public class Test2 {
    public static void main(String[] args)  {
        // 写入文件
        // 1、创建通道
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream("C:\Users\Desktop\2.txt");
            // 2、准备要写入的内容
            String content = "Java";
            // 3、写入内容
            fos.write(content.getBytes());
            // 清空缓冲区
            fos.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            // 4、关闭通道
            if(fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

上面的代码比较麻烦,在JDK的新版本(JDK1.7)有提供自动关闭的方式,如下:


public class Test2 {
    public static void main(String[] args){
        try (
                // 1、创建通道
                FileOutputStream fos = 
                        new FileOutputStream("C:\Users\Desktop\2.txt");
                ){
            // 写入文件
            // 2、准备要写入的内容
            String content = "Java";
            // 3、写入内容
            fos.write(content.getBytes());
            // 清空缓冲区
            fos.flush();
            // 会自动关闭通道
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

注意:在try的小括号中,不能随意定义变量,需要变量的类型实现了自动关闭接口,以便在代码执行完毕后自动关闭。

五、字节过滤流

5.1 缓冲流

BufferedInputStream:自带8kb的缓冲

BufferedOutputStream:自带8kb的缓冲

缓冲流是属于过滤流,不能独立完成IO操作,需要在创建对象时传入字节节点流,只是在该节点流操作文件的过程中进行增强,此处就是添加了缓冲的功能,减少频繁的IO操作。


public class Test4 {
    public static void main(String[] args) {
        try (
                // 1、创建读写的通道
                FileInputStream fis = 
                        new FileInputStream("C:\Users\Desktop\img\8.jpg");
                FileOutputStream fos = 
                        new FileOutputStream("C:\Users\Desktop\8.jpg");
                // 创建一个带缓冲的字节输入流(增强)
                BufferedInputStream bis = new BufferedInputStream(fis);
                // 创建一个带缓冲的字节输出流(增强)
                BufferedOutputStream bos = new BufferedOutputStream(fos);
                ){
            // 使用字节流复制图片
            // 2、读写内容
            long s = System.currentTimeMillis();
            int b;
            while((b = bis.read()) != -1) {
                bos.write(b);
            }
            long e = System.currentTimeMillis();
            System.out.println(e-s);
            
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e1) {
            e1.printStackTrace();
        }
    }
}
5.2 对象流
5.2.1 基本使用

ObjectInputStream和ObjectOutputStream,作用是将对象直接写入到文件中,以及在文件中直接读取对象,称为序列化和反序列化。


public class Student implements Serializable{
    /**
     * 序列化编号
     */
    private static final long serialVersionUID = -7252210178194837068L;
    
    private String id;
    private String name;
    private String sex;
    private String phone;
    public Student(String id, String name, String sex, String phone) {
        super();
        this.id = id;
        this.name = name;
        this.sex = sex;
        this.phone = phone;
    }
    public Student() {
        super();
    }
    public String getId() {
        return id;
    }
    public void setId(String 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;
    }
    public String getPhone() {
        return phone;
    }
    public void setPhone(String phone) {
        this.phone = phone;
    }
    @Override
    public String toString() {
        return "Student [id=" + id + ", name=" + name + ", sex=" + sex + ", phone=" + phone + "]";
    }
}

public class Test5 {
    public static void main(String[] args) {
        // 序列化,将对象写入文件
        // 1、创建对象写入的通道
        try (
                ObjectOutputStream oos = 
                    new ObjectOutputStream(new FileOutputStream("C:\Users\Desktop\bbb.txt"));
                ){
            // 2、创建需要写入的对象
            Student stu1 = new Student("1001", "zhangsan", "male", "18866554433");
            oos.writeObject(stu1);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

public class Test6 {
    public static void main(String[] args) {
        // 反序列化,将文件读取到对象中
        // 1、创建对象读取的通道
        try (
                ObjectInputStream ois = 
                    new ObjectInputStream(new FileInputStream("C:\Users\Desktop\bbb.txt"));
                ){
            // 2、将文件读取到对象中
            Student stu1 = (Student) ois.readObject();
            System.out.println(stu1);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

解惑:序列化接口中为什么没有方法?

如果需要方法,方法的作用应该是将属性和值进行对应,然后在文件中操作。但是Java中的反射机制能够自动扫描类中的属性名称,并读取和写入其中的值,所以Java中序列化操作可以由反射实现,不需要人工添加代码。

private static final long serialVersionUID = -7252210178194837068L;

序列号:

序列化和反序列化时,要求版本号(序列号)应该一致,避免出现版本问题。如果不一致,会报错。

5.2.2 transient关键字用法

当该关键字修饰属性时,表示该属性不参与序列化和反序列化。

六、字符流

字符流的父类:(抽象类)

  • Reader:读,read(char [] c)
  • Writer:写,write(String str)

节点流:

  • FileReader
  • FileWriter

public class Test7 {
    public static void main(String[] args) {
        try (
                // 写入文本
                // 1、创建字符流
                FileWriter fw = new FileWriter("C:\Users\Desktop\1.txt");
                ){
            fw.write("千锋我最强,千锋我最棒");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

public class Test8 {
    public static void main(String[] args) {
        try (
                // 读取文本
                // 1、创建字符流
                FileReader fw = new FileReader("C:\Users\Desktop\1.txt");
                ){
            char [] buffer = new char[1024];
            int len;
            while((len = fw.read(buffer)) != -1) {
                String str = new String(buffer, 0, len);
                System.out.println(str + "!!!");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

七、字符过滤流

缓冲流:

  • BufferedReader:添加了读取一行的方法
  • BufferedWriter:添加了换行的方法

public class Test9 {
    public static void main(String[] args) {
        // 使用字符缓冲流写入文件
        try (
                BufferedWriter bw = new BufferedWriter(new FileWriter("C:\Users\Desktop\1.txt"));
                ){
            bw.write("java我最强,");
            bw.newLine(); // 在文件中换行
            bw.write("java我最棒");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

public class Test10 {
    public static void main(String[] args) {
        // 使用字符缓冲流读取文件
        try (
                BufferedReader br = new BufferedReader(new FileReader("C:\Users\Desktop\1.txt"));
                ){
            String str;
            // 每次读取一行
            while((str = br.readLine()) != null) {
                System.out.println(str);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

八、转换流

将字节流转换成字符流。

InputStreamReader:

OutputStreamWriter:


public class Test11 {
    public static void main(String[] args) {
        try (
                // 模拟接收一个字节流
                FileInputStream fis = new FileInputStream("C:\Users\Desktop\1.txt");
                // 将字节流通过转换流转换成字符流
                InputStreamReader reader = new InputStreamReader(fis, "gb2312");
                // 将字符流封装成字符过滤流
                BufferedReader br = new BufferedReader(reader);
                ){
            String str;
            // 每次读取一行
            while((str = br.readLine()) != null) {
                System.out.println(str);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

九、文件操作

操作文件夹或者文件本身,对文件夹或者文件进行遍历、创建、删除、重命名等操作。

File类,代表一个文件夹或者文件。

  • createNewFile()//创建一个新文件
  • mkdir()//创建一个新目录,要求路径中包含的前置目录必须存在 make directory
  • mkdirs()//创建一个新目录,会同时创建前置目录中要求的所有文件夹
  • delete()//删除文件或空目录 ,如果删除的是文件夹,必须要先删除该文件夹中的所有内容,才能删除该空文件夹。
  • exists()//判断File对象所对象所代表的对象是否存在
  • getAbsolutePath()//获取文件的绝对路径 ,包含盘符的。
  • getName()//取得名字
  • getParent()//获取文件/目录所在的目录
  • isDirectory()//是否是目录
  • isFile()//是否是文件
  • length()//获得文件的长度 ,单位:字节
  • listFiles()//列出当前目录中的所有内容
  • renameTo()//修改文件名为

案例:复制一个文件夹中及其中的所有内容。

public class Test12 {
    public static String srcDir = "C:\Users\Desktop\img"; // 源文件夹
    public static String destDir = "D:\mypic\img";
    
    public static void main(String[] args) {
        // 复制一个文件夹中的所有内容
        try {
            // 创建一个文件对象
            File file = new File(srcDir);
            // 创建一个目标文件夹对象
            File destFile = new File(destDir);
            // 如果目标文件夹不存在
            if(!destFile.exists()) {
                // 创建文件夹
                destFile.mkdirs();
            }
            // 复制文件夹
            copyDirectory(file);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public static void copyDirectory(File file) {
        // 判断是否文件夹
        if(file.isDirectory()) {
            // 得到其中的所有文件
            File[] listFiles = file.listFiles();
            for (File file2 : listFiles) {
                if(file2.isFile()) {
                    String newFilePath = destDir + "\" + file2.getName(); // 获得文件路径
                    // 复制文件
                    copyFile(file2, newFilePath);
                }
            }
        }
    }
​
    private static void copyFile(File srcFile, String newFilePath) {
        try (
                BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcFile));
                BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(newFilePath));
                ){
            int len;
            while((len = bis.read()) != -1) {
                bos.write(len);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

递归复制:


public class Test12 {
    public static String srcDir = "C:\Users\Desktop\img"; // 源文件夹
    public static String destDir = "D:\mypic\img";
    
    public static void main(String[] args) {
        // 复制一个文件夹中的所有内容
        try {
            // 创建一个文件对象
            File file = new File(srcDir);
            // 创建一个目标文件夹对象
            File destFile = new File(destDir);
            // 如果目标文件夹不存在
            if(!destFile.exists()) {
                // 创建文件夹
                destFile.mkdirs();
            }
            // 复制文件夹
            copyDirectory(file, destFile);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public static void copyDirectory(File file, File destFile) {
        // 判断是否文件夹
        if(file.isDirectory()) {
            // 得到其中的所有文件
            File[] listFiles = file.listFiles();
            for (File file2 : listFiles) {
                if(file2.isFile()) {
                    String newFilePath = destFile.getPath() + "\" + file2.getName(); // 获得文件路径
                    // 复制文件
                    copyFile(file2, newFilePath);
                }else {
                    // 如果目标文件夹不存在
                    String newDir = destFile.getPath() + "\" + file2.getName();
                    File newFile = new File(newDir);
                    if(!newFile.exists()) {
                        // 创建文件夹
                        newFile.mkdirs();
                    }
                    // 复制文件夹
                    copyDirectory(file2, newFile);
                }
            }
        }
    }
​
    private static void copyFile(File srcFile, String newFilePath) {
        try (
                BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcFile));
                BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(newFilePath));
                ){
            int len;
            while((len = bis.read()) != -1) {
                bos.write(len);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

注意:Java中的路径符号处理问题:

1、windows路径使用\,需要转义,但是其他系统使用/

2、在windows系统中路径也可以使用/

3、在Java代码中应该使用File.separator来表示路径符号,而不应该直接硬编码。

文件递归复制:


public class Test13 {
    // 将a文件夹中的所有内容,复制到a1文件夹中
    public static final String srcPath = "C:/Users/Desktop/a";
    public static final String destPath = "C:/Users/Desktop/a1";
    
    public static void main(String[] args) {
        copy(srcPath, destPath);
    }
​
    /**
     * 复制文件夹
     * @param src
     * @param dest
     */
    private static void copy(String src, String dest) {
        // 源文件夹
        File srcDir = new File(src);
        File destDir = new File(dest);
        // 判断目标文件夹是否存在,如果不存在,则创建文件夹
        if(!destDir.exists()) {
            destDir.mkdirs();
        }
        if(srcDir.exists()) {
            // 打开文件夹,获取该文件夹里面的所有内容
            File[] files = srcDir.listFiles();
            for (File file : files) {
                if(file.isFile()) {
                    copyFile(file, dest + File.separator + file.getName());
                }else {
                    copy(file.getPath(), dest + File.separator + file.getName());
                }
            }
        }
    }
​
    private static void copyFile(File file, String string) {
        try (
                BufferedInputStream bis = new BufferedInputStream(
                        new FileInputStream(file));
                BufferedOutputStream bos = new BufferedOutputStream(
                        new FileOutputStream(string));
                ){
            int len;
            while((len = bis.read()) != -1) {
                bos.write(len);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

案例:创建文件,获得文件大小,然后删除该文件。


public class Test14 {
    public static void main(String[] args) {
        // 创建文件,写入内容,获得文件大小,重命名该文件,然后删除该文件。
        File file = new File("C:\Users\Desktop\1.txt");
        if(!file.exists()) {
            // 创建文件
            try {
                file.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        try (
                BufferedWriter writer = new BufferedWriter(
                        new FileWriter(file));
                ){
            writer.write("我是亚索,号称风一样的男子。");
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 得到文件大小
        System.out.println(file.length());
        // 删除文件
        file.delete();
    }
}

案例:删除文件夹(文件)。


public class Test15 {
    // 删除文件(文件夹)
    public static final String path = "C:/Users/Desktop/a1";
    
    public static void main(String[] args) {
        // 根据路径创建文件
        File destFile = new File(path);
        delete(destFile);
    }
​
    /**
     * 复制文件夹
     * @param src
     * @param dest
     */
    private static void delete(File destFile) {
        if(destFile.exists()) {
            if(destFile.isDirectory()) {
                // 打开文件夹,获取该文件夹里面的所有内容
                File[] files = destFile.listFiles();
                for (File file : files) {
                    delete(file);
                }
            }
            destFile.delete();
        }
    }
}

十、文件过滤

在遍历文件夹内容时,可以根据规则过滤其中的内容,将内容处理。


public class Test16 {
    // 删除文件(文件夹)
    public static final String path = "C:/Users/Desktop/img";
    
    public static void main(String[] args) {
        File file = new File(path);
        // 遍历文件夹中所有文件的名称
        String[] list = file.list();
        for (String string : list) {
            System.out.println(string);
        }
        
        // 通过文件名过滤
        String[] list1 = file.list(new FilenameFilter() {
            // dir表示上级目录,name表示当前文件名称
            @Override
            public boolean accept(File dir, String name) {
                // 当文件名称为jpg结尾,就返回true
                if(name.endsWith(".jpg")) {
                    return true;
                }
                return false;
            }
        });
        for (String string : list1) {
            System.out.println(string);
        }
        
        // 通过文件过滤
        File[] files = file.listFiles(new FileFilter() {
            // 当File是文件时,打印输出,如果是文件夹,则不打印
            @Override
            public boolean accept(File pathname) {
                if(pathname.isFile()) {
                    return true;
                }
                return false;
            }
        });
        
        for (File file2 : files) {
            System.out.println(file2.getName());
        }
    }
}

案例:复制所有的笔记md文件。


public class Test17 {
    // 将a文件夹中的所有内容,复制到a1文件夹中
    public static final String srcPath = "D:\mark";
    public static final String destPath = "C:/Users/Desktop/a1";
    
    public static void main(String[] args) {
        copy(srcPath, destPath);
    }
​
    /**
     * 复制文件夹
     * @param src
     * @param dest
     */
    private static void copy(String src, String dest) {
        // 源文件夹
        File srcDir = new File(src);
        File destDir = new File(dest);
        // 判断目标文件夹是否存在,如果不存在,则创建文件夹
        if(!destDir.exists()) {
            destDir.mkdirs();
        }
        if(srcDir.exists()) {
            // 打开文件夹,获取该文件夹里面的所有内容
            File[] files = srcDir.listFiles(new FileFilter() {
                @Override
                public boolean accept(File pathname) {
                    // 所有的文件夹,需要复制,返回true
                    if(pathname.isDirectory()) {
                        return true;
                    }
                    // 所有的文件,如果是md文件,也需要复制,返回true
                    if(pathname.getName().endsWith(".md")) {
//                      if(pathname.getParentFile().getName().equals("预习笔记")) {
//                          return false;
//                      }
//                      if(pathname.getParent().endsWith("预习笔记")) {
//                          return false;
//                      }
                        return true;
                    }
                    return false;
                }
            });
            for (File file : files) {
                if(file.isFile()) {
                    copyFile(file, dest + File.separator + file.getName());
                }else {
                    copy(file.getPath(), dest + File.separator + file.getName());
                }
            }
        }
    }
​
    private static void copyFile(File file, String string) {
        try (
                BufferedInputStream bis = new BufferedInputStream(
                        new FileInputStream(file));
                BufferedOutputStream bos = new BufferedOutputStream(
                        new FileOutputStream(string));
                ){
            int len;
            while((len = bis.read()) != -1) {
                bos.write(len);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}