Java IO 概要
为了方便理解和阐述,先引入两张图:
-
Java IO中的常用的类
类 说明 File
文件夹 RandomAccessFile
随机存取文件夹 InputStream
字节输入流 OutputStream
字节输出流 Reader
字符输入流 Writer
字符输出流 在整个
java.io
包中最重要的就是五个类和一个接口。五个类指的是File, InputStream, OutputStream, Reader, Writer
;一个接口指的是Serializable
,掌握了这些IO的核心操作那么对于Java中的IO体系也就有了一个初步的认识。Java IO主要包括如下几个层次,包含三个部分:
- 流式部分——IO的主体部分;
-
非流式部分——主要包含一些辅助流式部分的类,如
File
类、RandomAccessFile
类和FileDescriptor
等类; -
其他类——文件读取部分的与安全相关的类,如
SerializablePermission
类,以及与本地操作系统相关的文件系统的类,如:FileSystem
类和Win32FileSystem
类等等。
主要的类如下:
-
File
(文件特征与管理):用于文件或者目录的描述信息,例如生成新目录,修改文件名,删除文件,判断文件所在路径等。 -
InputStream
(二进制格式操作):抽象类,基于字节的输入操作,是所有输入流的父类。定义了所有输入流都具有的共同特征。 -
OutputStream
(二进制格式操作):抽象类,基于字节的输出操作,是所有输出流的父类。定义了所有输出流都具有的共同特征。 -
Reader
(文件格式操作):抽象类,基于字符的输入操作。 -
Writer
(文件格式操作):抽象类,基于字符的输出操作。 -
RandomAccessFile
(随机文件操作):一个独立的类,直接继承至Object
,它的功能丰富,可以从文件的任意位置进行存取(输入输出)操作。
Java中IO流的体系结构如图:
- Java 流类的类结构图:
1. 流的概念和作用
流:代表任何有能力产出数据的数据源对象或者是有能力接受数据的接收段对象。
流的本质:数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。
流的作用:为数据源和目的地建立一个输送通道。
Java中将输入输出抽象称为流,就好像水管,将两个容器连接起来。流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两设备间的传输称为流。
2、Java IO所采用的模型
Java的IO模型设计非常优秀,它使用Decorator
(装饰者)模式,按功能划分Stream
,可以动态装配这些Stream
,以便获得需要的功能。
例如,需要一个具有缓冲的文件输入流,则应当组合使用FileInputStream
和BufferedInputStream
。
3、IO流的分类
- 根据处理数据类型的不同分为:字节流和字符流
- 根据数据流向不同分为:输入流和输出流
- 按数据来源(去向)分类:
-
File
(文件):FileInputStream
,FileOutputStream
,FileReader
,FileWriter
-
byte[]
:ByteArrayInputStream
,ByteArrayOutputStream
-
char[]
:CharArrayReader
,CharArrayWriter
-
String
:StringBufferInputStream
,StringReader
,StringWriter
- 网络数据流:
InputStream
,OutputStream
,Reader
,Writer
-
3.1字节流和字符流
流序列中的数据既可以是未经加工的原始二进制数据,也可以是经一定编码处理后符合某种格式规定的特定数据。因此Java中的流分为两种:
- 字节流:数据流中最小的数据单元是字节
- 字符流:数据流中最小的数据单元是字符, Java中的字符是Unicode编码,一个字符占用两个字节。
字符流的由来: Java中字符是采用Unicode标准,一个字符是16位,即一个字符使用两个字节来表示。为此,JAVA中引入了处理字符的流。因为数据编码的不同,而有了对字符进行高效操作的流对象。本质其实就是基于字节流读取时,去查了指定的码表。
3.2输入流和输出流
根据数据的输入、输出方向的不同对而将流分为输入流和输出流。
- 输入流:程序从输入流读取数据源。数据源包括外界(键盘、文件、网络…),即是将数据源读入到程序的通信通道
- 输出流:程序向输出流写入数据。将程序中的数据输出到外界(显示器、打印机、文件、网络…)的通信通道。
采用数据流的目的就是使得输出输入独立于设备。
输入流( Input Stream
)不关心数据源来自何种设备(键盘,文件,网络)。
输出流( Output Stream
)不关心数据的目的是何种设备(键盘,文件,网络)。
3.3特性
相对于程序来说,输出流是往存储介质或数据通道写入数据,而输入流是从存储介质或数据通道中读取数据,一般来说关于流的特性有下面几点:
- 先进先出,最先写入输出流的数据最先被输入流读取到。
- 顺序存取,可以一个接一个地往流中写入一串字节,读出时也将按写入顺序读取一串字节,不能随机访问中间的数据。(
RandomAccessFile
可以从文件的任意位置进行存取(输入输出)操作) - 只读或只写,每个流只能是输入流或输出流的一种,不能同时具备两个功能,输入流只能进行读操作,对输出流只能进行写操作。在一个数据传输通道中,如果既要写入数据,又要读取数据,则要分别提供两个流。
4. Java IO流对象
4.1 字节输入流InputStream
IO 中输入字节流的继承图可见上图,可以看出:
-
InputStream
是所有的输入字节流的父类,它是一个抽象类。 -
ByteArrayInputStream
、StringBufferInputStream
、FileInputStream
是三种基本的介质流,它们分别从byte
数组、StringBuffer
和本地文件中读取数据。 -
PipedInputStream
是从与其它线程共用的管道中读取数据。 -
ObjectInputStream
和所有FilterInputStream
的子类都是装饰流(装饰器模式的主角)。
InputStream
类的定义如下:
public abstract class InputStream
extends Object
implements Closeable
InputStream
是一个抽象类,常用的操作方法如下:
方法名称 | 描述 |
---|---|
public void close() throws IOException |
关闭输入流,释放和这个流相关的系统资源。 |
public abstract int read() throws IOException |
读取字节,并返回读到的数据,如果返回-1,表示读到了输入流的末尾。 |
public int read(byte[] b) throws IOException |
将数据读入一个字节数组,同时返回实际读取的字节数。如果返回-1,表示读到了输入流的末尾。 |
public int read(byte[] b, int off, int len) throws IOException |
将数据读入一个字节数组,同时返回实际读取的字节数。如果返回-1,表示读到了输入流的末尾。off 指定在数组b 中存放数据的起始偏移位置;len 指定读取的最大字节数。 |
public long skip(long n) throws IOException |
在输入流中跳过n个字节,并返回实际跳过的字节数。 |
public int available() throws IOException |
返回在不发生阻塞的情况下,可读取的字节数。 |
public void mark(int readlimit) |
在输入流的当前位置放置一个标记,如果读取的字节数多于readlimit 设置的值,则流忽略这个标记。 |
public void reset() throws IOException |
返回到上一个标记。 |
public boolean markSupported() |
测试当前流是否支持mark 和reset 方法。如果支持,返回true ,否则返回false 。 |
流结束的判断:方法read()
的返回值为-1时;readLine()
的返回值为null时。
InputStream
本身属于抽象类,需要子类的支持,子类从文件中读取肯定是FileInputStream
方法名称 | 描述 |
---|---|
public FileInputStream(File file) throws FileNotFoundException |
通过File 类实例,创建文件输入流 |
4.2 字节输出流OutputStream
IO 中输出字节流的继承图可见上图,可以看出:
OutputStream
是所有的输出字节流的父类,它是一个抽象类。ByteArrayOutputStream
、FileOutputStream
是两种基本的介质流,它们分别向byte
数组、和本地文件中写入数据。PipedOutputStream
是向与其它线程共用的管道中写入数据。ObjectOutputStream
和所有FilterOutputStream
的子类都是装饰流。
OutputStream
类的定义:
public abstract class OutputStream
extends Object
implements Closeable, Flushable
OutputStream
是一个抽象类,常用的操作方法如下:
方法名称 | 描述 |
---|---|
public void close() throws IOException |
关闭输出流,释放所有与流相关的系统资源 |
public void flush() throws IOException |
刷新输出流,强制缓冲区中的输出字节被写出。 |
public abstract void write(int b) throws IOException |
往输出流中写入一个字节。 |
public void write(byte[] b) throws IOException |
往输出流中写入数组b 中的所有字节。 |
public void write(byte[] b, int off, int len) throws IOException |
往输出流中写入数组b 中从偏移量off 开始的len 个字节的数据。 |
OutputStream
是一个抽象类,抽象类需要通过子类来完成,要向文件中输出,使用FileOutputStream
类:
方法名称 | 描述 |
---|---|
public FileOutputStream(File file) throws FileNotFoundException |
接收File 类的实例,表示要操作的文件的位置 |
public FileOutputStream(File file, boolean append) throws FileNotFoundException |
接收File 类实例,并指定是否可以追加 |
4.3 字符输入流Reader
在上面的继承关系图中可以看出:
Reader
是所有的输入字符流的父类,它是一个抽象类。CharArrayReader
、StringReader
是两种基本的介质流,它们分别将char
数组、String
中读取数据。PipedReader
是从与其它线程共用的管道中读取数据。BufferedReader
很明显就是一个装饰器,它和其子类负责装饰其它Reader对象。FilterReader
是所有自定义具体装饰流的父类,其子类PushbackReader对Reader
对象进行装饰,会增加一个行号。InputStreamReader
是一个连接字节流和字符流的桥梁,它将字节流转变为字符流。FileReader
可以说是一个达到此功能、常用的工具类,在其源代码中明显使用了将FileInputStream
转变为Reader
的方法。我们可以从这个类中得到一定的技巧。Reader
中各个类的用途和使用方法基本和InputStream
中的类使用一致。后面会有Reader
与InputStream
的对应关系。
使用Reader
完成字符的输入功能,此类定义如下:
public abstract class Reader
extends Object
implements Readable, Closeable
Reader
类是抽象类,常用方法如下:
方法名称 | 描述 |
---|---|
public int read() throws IOException |
读取一个字符,并返回该字符,如果返回-1说明已经读到了流的末尾 |
public int read(char[] cbuf) throws IOException |
将一组字符读入cbuf 数组,返回读取的个数,如果返回-1说明已经读到了流的末尾 |
public abstract int read(char[] cbuf, int off, int len) throws IOException |
读取len 个字符,从数组cbuf[] 的下标off 处开始存放,返回值为实际读取的字符数量,该方法必须由子类实现 |
public abstract void close() throws IOException |
关闭字符输入流,释放所有与流相关的系统资源 |
Reader
类依然需要使用子类FileReader
类进行实例化操作,FileReader
类中的构造方法定义如下:
方法 | 描述 |
---|---|
public FileReader(File file) throws FileNotFoundException |
根据File 类构造FileReader 实例 |
4.3 字符输出流Writer
在上面的关系图中可以看出:
Writer
是所有的输出字符流的父类,它是一个抽象类。CharArrayWriter
、StringWriter
是两种基本的介质流,它们分别向char
数组、String
中写入数据。PipedWriter
是向与其它线程共用的管道中写入数据,BufferedWriter
是一个装饰器为Writer
提供缓冲功能。PrintWrite
r和PrintStream
极其类似,功能和使用也非常相似。OutputStreamWriter
是OutputStream
到Writer
转换的桥梁,它的子类FileWriter
其实就是一个实现此功能的具体类(具体可以研究一SourceCode)。功能和使用和OutputStream
极其类似.
抽象类Writer
的定义如下:
public abstract class Writer
extends Object
implements Appendable, Closeable, Flushable
常用方法如下:
方法 | 描述 |
---|---|
public void write(int c) throws IOException |
将整型值c 的低16位写入输出流 |
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 个字符写入输出流 |
FileWriter
为Writer
的子类,构造方法如下:
方法 | 描述 |
---|---|
public FileWriter(File file) throws IOException |
根据File 类构造FileWriter 实例 |
public FileWriter(File file boolean append) throws IOException |
根据File 类构造FileWriter 实例,可以追加内容 |
5. 输入与输出的对应
- 字节流输入与输出的对应
图中蓝色的为主要的对应部分,红色的部分就是不对应部分。从上面的图中可以看出JavaIO中的字节流是极其对称的。“存在及合理”我们看看这些字节流中不太对称的几个类吧!
LineNumberInputStream
主要完成从流中读取数据时,会得到相应的行号,至于什么时候分行、在哪里分行是由改类主动确定的,并不是在原始中有这样一个行号。在输出部分没有对应的部分,我们完全可以自己建立一个LineNumberOutputStream
,在最初写入时会有一个基准的行号,以后每次遇到换行时会在下一行添加一个行号,看起来也是可以的。好像更不入流了。PushbackInputStream
的功能是查看最后一个字节,不满意就放入缓冲区。主要用在编译器的语法、词法分析部分。输出部分的BufferedOutputStream
几乎实现相近的功能。StringBufferInputStream
已经被Deprecated
,本身就不应该出现在InputStream
部分,主要因为String
应该属于字符流的范围。已经被废弃了,当然输出部分也没有必要需要它了!还允许它存在只是为了保持版本的向下兼容而已。SequenceInputStream
可以认为是一个工具类,将两个或者多个输入流当成一个输入流依次读取。完全可以从IO包中去除,还完全不影响IO包的结构,却让其更“纯洁”――纯洁的Decorator模式。PrintStream
也可以认为是一个辅助工具。主要可以向其他输出流,或者FileInputStrea
m写入数据,本身内部实现还是带缓冲的。本质上是对其它流的综合运用的一个工具而已。一样可以踢出IO包!System.out
和System.out
就是PrintStream
的实例!
- 字符流输入与输出的对应
6. 字符流与字节流转换
转换流的特点:
其是字符流和字节流之间的桥梁
可对读取到的字节数据经过指定编码转换成字符
可对读取到的字符数据经过指定编码转换成字节
何时使用转换流?
当字节和字符之间有转换动作时;
流操作的数据需要编码或解码时。
具体的对象体现:
转换流:在IO中还存在一类是转换流,将字节流转换为字符流,同时可以将字符流转化为字节流。
InputStreamReader
:字节到字符的桥梁OutputStreamWriter
:字符到字节的桥梁
OutputStreamWriter(OutStream out)
:将字节流以字符流输出。
InputStreamReader(InputStream in)
:将字节流以字符流输入。
这两个流对象是字符体系中的成员,它们有转换作用,本身又是字符流,所以在构造的时候需要传入字节流对象进来。
7. 字节流和字符流的区别(重点)
- 字节流没有缓冲区,是直接输出的,而字符流是输出到缓冲区的。因此在输出时,字节流不调用colse()方法时,信息已经输出了,而字符流只有在调用close()方法关闭缓冲区时,信息才输出。要想字符流在未关闭时输出信息,则需要手动调用flush()方法。
- 读写单位不同:字节流以字节(8bit)为单位,字符流以字符为单位,根据码表映射字符,一次可能读多个字节。
- 处理对象不同:字节流能处理所有类型的数据(如图片、avi等),而字符流只能处理字符类型的数据。
结论:只要是处理纯文本数据,就优先考虑使用字符流。除此之外都使用字节流。
8. 非流式文件类——File
类
首先来看看File
类的定义
public class File
extends Object
implements Serializable, Comparable<File>
从定义看,File类是Object的直接子类,同时它继承了Comparable接口可以进行数组的排序。
File类的操作包括文件的创建、删除、重命名、得到路径、创建时间等,以下是文件操作常用的函数:
方法或常量 | 描述 |
---|---|
public static final String separator |
代表路径分隔符"" |
public static final String pathSeparator |
表示路径分隔,表示"." |
public File(String pathname) |
构造File 类实例,要传入路径 |
public boolean createNewFile() throws IOException |
创建新文件 |
public boolean delete() |
删除文件 |
public String getParent() |
得到文件的上一级路径 |
public boolean isDirectory() |
判断给定的路径是否是文件夹 |
public boolean isFile() |
判断给定的路径是否是文件 |
public String[] list() |
列出文件夹中的文件 |
public File[] listFiles() |
列出文件夹中的所有文件 |
public boolean mkdir() |
创建新的文件夹 |
public boolean renameTo(File dest) |
为文件重命名 |
public long length() |
返回文件大小 |
File类是对文件系统中文件以及文件夹进行封装的对象,可以通过对象的思想来操作文件和文件夹。File类保存文件或目录的各种元数据信息,包括文件名、文件长度、最后修改时间、是否可读、获取当前文件的路径名,判断指定文件是否存在、获得当前目录中的文件列表,创建、删除文件和目录等方法。
File类共提供了三个不同的构造函数,以不同的参数形式灵活地接收文件和目录名信息:
构造方法 | 例子 |
---|---|
File (String pathname) |
File f1=new File("FileTest1.txt"); //创建文件对象f1,f1所指的文件是在当前目录下创建的FileTest1.txt
|
File (String parent, String child) |
File f2=new File(“D:\\dir1","FileTest2.txt") ;``// 注意:D:\\dir1目录事先必须存在,否则异常 |
File (File parent, String child) |
File f4=new File("\\dir3"); File f5=new File(f4,"FileTest5.txt"); //在如果 \\dir3目录不存在使用f4.mkdir()先创建
|
示例:
public class FileDemo1 {
public static void main(String[] args) {
File file = new File("D:" + File.separator + "test.txt");
if (file.exists()) {
file.delete();
} else {
try {
file.createNewFile();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
9. RandomAccessFile
类
先来看定义
public class RandomAccessFile
extends Object
implements DataOutput, DataInput, Closeable
下面是该类的两个构造方法:
该类并不是流体系中的一员,其封装了字节流,同时还封装了一个缓冲区(字符数组),通过内部的指针来操作字符数组中的数据。该对象特点:
该对象只能操作文件,所以构造函数接收两种类型的参数:a.字符串文件路径;b.File对象。
该对象既可以对文件进行读操作,也能进行写操作,在进行对象实例化时可指定操作模式(r,rw)
注意:该对象在实例化时,如果要操作的文件不存在,会自动创建;如果文件存在,写数据未指定位置,会从头开始写,即覆盖原有的内容。 可以用于多线程下载或多个线程同时写数据到文件。
10. System
类对IO的支持
针对一些频繁的设备交互,Java语言系统预定了3个可以直接使用的流对象,分别是:
-
System.in
(标准输入),通常代表键盘输入。 -
System.out
(标准输出):通常写往显示器。 -
System.err
(标准错误输出):通常写往显示器。
附加
IOException
异常类的子类:
public class EOFException
: 非正常到达文件尾或输入流尾时,抛出这种类型的异常。public class FileNotFoundException
: 当文件找不到时,抛出的异常。public class InterruptedIOException
: 当I/O操作被中断时,抛出这种类型的异常。