Java IO

1、IO流

1.1 概念细分

  • 按流的方向
    1. 输入流:数据流向是数据源到程序(以InputStreamReader结尾的流)。
    2. 输出流:数据流向是程序到目的地(以OutPutStreamWriter结尾的流)。
  • 按处理的数据单元
    1. 字节流:以字节为单位获取数据,命名上以Stream结尾的流一般是字节流。
    2. 字符流:以字符为单位获取数据,命名上以Reader/Writer结尾的流一般是字符流。
  • 按处理对象不同
    1. 节点流:可以直接从数据源或目的地读写数据,如FileInputStream、FileReader、DataInputStream等。
    2. 处理流:不直接连接到数据源或目的地,是”处理流的流”。通过对其他流的处理提高程序的性能,如BufferedInputStream、BufferedReader等。处理流也叫包装流

1.2 类别体系

  1. InputStream/OutputStream
    字节流的抽象类。

  2. Reader/Writer
    字符流的抽象类。

  3. FileInputStream/FileOutputStream
    节点流:以字节为单位直接操作“文件”。

  4. ByteArrayInputStream/ByteArrayOutputStream
    节点流:以字节为单位直接操作“字节数组对象”。

  5. FileReader/FileWriter
    节点流:以字符为单位直接操作“文本文件”(注意:只能读写文本文件)。

  6. ObjectInputStream/ObjectOutputStream
    处理流:以字节为单位直接操作“对象”。

  7. DataInputStream/DataOutputStream
    处理流:以字节为单位直接操作“基本数据类型与字符串类型”。

  8. BufferedReader/BufferedWriter
    处理流:将Reader/Writer对象进行包装,增加缓存功能,提高读写效率。

  9. BufferedInputStream/BufferedOutputStream
    处理流:将InputStream/OutputStream对象进行包装,增加缓存功能,提高 读写效率。

  10. InputStreamReader/OutputStreamWriter
    处理流:将字节流对象转化成字符流对象。

  11. PrintStream
    处理流:将OutputStream进行包装,可以方便地输出字符,更加灵活。

2、常见流

2.1 文件字节流

  • 节点流
  • 适合读取所有类型的文件(图像、视频、文本文件等)。

利用 文件字节流 实现文件的复制

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class 文件字节流 {
    public static void main(String[] args) {
        //将0.jpg内容拷贝到1.jpg
        copyFile("h:/0.jpg", "h:/1.jpg");
    }

    /**
     * 将 src文件的内容拷贝到 dec文件
     * @param src 源文件
     * @param dec 目标文件
     */
    static void copyFile(String src, String dec) {
        FileInputStream fis = null;
        FileOutputStream fos = null;
        //为了提高效率,设置缓存数组!(读取的字节数据会暂存放到该字节数组中)
        byte[] buffer = new byte[1024];
        int temp = 0;
        try {
            fis = new FileInputStream(src);
            fos = new FileOutputStream(dec);
            //边读边写
            //temp指的是本次读取的真实长度,temp等于-1时表示读取结束
            while ((temp = fis.read(buffer)) != -1) {
                /*将缓存数组中的数据写入文件中,注意:写入的是读取的真实长度;
                 *如果使用fos.write(buffer)方法,那么写入的长度将会是1024,即缓存
                 *数组的长度*/
                fos.write(buffer, 0, temp);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //两个流需要分别关闭
            try {
                if (fos != null) {
                    fos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (fis != null) {
                    fis.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

2.2 文件字符流

  • 节点流
  • 专门处理文本文件

利用 文件字符流 实现文件的复制

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class 文件字符流 {
    public static void main(String[] args) {
        // 写法和使用Stream基本一样。只不过,读取时是读取的字符。
        FileReader fr = null;
        FileWriter fw = null;
        int len = 0;
        try {
            fr = new FileReader("h:/0.txt");
            fw = new FileWriter("h:/1.txt");
            //为了提高效率,创建缓冲用的字符数组
            char[] buffer = new char[1024];
            //边读边写
            while ((len = fr.read(buffer)) != -1) {
                fw.write(buffer, 0, len);
            }

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

2.3 字节数组流

  • 节点流
  • 常用在需要流和数组之间转化的情况

简单测试ByteArrayInputStream 的使用

import java.io.ByteArrayInputStream;
import java.io.IOException;

public class 字节数组流 {
    public static void main(String[] args) {
        //将字符串转变成字节数组
        byte[] b = "abcdefg".getBytes();
        test(b);
    }
    public static void test(byte[] b) {
        ByteArrayInputStream bais = null;
        StringBuilder sb = new StringBuilder();
        int temp = 0;
        //用于保存读取的字节数
        int num = 0;
        try {
            //该构造方法的参数是一个字节数组,这个字节数组就是数据源
            bais = new ByteArrayInputStream(b);
            while ((temp = bais.read()) != -1) {
                sb.append((char) temp);
                num++;
            }
            System.out.println(sb);
            System.out.println("读取的字节数:" + num);
        } finally {
            try {
                if (bais != null) 
                    bais.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

2.4 缓冲字节流

  • 处理流
  • 当对文件或者其他数据源进行频繁的读写操作时使用,能够更高效的读写信息
    • 先将数据缓存起来,
    • 然后当缓存区存满后或者手动刷新时再一次性的读取到程序或写入目的地。

缓冲流实现文件的高效率复制

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * 
 * 当对文件或者其他数据源进行频繁的读写操作时使用,能够更高效的读写信息
 * 先将数据缓存起来,然后当缓存区存满后或者手动刷新时再一次性的读取到程序或写入目的地。
 *
 * 
 * @author sjk
 */
public class 缓冲字节流 {
    public static void main(String[] args) {
        // 使用缓冲字节流实现复制
        long time1 = System.currentTimeMillis();
        copyFile1("h:0.jpg", "h:1.jpg");
        long time2 = System.currentTimeMillis();
        System.out.println("缓冲字节流花费的时间为:" + (time2 - time1));
    }
    /**缓冲字节流实现的文件复制的方法*/
    static void copyFile1(String src, String dec) {
        FileInputStream fis = null;
        BufferedInputStream bis = null;
        FileOutputStream fos = null;
        BufferedOutputStream bos = null;
        int temp = 0;
        try {
            fis = new FileInputStream(src);
            fos = new FileOutputStream(dec);
            //使用缓冲字节流包装文件字节流,增加缓冲功能,提高效率
            //缓存区的大小(缓存数组的长度)默认是8192,也可以自己指定大小
            bis = new BufferedInputStream(fis);
            bos = new BufferedOutputStream(fos);
            while ((temp = bis.read()) != -1) {
                bos.write(temp);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //注意:增加处理流后,注意流的关闭顺序!“后开的先关闭!”
            try {
                if (bos != null) bos.close();
                if (bis != null) bis.close();
                if (fos != null) fos.close();
                if (fis != null) fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

2.5 缓冲字符流

  • 处理流
  • 专门处理文本时
  • 增加了缓存机制,大大提高了读写文本文件的效率
  • 同时,提供了更方便的按行读取的方法:readLine()

2.6 数据流

  • 处理流、字节流
  • “基本数据类型与字符串类型” 作为数据源,从而允许程序以与机器无关的方式从底层输入输出流中操作Java基本数据类型与字符串类型。
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class 数据流 {
    public static void main(String[] args) {
        DataOutputStream dos = null;
        DataInputStream dis = null;
        FileOutputStream fos = null;
        FileInputStream  fis = null;
        try {
            fos = new FileOutputStream("h:/data.txt");
            fis = new FileInputStream("h:/data.txt");
            //使用数据流对缓冲流进行包装,新增缓冲功能
            dos = new DataOutputStream(new BufferedOutputStream(fos));
            dis = new DataInputStream(new BufferedInputStream(fis));
            //将如下数据写入到文件中
            dos.writeChar('a');
            dos.writeInt(10);
            dos.writeDouble(Math.random());
            dos.writeBoolean(true);
            dos.writeUTF("java");
            //手动刷新缓冲区:将流中数据写入到文件中
            dos.flush();
            //直接读取数据:读取的顺序要与写入的顺序一致,否则不能正确读取数据。
            System.out.println("char: " + dis.readChar());
            System.out.println("int: " + dis.readInt());
            System.out.println("double: " + dis.readDouble());
            System.out.println("boolean: " + dis.readBoolean());
            System.out.println("String: " + dis.readUTF());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if(dos!=null) dos.close();
                if(dis!=null) dis.close();
                if(fos!=null) fos.close();
                if(fis!=null) fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

2.7 对象流

  • 字节流、处理流
  • 对象流不仅可以读写对象,还可以读写基本数据类型
  • 使用对象流读写对象时,该对象必须序列化与反序列化。
  • 系统提供的类(如Date等)已经实现了序列化接口,自定义类必须手动实现序列化接口。
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.util.Date;

public class 对象流 {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
    	String path="h:object.txt";
        write(path);
        read(path);
    }
    /**使用对象输出流将数据写入文件*/
    public static void write(String path){
        // 创建Object输出流,并包装缓冲流,增加缓冲功能
        OutputStream os = null;
        BufferedOutputStream bos = null;
        ObjectOutputStream oos = null;
        try {
            os = new FileOutputStream(new File(path));
            bos = new BufferedOutputStream(os);
            oos = new ObjectOutputStream(bos);
            // 使用Object输出流
            //对象流也可以对基本数据类型进行读写操作
            oos.writeInt(12);
            oos.writeDouble(3.14);
            oos.writeChar('A');
            oos.writeBoolean(true);
            oos.writeUTF("北京尚学堂");
            //对象流能够对对象数据类型进行读写操作
            //Date是系统提供的类,已经实现了序列化接口
            //如果是自定义类,则需要自己实现序列化接口
            oos.writeObject(new Date());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 关闭Object输出流
            try {
                if(oos != null) oos.close();
                if(bos != null) bos.close();
                if(os != null)  os.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    /**使用对象输入流将数据读入程序*/
    public static void read(String path) {
        // 创建Object输入流
        InputStream is = null;
        BufferedInputStream bis = null;
        ObjectInputStream ois = null;
        try {
            is = new FileInputStream(new File(path));
            bis = new BufferedInputStream(is);
            ois = new ObjectInputStream(bis);
            // 使用Object输入流按照写入顺序读取
            System.out.println(ois.readInt());
            System.out.println(ois.readDouble());
            System.out.println(ois.readChar());
            System.out.println(ois.readBoolean());
            System.out.println(ois.readUTF());
            System.out.println(ois.readObject().toString());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 关闭Object输入流
            try {
                if(ois != null) ois.close();
                if(bis != null) bis.close();
                if(is != null)  is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

2.8 转换流

  • 字符流
  • 将字节流转化成字符流

示例:接收用户的输入,并输出到控制台

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;

public class 转换流 {
    public static void main(String[] args) {
        // 创建字符输入和输出流:使用转换流将字节流转换成字符流
        BufferedReader br = null;
        BufferedWriter bw = null;
        try {
            br = new BufferedReader(new InputStreamReader(System.in));
            bw = new BufferedWriter(new OutputStreamWriter(System.out));
            // 使用字符输入和输出流
            String str = br.readLine();
            // 一直读取,直到用户输入了exit为止
            while (!"exit".equals(str)) {
                // 写到控制台
                bw.write(str);
                bw.newLine();// 写一行后换行
                bw.flush();// 手动刷新
                // 再读一行
                str = br.readLine();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 关闭字符输入和输出流
            try {
                if (br != null) br.close();
                if (bw != null) bw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

3、序列化、反序列化

参考链接:

  1. https://www.cnblogs.com/9dragon/p/10901448.html

3.1 序列化的作用

  1. java远程方法调用
  2. 对JavaBeans进行序列化

3.2 Serializable接口实现序列化

  1. 步骤
  • 序列化步骤:

    • 步骤一:创建一个ObjectOutputStream输出流;
    • 步骤二:调用ObjectOutputStream对象的writeObject输出可序列化对象。
    • Serializable 接口是一个空接口,只起到标记作用
    • 如果不在改方法中添加readObjeat()writeObjeat()方法,则采取默认的序列化机制。如果添加了这两个方法之后还想利用Java默认的序列化机制,则在这两个方法中分别调用defaultReadObject()defaultWriteObject()两个方法
    • 为了保证安全性,可以使用transient关键字进行修饰不必序列化的属性。因为在反序列化时, private修饰的属性也能发查看到
  • 反序列化步骤:

    • 步骤一:创建一个ObjectInputStream输入流;
    • 步骤二:调用ObjectInputStream对象的readObject()得到序列化的对象。
    • 反序列化不会调用对象所在类的构造方法,完全基于字节。反序列的对象是由JVM自己生成的对象,不通过构造方法生成。
  1. 示例
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

//Person类实现Serializable接口后,Person对象才能被序列化
class Person implements Serializable {
    // 添加序列化ID,它决定着是否能够成功反序列化!
    private static final long serialVersionUID = 1L;
    int age;
    int sex;
    String name;

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

    @Override
    public String toString() {
        return "Person [age=" + age + ", isMan=" + sex + ", name=" + name + "]";
    }
}

public class TestSerializable {
    public static void main(String[] args) {
        FileOutputStream fos = null;
        ObjectOutputStream oos = null;
        ObjectInputStream ois = null;
        FileInputStream fis = null;
        try {
            // 通过ObjectOutputStream将Person对象的数据写入到文件中,即序列化。
            Person person = new Person(18, 1, "张三");
            // 序列化
            fos = new FileOutputStream("h:/serializableObject.txt");
            oos = new ObjectOutputStream(fos);
            oos.writeObject(person);
            oos.flush();
            // 反序列化
            fis = new FileInputStream("h:/serializableObject.txt");
            // 通过ObjectInputStream将文件中二进制数据反序列化成Person对象:
            ois = new ObjectInputStream(fis);
            Person p = (Person) ois.readObject();
            System.out.println(p);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (oos != null) oos.close();
                if (fos != null) fos.close();
                if (ois != null) ois.close();
                if (fis != null) fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

3.3 Externalizable:强制自定义序列化

  1. 序列化
    • 自己对要序列化的内容进行控制:可以控制哪些属性能被序列化,哪些不能被序列化。
    • 通过实现Externalizable接口,必须实现writeExternal、readExternal方法
  2. 反序列化
    • 必须提供pulic的无参构造器,因为在反序列化的时候需要反射创建对象。

3.4 注意事项

  1. 所有需要网络传输的对象都需要实现序列化接口,通过建议所有的javaBean都实现Serializable接口。
  2. 对象的类名、属性(包括基本类型,数组,对其他对象的引用)都会被序列化方法、类变量、transient实例变量都不会被序列化static属性不参与序列化
  3. 可以使用transient修饰对象中的某些不想被序列化属性,不能使用static。
  4. 序列化对象的引用类型成员变量,也必须是可序列化的,否则,会报错。
  5. 反序列化时必须有序列化对象的class文件
  6. 当通过文件、网络来读取序列化后的对象时,必须按照实际写入的顺序读取
  7. 单例类序列化,需要重写readResolve()方法;否则会破坏单例原则。
  8. 同一对象序列化多次,只有第一次序列化为二进制流,以后都只是保存序列化编号,不会重复序列化。
  9. 为了防止读和写的序列化ID不一致,一般指定一个固定的serialVersionUID版本号,方便项目升级。不同的JVM中默认生成的serialVersionUID可能不同,会造成反序列化失败

3.5 常见的序列化协议

3.5.1 COM

COM主要用于Windows平台,并没有真正实现跨平台,另外COM的序列化的原理利用了编译器中虚表,使得其学习成本巨大。

3.5.2 CORBA

CORBA是早期比较好的实现了跨平台,跨语言的序列化协议。COBRA的主要问题是参与方过多带来的版本过多,版本之间兼容性较差,以及使用复杂晦涩。

3.5.3 XML&SOAP

  • XML是种常用的序列化和反序列化协议,具有跨机器,跨语言等优点。
  • SOAP ( 5imple Object Access protocol)是一种被广泛应用的,基于XML为序列化和反列化协议的结构化消息传递协议。5OAP具有安全、可扩展、跨语言、跨平台并支持多种传输层协议。

3.5.4 JSON

  • 这种Associative array格式非常符合工程师对对象的理解。
  • 它保持了XML的人眼可读( Human-readable )的优点。
  • 相对于XML而言,序列化后的数据更加简洁。
  • 它具备Javascript的先天性支持,所以被广泛应用于Web browser的应用常景中,是Ajax的事实标准协议。
  • 与XML相比,其协议比较简单,解析連度比较快。
  • 松散的Associative array使得其具有良好的可扩展性和兼容性.

3.5.5 Thrift

Thrit是Facebook开源提供的一个高性能,轻量级RPC服务框架,其产生正是为了满足当前大数据量、分布式、跨语言跨平台数据通讯的需求。
Thrift在空间开销和解析性能上有了比较大的提升,对于对性能要求比较高的分布式系统,它是一一个优秀的RPC解决方案;但是由于Thrift的序列化被嵌入到Thrift框架里面, Thrift框架本身并没有透出序列化和反序列化接口,这导致其很难和其他传输层协议共同使用。

3.5.6 Protobuf

  • 标准的IDL和IDL编译器,这使得其对工程师非常友好。
  • 序列化数据非常简洁,紧凑,与XML相比,其序列化之后的数据量约为1/3到1/10.
  • 解析連度非常快,比对应的XML快约20-100倍。
  • 提供了非常友好的动态库,使用非常简介,反序列化只需要-行代码。 由于其解析性能高,列化后数据量相对少,非常适合应用层对象的持久化场景

3.5.7 Avro

  • 解决了JSON的冗长和没有IDL的问题。Avro属于Apache Hadoop的一个子项目。
  • Avro提供两种序列化格式: JISON格式或者Binary格式。
    • Binany格式在空间开销和解析性能方面可以和Protobu媲美
    • JSON格式方便测试阶段的调试。适合于高性能的序列化服务。

3.5.8 几种协议的对比

  • XML序列化( Xstream )无论在性能和简洁性上比较差;
  • Thrift与Protobuf相比在时空开销方面都有一定的劣势;
  • ProtobufAvro在两方面表现都非常优越。

4 IOUtils、FileUtils工具

  1. FieUtils类中常用方法的介绍

    • cleanDirectory:清空目录,但不删除目录。
    • contentEquals:比较两个文件的内容是否相同。
    • copyDirectory:将一个目录内容拷贝到另一个目录。可以通过FileFilter过滤需要拷贝的 文件。
    • copyFile:将一个文件拷贝到一个新的地址。
    • copyFileToDirectory:将一个文件拷贝到某个目录下。
    • copyInputStreamToFile:将一个输入流中的内容拷贝到某个文件。
    • deleteDirectory:删除目录。
    • deleteQuietly:删除文件。
    • listFiles:列出指定目录下的所有文件。
    • openInputSteam:打开指定文件的输入流。
    • readFileToString:将文件内容作为字符串返回。
    • readLines:将文件内容按行返回到一个字符串数组中。
    • size:返回文件或目录的大小。
    • write:将字符串内容直接写到文件中。
    • writeByteArrayToFile:将字节数组内容写到文件中。
    • writeLines:将容器中的元素的toString方法返回的内容依次写入文件中。
    • writeStringToFile:将字符串内容写到文件中。
  2. IOUtils类中常用方法的介绍

    1. buffer:将传入的流进行包装,变成缓冲流。并可以通过参数指定缓冲大小。
    2. closeQueitly:关闭流。
    3. contentEquals:比较两个流中的内容是否一致。
    4. copy:将输入流中的内容拷贝到输出流中,并可以指定字符编码。
    5. copyLarge:将输入流中的内容拷贝到输出流中,适合大于2G内容的拷贝。
    6. lineIterator:返回可以迭代每一行内容的迭代器。
    7. read:将输入流中的部分内容读入到字节数组中。
    8. readFully:将输入流中的所有内容读入到字节数组中。
    9. readLine:读入输入流内容中的一行。
    10. toBufferedInputStreamtoBufferedReader:将输入转为带缓存的输入流。
    11. toByteArraytoCharArray:将输入流的内容转为字节数组、字符数组。
    12. toString:将输入流或数组中的内容转化为字符串。
    13. write:向流里面写入内容。
    14. writeLine:向流里面写入一行内容。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值