Java IO基础:
IO简介:
IO指Input / Output,即输入和输出。
IO流是一种顺序读写数据的模式:
单向流动;
以byte为最小单位(字节流)。
如果字符不是单字节表示的ASCII:
Java提供了Reader/Writer表示字符流;
字符流传输的最小数据单位是char;
字符流输出的byte取决于编码方式。
Reader/Writer本质上是一个能自动编解码的InputStream/OutputStream。
如:
使用Reader:
使用InputStream:
如果数据源不是文本,那就使用InputStream;如果数据源是文本,那么使用Reader更方便一些。
同步IO和异步IO:
同步IO:
读写IO时代码等待数据返回后才继续执行后续代码;
代码编写简单,CPU执行效率低。
异步IO:
读写IO时仅发出请求,然后立刻执行后续代码;
代码编写复杂,CPU执行效率高。
JDK的java.io包提供了同步IO功能,JDK的java.nio包提供了异步IO功能。
总结:
IO流是一种流式的数据输入/输出模型;
二进制数据以byte为最小单位在InputStream/OutputStream中单向流动;
字符数据以char为最小单位在Reader/Writer中单向流动;
JDK的java.io包提供了同步IO功能,JDK的java.nio包提供了异步IO功能;
Java的IO流的接口和实现是分离的:
字节流接口:InputStream / OutputStream
字符流接口:Reader / Writer
File对象:
java.io.File表示文件系统的一个文件或者目录:
构造方法:File(String pathname)
如:
String getPath()/getAbsolutePath()/getCanonicalPath():3种返回路径的方法
如:
boolean isFile():是否是文件
boolean isDirectory():是否是目录
如:
当File对象表示一个文件时(isFile()==true):
boolean canRead():是否允许读取该文件
boolean canWrite():是否允许写入该文件
boolean canExecute():是否允许运行该文件
long length():获取文件大小
如:
boolean createNewFile():创建一个新文件
static boolean createTempFile(String prefix,String suffix):创建一个临时文件
boolean delete():删除该文件
void deleteOnExit():在JVM退出时删除该文件。
如:
当File对象表示一个目录时(isDirectory()==true):
String[] list():列出目录下的文件和子目录名;
File[] listFiles():列出目录下的文件和子目录名;
File[] listFiles(FileFilter filter)
File[] listFiles(FilenameFilter filter)
如:
当File对象表示一个目录时(isDirectory()==true):
boolean mkdir():创建该目录
boolean mkdirs():创建该目录,并在必要时将不存在的父目录也创建出来
boolean delete():删除该目录
如:
总结:
File对象表示一个文件或者目录:
创建File对象本身不涉及IO操作;
获取路径/绝对路径/规范路径:getPath()/getAbsolutePath()/getCanonicalPath();
可以获取目录的文件和子目录;
通过File对象可以创建或删除文件和目录。
Input和Output:
InputStream:
InputStream是所有输入流的超类:
abstract int read():
读取下一个字节,并返回字节(0-255);
如果已读到末尾,返回-1;
int read(byte[] b):
读取若干字节并填充到byte[]数组,返回读取的字节数
int read(byte[] b,int off,int len):
指定byte[]数组的偏移量和最大填充数
void close():关闭输入流
如:
完整地读取一个InputStream的所有字节:
这段代码的问题是,如果读取过程中发生了错误,则无法正常关闭。
改进代码:
我们也可以用JDK1.7新增的try(resource)可以保证InputStream正确关闭:
我们还可以利用缓冲区一次读取多个字节:
read()方法是阻塞(blocking)的:
如:
FileInputStream可以从文件获取输入流:
ByteArrayInputStream可以在内存中模拟一个InputStream:
总结:
InputStream定义了所有输入流的超类;
FileInputStream实现了文件流输入;
ByteArrayInputStream在内存中模拟一个字节流输入;
使用try(resource)保证InputStream正确关闭。
OutputStream:
java.io.OutputStream是所有输出流的超类:
abstract write(int b):写入一个字节
void write(byte[] b):写入byte[]数组的所有字节
void write(byte[] b,int off,int len):写入byte[]数组指定范围的字节
void close():关闭输出流
void flush():将缓冲区的内容输出,缓冲区满时会自动调用flush(),在关闭OutputStream之前也会自动调用flush()
将byte写入OutputStream:
写入过程中如果发生错误,就无法正确地关闭。
我们使用JDK1.7的try(resource)保证OutputStream正确关闭。
将byte[]写入OutputStream:
write()方法是阻塞(blocking)的:
FileOutputStream可以输出到文件:
ByteArrayOutputStream可以在内存中模拟一个OutputStream:
总结:
OutputStream定义了所有输出流的超类;
FileOutputStream实现了文件流输出;
ByteArrayOutputStream在内存中模拟一个字节流输出;
使用try(resource)保证OutputStream正确关闭。
Filter模式:
JDK提供的InputStream包括:
FileInputStream:从文件读取数据;
ServletInputStream:从http请求读取数据;
Socket.getInputStream:从TCP连接读取数据。
如果要给FileInputStream添加各种InputStream功能:
这样一来,我们发现添加各种功能需要很多的子类:
这就是子类数量爆炸的情况。
Filter模式是为了解决子类数量爆炸的问题。
为了解决这个问题,JDK把InputStream分为两类:
直接提供数据的InputStream:FileInputStream、ByteArrayInputStream、ServletInputStream
提供附加功能的InputStream从FilterInputStream派生:BufferedInputStream、DigestInputStream、CipherInputStream、GZIPInputStream
使用InputStream时,根据情况组合:
首先必须要有一个真正提供数据源的InputStream,如图,即FileInputStream;然后用BufferedInputStram来包装FileInputStream;再假设文件已经被GZIP压缩过,我们希望能直接读取解压缩后的内容,我们就再用GZIPInputStream包装一层。
无论我们包装了多少次,得到的对象总是InputStream,所以我们用InputStream来引用它读取数据。
组合功能而非继承的设计模式称为Filter模式(又称Decorator模式)。
通过少量的类实现了各种功能的组合。
InputStream的继承体系:
OutputStream的继承体系和InputStream类似。
总结:
Java IO使用Filter模式为InputStream/OutputStream增加功能;
可以把一个InputStream和任意FilterInputStream组合;
可以把一个OutputStream和任意FilterOutputStream组合;
Filter模式可以在运行期间动态增加功能(又称Decorator模式)。
操作zip:
ZipInputStream是一种FilterInputStream。
它可以直接读取Zip的内容。
JarInputStream提供了额外读取jar包内容的能力。
ZipInputStream的基本用法:
ZipOutputStream是一种FilterOutputStream,可以直接写入Zip的内容。
ZipOutputStream的基本用法:
总结:
ZipInputStream可以读取Zip格式的流;
ZipOutputStream可以把数据写入Zip;
ZipInputStream/ZipOutputStream都是FilterInputStream/FilterOutputStream;
配合FileInputStream和FileOutputStream就可以读写Zip文件。
classpath资源:
Java存放.class的目录或jar包也可以包含任意其他类型的文件:
.properties,.txt,.jpg,.mov,...
从classpath读取文件可以避免不同环境下文件路径不一致的问题。
如:
这段代码在windows下可以正常执行,但是在linux系统下路径就完全不同。
如果资源文件不存在,会返回null。
这样我们就可以不用管不同环境下文件路径不一致的问题。
总结:
把资源存储在classpath中可以避免文件路径依赖;
Class对象的getResourceAsStream()可以从classpath中读取资源;
需要检查返回的InputStream是否为null。
序列化:
序列化是指把一个Java对象变成二进制内容(byte[]数组):
序列化后可以把byte[]保存到文件中;
序列化后可以把byte[]通过网络传输;
一个Java对象要能实现序列化,必须实现Serializable接口:
Serializable接口没有定义任何方法;
空接口被称为“标记接口”(Marker Interface);
反序列化是指把一个二进制内容(byte[])变成Java对象:
反序列化后可以从文件读取byte[]并变为Java对象;
反序列化后可以从网络读取byte[]并变为Java对象。
ObjectOutputStream负责把一个Java对象写入二进制流:
ObjectInputStream负责从二进制流读取一个Java对象:
readObject()可能抛出的异常:
ClassNotFoundException:没有找到对应的Class;
InvalidClassException:Class不匹配;
反序列化的重要特点:反序列化由JVM直接构造出Java对象,不调用构造方法。
总结:
可序列化的Java对象必须实现java.io.Serializable接口;
类似Serializable这样的空接口被称为“标记接口”(Marker Interface);
反序列化时不调用构造方法;
可设置serialVersionUID作为版本号(非必需);
Java的序列化机制仅使用与Java,如果需要与其他语言交换数据,必须使用通用的序列化方法,例如JSON。
Reader和Writer:
Reader:
java.io.Reader和java.io.InputStream的区别:
java.io.Reader是所有字符输入流的超类:
int read():读取下一个字符并返回字符(0-65535),如果已读到末尾,返回-1;
int read(char[] c):读取若干字符并填充到char[]数组,返回读取的字符数;
int read(char[] c,int off,int len):指定char[]数组的偏移量和最大填充数;
void close():关闭Reader。
完整地读取一个Reader的所有字符:
同样的,如果读取过程中出现错误,无法正常关闭。
我们修改一下代码,使用try...finally写法:
或使用try(resource)写法(JDK>=1.7):
利用缓冲区一次读取多个字符:
FileReader可以从文件获取Reader对象:
注意这里使用的字符编码是系统默认的编码。
CharArrayReader可以在内存中模拟一个Reader对象:
实际上是把一个char[]数组变成一个Reader对象。
Reader是基于InputStream构造的:
FileReader内部持有一个FileInputStream;
Reader可以通过InputStream构造。
任何InputStream都可指定编码并通过InputStreamReader转换为Reader:Reader reader = new InputStreamReader(input, "UTF-8")。
总结:
Reader定义了所有字符输入流的超类;
FileReader实现了文件字符流输入;
CharArrayReader在内存中模拟一个字符流输入;
Reader是基于InputStream构造的:
FileReader使用系统默认编码,无法指定编码,可以通过InputStreamReader指定编码
使用try(resource)保证Reader正确关闭。
Writer:
java.io.Writer和java.io.OutputStream的区别:
java.io.Writer是所有字符输入流的超类:
void write(in c):写入下一个字符(0-65535);
void write(char[] c):写入字符数组的所有字符;
void write(char[] c,int off,int len):写入字符数组指定范围的字符;
void write(String s):写入String表示的所有字符。
向Writer对象写入字符:
显然当写入出现错误时无法正常关闭。
我们可以使用try...finally来保证出错时正常关闭。
或使用try(resource)写法(JDK>=1.7):
一次写入多个字符:
write()方法是阻塞(blocking)的:
FileWriter可以从文件获取Writer:
字符编码是系统默认编码。
CharArrayWriter可以在内存中模拟一个Writer:
实际上是把一个char[]数组变成一个Writer对象。
Writer是基于OutputStream构造的:
FileWriter内部持有一个FileOutputStream;
Writer可以通过OutputStream构造;
任何OutputStream都可指定编码并通过OutputStreamWriter转换为Writer:Writer writer = new OutputStreamWriter(output, "UTF-8")。
总结:
Writer定义了所有字符输出流的超类;
FileWriter实现了文件字符流输出;
CharArrayWriter在内存中模拟一个字符流输出;
Writer是基于OutputStream构造的:
FileWriter使用系统默认编码,无法指定编码;可以通过OutputStreamWriter指定编码。
使用try(resource)保证Writer正确关闭。