java的IO流简单汇总

962 阅读14分钟

写在前面

  • 对于java中IO流这部分的内容,一眼望过去就一个字:多。而且这部分内容在web开发中可能不是经常使用,即使要用,可能都是使用各种工具类或者是框架了。但是你不会呢又感觉说不过去。哎,难受。 这意味着即使现在清楚了,以后肯定会淡忘啊,要不简单写个小总结,以后忘了但又需要的时候也可以查阅?嗯,就这么搞吧。
  • 阅读须知:对IO的一个简单汇总,部分地方的详情请参照API。

一、File类

1.1 基础认识

  • 类: java.io.File,是实体文件的一个抽象形式。
  • 静态常量:
    • static String pathSeparator -> 与系统有关的路径分隔符,eg: 分号。
    • static String separator -> 与系统相关的默认分割符, eg: / \
  • 构造方法:
    • public File(String pathname): 直接文件路径(相对|绝对)
    • public File(String parent , String child): 需要在哪里创建什么文件。
    • public File(File parent , String child): 同上,只不过第一个参数类型不同。

1. 2 使用

  • 注意点: 皆是对象思想,故使用前一定是创建File对象。
  • 创建方法:
    • public boolean createNewFile() : 该名称的文件不存在时,创建一个新的空文件
    • public boolean mkdir() :创建单个目录
    • public boolean mkdirs():创建由此File对象表示的目录,包括任何必需但不存在的父级目录
  • 获取方法:
    • public String getAbsolutePath(): 获取File表示的绝对路径
    • public String getPath(): 将File对象转为路径名字的字符串,创建时是啥就是啥
    • public String getName(): 获取指定文件或者文件夹的名字
    • public long length(): 获取的是指定文件的长度(字节)
    • public File getParentFile(): 获取指定文件或者目录的父路径,没有父路径则返回null
  • 判断方法:
    • public boolean exists() : 此File对象代表的文件是否存在
    • public boolean isDirectory() : 判断是否为文件夹
    • public boolean isFile(): 判断是否为文件
  • 删除方法:
    • public boolean delete(): 删除此对象表示的文件或者文件夹
      • 操作需知: ① 删除文件夹的时候只能是空的文件夹
      • ② 删除之后的文件或者文件夹是不走回收站的,小心使用
  • 遍历方法:
    • public String[] list() : 获取指定文件夹路径下的文件夹及文件的名字组成的字符串数组
    • public File[] listFiles(): 获取的是指定文件夹路径下的文件夹及文件组成的文件对象数组

1.3 统计文件夹大小的案例

private static long countFileSize(File file) {
        File[] files = file.listFiles();
        // 为空直接返回
        if (files.length == 0 ){
            return 0;
        }
        long size = 0;
        for (File f : files) {
            if (f.isDirectory()){
                size+=countFileSize(f);
            }else {
                size+=f.length();
            }
        }
        return size;
    }

二、IO总述与文件流

2.1 序

  • 分类:

    • 以操作的数据单位来分: 操作8bit的是字节流,操作16bit的是字符流
    • 以流向来分: 输入流,输出流(我们一般以运行内存为基准)
    • 以流所扮演的角色来分:节点流,处理流
  • 这里就简单汇总一部分:

    分类 字节输入流 字节输出流 字符输入流 字符输出流
    抽象基类 InputStream OutputStream Reader Writer
    文件流 FileInputStream FileOutputStream FileReader FileWriter
    缓冲流 BufferedInputStream BufferedOutputStream BufferedReader BufferedWriter
    转换流 —————— —————— InputStreamReader OutputStreamWriter
    序列化流 ObjectInputStream ObjectOutputStream —————— ——————
  • 接下来,就是具体详情。

2.2 字符流

  • 对于非文本类,不能使用字符流去操作。因为你在读的时候是使用默认编码去以字符为单位读取,但是其中难免有不认识的字节(因为我是非文本),所以读的时候就会出现乱码了。
  • 基类:Reader和Writer
  • 常用方法:
    • int read() : 读方法,按一个一个字符读取返回值为int
    • int read(char[] cbuf) : 读方法, 一次读取一个字符数组,返回有效长度
    • close() : 读|写 方法,关闭流,对于写操作来说,是先刷新,再关闭。
    • void write(int c) : 写方法,一次写单个字符
    • void write(char[] cbuf) :写方法 , 一次写一个数组
    • void write(char[] cbuf, int off , int len): 写方法,写以某位置开始的有效长度为len的字符数组
    • void write(String str) : 写方法, 写一个字符串
    • void write(String str, int off , int len) : 写方法,写以某位置开始的有效长度的字符串内容
    • void flush(): 刷新缓冲区,流对象可以继续使用

2.2.1 字符输入流

  • 构造方法
    • FileReader(File file)
    • FileReader(String fileName)

2.2.2 字符输出流

  • 注: 操作的时候带了个缓冲区,所写的内容在缓冲区,需要刷新
  • 构造方法
    • FileWriter(File file)
    • FileWriter(String fileName)
    • 注:可以有第二个参数,为boolean 类型,默认为false,若显示定义为true, 则可以续写。

2.2.3 小案例

  • 这里是复制单个文件的小案例,复制文件及文件夹请移步:传送门

    public class FileWandRTest {
        public static void main(String[] args) {
            // 需要复制的文件
            File from = new File("D:\\up2\\eg.txt");
            // 需要复制到哪里
            File to = new File("D:leboy\\up");
            try {
                copyFile_char(from,to );
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        private static void copyFile_char(File from , File to) throws IOException {
            // 给赋值后的文件命名
            File copy = new File(to, "copy.txt");
            // 创建流
            FileReader readF = new FileReader(from);
            FileWriter writeF = new FileWriter(copy);
            // 定义字符数组
            char[] chars = new char[1024];
            // 定义有效长度
            int len = 0;
            while ((len = readF.read(chars))!=-1){
                // 若想观察
                //String s = new String(chars, 0, len);
                //System.out.println(s);
                writeF.write(chars,0 ,len );
            }
            // 注意: 字符流写的时候自带缓冲,flush刷新,close刷新后关闭
            // 关闭流
            writeF.close();
            readF.close();
        }
    }
    

2.3 字节流

  • 号称万能流,啥都可以操作。但是对于文本类(比如中文.txt)的操作时,你得等人家操作完,若半路杀出个程咬金去拦路,那么可能会读取到乱码,因为可能会把某些汉字字节给截断。
  • 基类:InputStream和OutputStream
  • 常用方法:与字符流何其相似,这里不做赘述。

2.3.1 字节输入流

  • 构造: 参数均为要读取文件的位置。
    • FileInputStream(File file )
    • FileInputStream(String name)

2.3.2 字节输出流

  • 构造:
    • FileOutputStream(File file)
    • FileOutputStream(String name)
    • 注:可以有第二个参数,为boolean 类型,默认为false,若显示定义为true, 则可以续写。

2.3.3 小案例

  • 这里是复制视频的小案例。

    public static void main(String[] args) throws IOException {
            File fileIn = new File("D:\\io\\test.avi");
            File fileOut = new File("D:\\io\\myCopy.avi");
            // 输入流
            FileInputStream fis = new FileInputStream(fileIn);
            // 输出流
            FileOutputStream fos = new FileOutputStream(fileOut);
            // 定义字节数组
            byte[] glass = new byte[1024];
            // 定义字节长度
            int len = 0;
            // 开始
            while ((len = fis.read(glass))!=-1){
                // 只获取有效长度
                fos.write(glass,0,len);
            }
            // 关闭流
            fos.close();
            fis.close();
        }
    

三、IO之处理流

  • 说明:
    • 基本的读,写,关流(先开后关)等操作与文件流是相同的,以下将不再赘述。

3.1 高效流(缓冲流)

  • 缓冲流之所以可以高效读写,是因为在创建流对象的时候,会创建一个默认大小的缓冲区数组,通过缓冲区读写,减少系统IO的次数,从而提高读写的效率。

3.1.1 字节缓冲流

  • 构造:
    • BufferedInputStream(InputStream in): 形参为抽象类,故实参应为其子类,一般我们使用FileInputStream
    • BufferedOutputStream(OutputStream out): 同理 , 一般我们使用FileOutputStream

3.1.2 字符缓冲流

  • 构造 :
    • BufferedWriter(Writer out) : 其中形参为抽象类,则传递他的子类,一般传递FileWriter
    • BufferedReader(Reader in) : 同理,传递的是Reader的子类,一般传递FileReader
  • 对于字符缓冲流特有的方法:
    • 写: void newLine() : 用于写的时候换行,字符缓冲流对象调用即可。
    • 读:String readLine() :一次读取一行, 字符缓冲流对象调用后的返回值即是读取的那一行字符串。有个注意的点:读完的时候返回的可不是-1, 而是返回null,那么在循环读取的时候条件不等于-1换为不等于null即可。

3.1.3 简单小案例

  • 需求:已知某文本文件如下内容如下:

    ​ D.低头思故乡 ​ C.举头望明月 ​ A.窗前明月光 ​ B.疑是地上霜

    请拷贝一份文件,但是拷贝的这份文件中顺序是排好的。

    实现如下:

    public class BufferedWandRTest {
        public static void main(String[] args) throws IOException {
            BufferedReader br = new BufferedReader(new FileReader("D:\\eg.txt"));
            BufferedWriter bw = new BufferedWriter(new FileWriter("D:\\egOut.txt"));
            // TreeSet 自动 是按字典顺序排
            TreeSet<String> strSet = new TreeSet<>();
            // 定义字符串用于读
            String s = null;
            while ((s=br.readLine())!=null){
                strSet.add(s);
            }
            // 遍历进行输出
            for (String str : strSet) {
                bw.write(str);
                // 换行
                bw.newLine();
            }
            // 关流
            bw.close();
            br.close();
        }
    }
    

3.2 转换流

3.2.1 序

  • 关于字符编码,若深究,篇幅不比IO流少,这里不做过多探讨,简单说明一下。
    • 计算机存储信息都是二进制,我们看到的数字,符号,汉字等等信息存入计算机叫编码,反之叫解码。此两个过程都是按照某种规则在进行,类似密码本,就是一套自然语言与二进制之间的对应规则。
  • 关于字符集,也可以称之为编码表就是上述所表达的密码本。

3.2.2 字符输入转换流

  • InputStreamReader: 是字节流通向字符流的桥梁

  • 构造

    • InputStreamReader(InputStream in): 创建一个使用默认字符集的字符流。
    • InputStreamReader(InputStream in , String charsetName): 创建一个指定字符集的字符流。
  • 简单示例

    • test_1.txt文件是保存的方式为GBK,则其编码为GBK,我们现在默认(utf-8)去读与指定去读:
    public static void main(String[] args) throws IOException {
            InputStreamReader isr_1 = new InputStreamReader(new FileInputStream("D:\\IO\\test_1.txt"));
            int r_1 = isr_1.read();
            System.out.println((char)r_1); // �
            isr_1.close();
            InputStreamReader isr_2 = new InputStreamReader(new FileInputStream("D:\\IO\\test_1.txt"),"GBK");
            int r_2 = isr_2.read();
            System.out.println((char)r_2); //我
            isr_2.close();
        }
    

3.2.2 字符输出转换流

  • OutputStreamWriter : 字符流通向字节流的桥梁

  • 构造

    • OutputStreamWriter(OutputStream in): 创建一个使用默认字符集的字符流。
    • OutputStreamWriter(OutputStream in ,String charsetName): 创建一个指定字符集的字符流。
  • 简单示例

    • 以不同的编码写入数据,默认utf-8:
        OutputStreamWriter osw_1 = new OutputStreamWriter(new FileOutputStream("D:\\IO\\test_2.txt"));
            osw_1.write("在吗?");
            osw_1.close();
            OutputStreamWriter osw_2 = new OutputStreamWriter(new FileOutputStream("D:\\IO\\test_3.txt"),"GBK");
            osw_2.write("在吗?");
            osw_2.close();
        }
    

3.3 序列化流

3.3.1 序

  • java提供一种对象序列化机制,用一个自己序列表示一个对象,包含对象的数据、对象的类型等等,字节序列写到文件后可以持久保存对象的信息。
  • 序列化:采用ObjectOutputStream将对象转换为字节
  • 反序列化:采用ObjectInputStream将字节重构为对象

3.3.2 ObjectOutputStream类

  • 构造:
    • public ObjectOutputStream(OutputStream out):形参为OutputStream抽象类,则实参应为其子类
  • 写方法:
    • public final void writeObject (Object obj): 将指定对象写出

3.3.3 ObjectInputStream类

  • 构造:
    • public ObjectInputStream(InputStream in ): 形参为InputStream抽象类,则实参应为其子类
  • 读方法:
    • public final Object readObject() : 读取一个对象

3.3.4 值得注意的点

  • 一个对象若想序列化,则必须实现java.io.Serializable接口。

  • 一个对象若想序列化,还必须存在可序列化的属性,static修饰的属性不可被序列化,若想某个属性不被序列化,专门提供了关键字修饰: transient.

  • 对象要被反序列化,有要求:jvm可以找到class文件的类

  • 反序列化要成功,还要保证反序列化操作时与序列化时的class文件是相同的,反序列化操作失败的原因有:

    • 该类的序列版本号与从流中读取的类描述符的版本号不匹配

      • Serializeable接口提供一个版本号:serialVersionUID可以验证序列化的对象和对应类是否版本匹配,serialVersionUID是一个long型的静态常量。
    • 该类包含未知数据类型

    • 该类没有可访问的无参构造方法

3.3.5 简单案例

  • 定义Student类,有姓名、性别、年龄,并实现Serializable接口后,实例化一个对象进行序列化和反序列化测试

    • Student类的部分展示:
    public class Student implements Serializable {
        // 序列版本号
        private  static final long serialVersionUID = 3141592653589L;
    
    • 序列化操作:

      // 序列化
          private static void serialized() throws Exception {
              Student stu = new Student("迪丽热巴", "女", 18);
              // 开流
              ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\IO\\test3_1.txt"));
              // 写
              oos.writeObject(stu);
              // 关流
              oos.close();
          }
      
    • 反序列化操作:

      // 反序列化
          private static void deserialized() throws Exception {
              // 开流
              ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\IO\\test3_1.txt"));
              // 读
              Object o = ois.readObject();
              // 查看
              System.out.println((Student)o);
              // 关流
          }
      

3.4 打印流

  • 平时控制台打印输出调用的print和println方法都是java.io.PrintStream类的方法,因为System.out就是PrintSream类型。
  • 构造:
    • public PrintStream(String fileName): 创建新的打印流到指定文件。
  • 使用:
    • System.setOut(PrintStream ps): 可以设置系统的打印流向

四、异常处理

  • 在实际开发中,不建议采用甩锅式上抛异常,那就需要及时处理异常,这里以FileWriter向文件输出某字符串为例子说明异常的处理。

  • JDK7之前的处理方式为try...catch...finally代码块。

     public static void main(String[] args) {
            FileWriter f = null;
            try {
                f = new FileWriter("out.txt");
                f.write("this is test!!");
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                // 若为null , 则文件创建不成功,也就无关闭之说
                if (f != null){
                    try {
                        f.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
  • JDK7及以后新增了 try-with-resource语句,该语句可以确保每个资源在语句结束时关闭。

    public static void main(String[] args) {
        // 若有多个创建流对象,可以用分号隔开
            try (FileWriter f = new FileWriter("out.txt")){
                f.write("this is test!");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    

五、其他类的使用

5.1 Properties类

  • java.util.Properties类继承于Hashtable,是一个持久的属性集,一般用于保存配置文件。
  • 构造方法: public Properties();
  • 自身特点:
    • 数据结构为哈希表,无序
    • 线程为安全,也就意味着运行速度相对慢
    • key和value均为String
    • 不允许出现null,无论是值还是键

5.1.1 基本的使用

  • 与其说properties是一个类似HashMap结构,不如说他更像JSON。
  • 特有的方法:
    • Object setProperty(String key,String value): 添加键值对。
    • String getProperty(String key) : 获取键对应的值,若没有key,则返回null.
    • Set stringPropertyNames() : 所有的键组成的集合。

5.1.2 配合IO流使用

  • 方法:
    • void load(输入流对象) : 输入流读取列表,用于读操作
    • void store(输出流对象,String comments) : 输出流列表 , 用于写操作, 可选择写入有关信息

5.1.3 配合使用小案例

public class PropertiesTest {
    public static void main(String[] args) throws IOException {
        Properties pro = new Properties();
        // 设置
        pro.setProperty("username","faker" );
        pro.put("password","sss" );
        //修改
        pro.setProperty("password","aaa" );
        // 持久化
        pro.store(new FileWriter("pro.properties"), "supper pass!");
          // 获取
        Properties pro_ = new Properties();
        pro_.load(new FileReader("pro.properties"));
        // 取所有键
        Set<String> keys = pro_.stringPropertyNames();
        // 遍历
        for (String key : keys) {
            System.out.println("集合中"+key+"对应的值是:"+pro_.getProperty(key));
        }
    }
}

5.2 ResourceBundle工具类

  • 说明:这里不对本地化信息展开叙述,仅是简化使用Properties集合中load方法读取文件。

5.2.1 基本介绍

  • ResourceBundle类中提供了一个静态方法,用于实例化对象(他是抽象类,所以是个子类实例)

    • static ResouceBundle getBundle(String baseName);
  • 须知:

  • ① .properties文件必须为当前模块的src的子文件。

  • ② baseName需要去掉后缀.properties。

  • ② .properties文件一般都不存储中文,所以不建议,key一定不能为中文。

5.2.2 使用案例

public class ResourceBundleTest {
    public static void main(String[] args) {
        ResourceBundle bundle = ResourceBundle.getBundle("test");
        String username = bundle.getString("username");
        String password = bundle.getString("password");
        System.out.println("usernaem="+username+", password="+password);
    }
}

六、commons-io工具包

6.1 序

  • IO技术开发中,代码量很大,而且代码的重复率较高。于是乎,apache开源基金组织提供的一组IO操作的类库,即commons-io, 它是可以提高IO功能开发的效率。commons-io工具包提供了很多有关io操作的类:
功能描述
org.apache.commons.io 有关Streams、Readers、Writers、Files的工具类
org.apache.commons.io.input 输入流相关的实现类,包含Reader和InputStream
org.apache.commons.io.output 输出流相关的实现类,包含Writer和OutputStream
org.apache.commons.io.serialization 序列化相关的类

6.2 如何使用

  • 下载commons-io相关jar包,请移步:传送门
  • 把commons-io-2.6.jar包复制到指定的Module的lib目录中
  • 将commons-io-2.6.jar加入到classpath中
  • 接下来就是使用了,里面工具类非常多,这里不做赘述,简单示例几个。
    • org.apache.commons.io.IOUtils:封装了大量IO读写操作的代码。比如:
      • IOUtils.copy(InputStream in, OutputStream out):把input输入流中的内容拷贝到output输出流中,返回拷贝的字节个数 (适合文件大小为2GB以下,比如拷贝图片)。
      • IOUtils.copyLarge(InputStream in, OutputStream out): 把input输入流中的内容拷贝到output输出流中,返回拷贝的字节个数(适合文件大小为2GB以上)。
      • IOUtils.closeQuietly (任意流对象) : 释放资源,自动处理close()方法抛出的异常。
    • org.apache.commons.io.FileUtils:封装了一些对文件操作的方法。 比如:
      • FileUtils.copyFileToDirectory(File srcFile, File destFile) : 复制文件到另外一个目录下。
      • FileUtils.copyDirectoryToDirectory( from, to): 复制from目录到to位置。
      • FileUtils.writeStringToFile(File file, String str) : 写字符串到文本中。
      • FileUtils.readFileToString(File file) :读取文本文件,返回字符串。

6.3 使用案例

  • 简单示例说明,具体使用的时候具体情况具体分析即可。

    public static void main(String[] args) throws IOException {
            //实现文件拷贝到某文件夹
            FileUtils.copyFileToDirectory(new File("D:\\io\\test.avi"), new File("D:\\io\\up"));
            // 实现文件夹的复制,简化了我们用递归去操作
            FileUtils.copyDirectoryToDirectory(new File("D:\\io\\up"),new File("D:\\io\\up") );
            // 实现图片的拷贝
            IOUtils.copy(new FileInputStream("D:\\io\\test.jpeg"), new FileOutputStream("D:\\io\\copy.jpeg"));
    
        }