1、基本概念

1.1 Java I/O

Java的输入输出系统
区分Java输出和输出:把自己当作程序

  • 输入(InputStream/Reader):从外部读取数据到程序中
  • 输出(OutputStream/Writer):从程序中向外部写数据

    1.2 Stream

    Java中将数据的输入和输出抽象为流(Stream),这是有序、单向、有起始和终止点的数据集合。流按照最小的数据单元分为字符流和字节流。

  • 字节流:以8位(1 byte, 8bit)作为数据单元。能处理所有类型的数据(包括图片、视频等)。无缓冲区,因此不需要调用close,数据直接输出。

  • 字符流:以16位(1 char,2 byte,16 bit)作为数据单元,Java语言中字符默认的是Unicode编码,一个字符占两个字节。只能处理字符数据()。字符流会先输出到缓冲区,然后在调用close()方法关闭缓冲区时数据才会输出,如果想要在关闭字符流之前输出信息,则需要手动调用flush()方法。

    2、I/O分类

    Java I/O系统 - 图1

    2.1 流式部分类

    Java I/O系统 - 图2

    2.1.1 InputStream及其子类的关系

  • InputStream 是所有的输入字节流的父类,它是一个抽象类。

  • PushbackInputStream、DataInputStream 和 BufferedInput Stream都是处理流,他们的的父类是 FilterInputStream。
  • ByteArrayInputStream、StringBufferInputStream、FileInputStream 是三种基本的介质流,它们分别从 Byte 数组、StringBuffer、和本地文件中读取数据。PipedInputStream 是从与其它线程共用的管道中读取数据。

    2.1.2 InputStream的三个基本的读方法

  • abstract int read() :读取一个字节数据,并返回读到的数据,如果返回 -1,表示读到了输入流的末尾。

  • int read(byte[] b) :将数据读入一个字节数组,同时返回实际读取的字节数。如果返回-1,表示读到了输入流的末尾。
  • int read(byte[] b, int off, int len) :将数据读入一个字节数组,同时返回实际读取的字节数。如果返回 -1,表示读到了输入流的末尾。off 指定在数组 b 中存放数据的起始偏移位置;len 指定读取的最大字节数。

    2.1.3 OutputStream及其子类的关系

  • OutputStream 是所有的输出字节流的父类,它是一个抽象类。

  • ByteArrayOutputStream、FileOutputStream 是两种基本的介质流,它们分别向 Byte 数组、和本地文件中写入数据。
  • PipedOutputStream 是向与其它线程共用的管道中写入数据。
  • BufferedOutputStream、DataOutputStream 和 PrintStream 都是处理流,他们的的父类是 FilterOutputStream。

    2.1.4 OutputStream中的三个基本的写方法

  • abstract void write(int b):往输出流中写入一个字节。

  • void write(byte[] b) :往输出流中写入数组b中的所有字节。
  • void write(byte[] b, int off, int len) :往输出流中写入数组 b 中从偏移量 off 开始的 len 个字节的数据。

    2.1.5 其它重要方法

  • void flush() :刷新输出流,强制缓冲区中的输出字节被写出。

  • void close() :关闭输出流,释放和这个流相关的系统资源。

    2.1.6 Reader及其子类的关系

  • Reader 是所有的输入字符流的父类,它是一个抽象类。

  • CharReader、StringReader 是两种基本的介质流,它们分别将 Char 数组、String 中读取数据。PipedReader 是从与其它线程共用的管道中读取数据。
  • BufferedReader 很明显就是一个装饰器,它和其子类负责装饰其它 Reader 对象。
  • FilterReader 是所有自定义具体装饰流的父类,其子类 PushbackReader 对 Reader 对象进行装饰,会增加一个行号。
  • InputStreamReader 是一个连接字节流和字符流的桥梁,它将字节流转变为字符流。

    2.1.7 Reader基本的三个读方法(和字节流对应)

  • public int read() throws IOException; 读取一个字符,返回值为读取的字符。

  • public int read(char cbuf[]) throws IOException; 读取一系列字符到数组 cbuf[]中,返回值为实际读取的字符的数量。
  • public abstract int read(char cbuf[],int off,int len) throws IOException; 读取 len 个字符,从数组 cbuf[] 的下标 off 处开始存放,返回值为实际读取的字符数量,该方法必须由子类实现。

    2.1.8 Writer及其子类的关系

  • Writer 是所有的输出字符流的父类,它是一个抽象类。

  • CharArrayWriter、StringWriter 是两种基本的介质流,它们分别向 Char 数组、String 中写入数据。PipedWriter 是向与其它线程共用的管道中写入数据。
  • BufferedWriter 是一个装饰器为 Writer 提供缓冲功能。
  • PrintWriter 和 PrintStream 极其类似,功能和使用也非常相似。
  • OutputStreamWriter 是 OutputStream 到 Writer 转换的桥梁,它的子类 FileWriter 其实就是一个实现此功能的具体类。

    2.1.9 Writer基本的三个写方法(和字节流对应)

  • public void write(int c) throws IOException; //写单个字符

  • public void write(char cbuf[]) throws IOException; //将字符数组 cbuf[] 写到输出流 。
  • public abstract void write(char cbuf[],int off,int len) throws IOException; //将字符数组cbuf[]中的从索引为off的位置处开始的len个字符写入输出流 。
  • public void write(String str) throws IOException; //将字符串str中的字符写入输出流 。
  • public void write(String str,int off,int len) throws IOException; //将字符串 str 中从索引 off 开始处的 len 个字符写入输出流 。

    2.1.10 字符流的输入与输出的对应

    Java I/O系统 - 图3

    2.1.11 处理流

    处理流是对一个已存在的流的连接和封装,通过所封装的流的功能调用实现数据读写。如 BufferedReader。

  • 缓冲流 :BufferedImputStrean,BufferedOutputStream,BufferedReader ,BufferedWriter,需要父类作为参数构造,增加缓冲功能,避免频繁读写硬盘,可以初始化缓冲数据的大小,由于带了缓冲功能,所以就写数据的时候需要使用 flush 方法,另外,BufferedReader 提供一个 readLine( ) 方法可以读取一行,而 FileInputStream 和 FileReader 只能读取一个字节或者一个字符,因此 BufferedReader 也被称为行读取器。

  • 转换流:InputStreamReader,OutputStreamWriter,要 inputStream 或 OutputStream 作为参数,实现从字节流到字符流的转换,我们经常在读取键盘输入(System.in)或网络通信的时候,需要使用这两个类。
  • 数据流:DataInputStream,DataOutputStream,提供将基础数据类型写入到文件中,或者读取出来

    2.2 非流式部分类

    主要包含一些辅助流式部分的类。
    例如:File类、RandomAccessFile类。

    2.2.1 File类

    这个类既能代指某个文件的名称,又能代表目录下一组文件的名称。

  • 某个文件:

  • 文件集:调用list()方法,返回字符数组

(1)目录列表器

  1. File path = new File(".");
  2. String[] list;
  3. list = path.list();//list里存储目录的文件集

(2)查看、创建和删除目录

  1. File.getAbsolutePath();//获取绝对路径
  2. File.canRead();//如果文件可以被读,返回true。
  3. File.canWrite();//如果文件可以被写,返回true。
  4. File.getName();//返回文件或目录名——绝对路径的最后一个名字
  5. File.getParent();//返回文件或目录的上一级路径
  6. File.getPath();//获取构造时的路径
  7. File.length();//文件的字节大小,若File为目录,返回值不确定,若不存在返回值为0L
  8. File.lastModified();//文件最后一次被修改的时间其实返回的是文件修改时的时刻与00:00:00 GMT, January 1, 1970的差值(用毫秒计),所以可以用Date类的构造方法Date(long date)输出。new Date(f1.lastModified())
  9. File.isFile();//
  10. File.isDirectory();//
  11. File.renameTo(STring s);//重命名为s
  12. File.exists();//文件存在
  13. File.mkdir();//构建初始化名称的目录

2.2.2 自我独立类:RandomAccessFile

(1)RandomAccessFile的用途
平常创建流对象关联文件,开始读文件或者写文件都是从头开始的,不能从中间开始,如果是开多线程下载一个文件我们之前学过的FileWriter或者FileReader等等都无法完成,而RandomAccessFile是可以指定位置读,指定位置写的一个类,通常开发过程中,多用于多线程下载一个大文件
(2)使用方法

  • 构造方法

    1. RandomAccessFile raf = newRandomAccessFile(File file, String mode);
    2. //其中参数 mode 的值可选 "r":可读,"w" :可写,"rw":可读性;
  • 成员方法

    1. RandomAccessFile.seek(int index);//可以将指针移动到某个位置开始读写;
    2. RandomAccessFile.setLength(long len);//给写入文件预留空间:

    3、标准I/O

    3.1 从标准输入、输出和错误中读取

    3.1.1 标准输入

    System.in
    需要自定义包装,将标准输入包装成其他输入类字符或字节输入类,

    3.1.2 标准输出

    System.out
    已经被包装成PrintStream
    (1)默认PrintStream

    1. System.out.*

    (2)转换成PrintWriter
    PrintStream是一个OutputStream,而PrintWriter有一个可以接受OutputStream作为参数的构造器。

    1. PrintWriter out = new PrintWriter(System.out, true);//第二个参数是开启自动清空功能,防止看不到输出。
    2. out.println("Hello, World!!!");

    3.1.3 标准错误

    System.err
    已经被包装成printStream

    3.2 标准I/O重定向

    System类提供了重定向方法:

    1. setln(InputStream);
    2. setOut(PrintStream);
    3. setln(PrintStream);

    I/O操作的是字节流,而不是字符流。

    4、Java程序控制其他程序

    在写java项目时, 有些特殊情况下需要用到操作命令行, 也就是所谓的。在Java中提供了两种方法来启动其他程序:
    (1) 使用Runtime的exec()方法
    (2) 使用ProcessBuilder的start()方法
    Runtime和ProcessBulider提供了不同的方式来启动程序,设置启动参数、环境变量和工作目录。但是这两种方法都会返回一个用于管理操作系统进程的Process对象。 ```java (1)Runtime的exec()方法 Process p = null;
    try {
    p = Runtime.getRuntime().exec(“notepad.exe”);
    } catch (Exception e) {
    e.printStackTrace();
    }
    System.out.println(“我想被打印…”);

(2)ProcessBuilder的start()方法 String commandStr = “ping www.taobao.com”; Process process = new ProcessBuilder(commandStr.split(“ “)).start(); BufferedReader input = new BufferedReader(new InputStreamReader(process.getInputStream())); String s = null; s = input.readLine()+”\n”+input.readLine(); System.out.println(s);

  1. <a name="ddgkQ"></a>
  2. # 5、压缩
  3. 属于InputStream和OutputStream继承层次结构的一部分,按字节处理。
  4. ```java
  5. DeflaterOutputStream //压缩类的基类
  6. InflaterInputStream //解压类的基类

5.1 GZIP

简化的压缩I/O类,只对单个数据流进行压缩。

  1. GZIPOutputStream //基于DeflaterOutputStream的压缩类,将数据压缩成GZip
  2. GZIPInputStream //基于InflaterInputStream的压缩类,将数据压缩成GZip

5.2 Zip

多文件压缩,使用的是标准的Zip格式;

  1. ZipOutputStream //基于DeflaterOutputStream的压缩类,将数据压缩成Zip
  2. ZipInputStream //基于InflaterInputStream的压缩类,将数据压缩成Zip

允许使用Checksum类计算和校验文件的校验和,共有两种Checksum类型:

  • Adler32

速度快

  • CRC32

速度略慢,但更加精确

  1. CheckedInputStream //GetCheckSum()为任何InputStream产生校验和
  2. CheckedOutputStream //GetCheckSum()为任何OutputStream产生校验和

6、序列化和反序列化

6.1 概念

把对象转换为字节序列的过程称为对象的序列化。
把字节序列恢复为对象的过程称为对象的反序列化。

6.2 用途

对象的序列化主要有两种用途:
(1) 对象持久化:把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;
(2) 网络传输对象:在网络上传送对象的字节序列。可以通过序列化把主机A进程上的对象序列化为二进制序列,传输到主机B上的进程从序列中重构出该对象。这在RMI(远程方法调用)上很常见。
  在很多应用中,需要对某些对象进行序列化,让它们离开内存空间,入住物理硬盘,以便长期保存。比如最常见的是Web服务器中的Session对象,当有 10万用户并发访问,就有可能出现10万个Session对象,内存可能吃不消,于是Web容器就会把一些seesion先序列化到硬盘中,等要用了,再把保存在硬盘中的对象还原到内存中。
  当两个进程在进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以二进制序列的形式在网络上传送。发送方需要把这个Java对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为Java对象。

6.3 序列化和反序列化对象的方式

6.3.1 Java序列化的默认API

(1)序列化
通过java.io.ObjectOutputStream对象输出流的writeObject(Object obj)方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。
(2)反序列化
通过java.io.ObjectInputStream对象输入流的readObject()方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回,返回时通过强制类型转换赋值给具体的类对象引用。

6.3.2 让对象可以被序列化的三种方式

(1)默认序列化
定义类时实现Serializable接口即可,这个Serializable接口是一个空接口,没有需要实现的方法。作用是标记该类的对象可以被序列化,启用其序列化功能。通过调用ObjectOutputStream和ObjectInputStream的方法来对该对象进行序列化和反序列化。
(2)类自定义序列化方式一
定义类时,实现Serializable接口,并在类中定义:    

  1. private void writeObject(java.io.ObjectOutputStream out) throws IOException
  2. private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException;
  1. 这两个方法,在方法中**通过对象输入流参数、对象输出流参数进行自定义的内容输出。**这样通过对象输出流和对象输入流的输入输出方法序列化和反序列化对象时,**会自动调用类中定义**的writeObjectreadObject方法而不是默认的序列化和反序列化方法。<br />(3)**类自定义序列化方式二**<br />实现**Externalnalizable接口**(继承自 Serializable接口)**,**并且在类中实现**readExternal**(ObjectInput in)和**writeExternal**(ObjectOutput out)方法,在方法中定义类对象自定义的序列化和反序列化操作。这样通过对象输出流和对象输入流的输入输出方法序列化和反序列化对象时**会自动调用类中定义**的readExternal(ObjectInput in)和writeExternal(ObjectOutput out)方法。

6.3.3 序列化和反序列化的使用步骤

对象序列化包括如下步骤:
(1)创建类,实现Serializable接口或者Externalizable接口,实现相应的序列化和反序列化方法(也可采取默认方法);
(2) 创建对象输出流ObjectOutputStream对象并在构造参数中指定流的输出目标(比如一个文件),通过objectOutputStream.writeObject(obj)把对象序列化并输出到流目标;
(3)在需要提取对象处:创建对象输入流ObjectInputStream对象并在构造参数中指定流的来源,然后通过readObject()方法获取对象,并通过强制类型转换赋值给类对象引用。

  1. (1)序列化
  2. Person person = new Person();
  3. person.setName("gacl");
  4. person.setAge(25);
  5. person.setSex("男");
  6. // ObjectOutputStream 对象输出流,将Person对象存储到E盘的Person.txt文件中,完成对Person对象的序列化操作
  7. ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream(new File("E:/Person.txt")));
  8. oo.writeObject(person);
  9. System.out.println("Person对象序列化成功!");
  10. oo.close();
  11. (2)反序列化
  12. ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("E:/Person.txt")));
  13. Person person = (Person) ois.readObject();
  14. System.out.println("Person对象反序列化成功!");

6.3.4 特殊情况

(1)静态变量和transient关键字修饰的变量不能被序列化;
(2)反序列化时要按照序列化的顺序重构对象:如先序列化A后序列化B,则反序列化时也要先获取A后获取B,否则报错。
(3)序列化ID的作用:虚拟机是否允许对象反序列化,不仅取决于该对象所属类路径和功能代码是否与虚拟机加载的类一致,而是主要取决于对象所属类与虚拟机加载的该类的序列化 ID 是否一致
(4)自定义序列化方法的应用场景:对某些敏感数据进行加密操作后再序列化;反序列化对加密数据进行解密操作。
(5)重复序列化:同一个对象重复序列化时,不会把对象内容再次序列化,而是新增一个引用指向第一次序列化时的对象而已。

6.4 XML、JSON

其他序列化方式:

  • 把对象包装成JSON格式进行序列化
  • 用XML格式序列化