Java IO 系统

Java IO 概要

为了方便理解和阐述,先引入两张图:

  • Java IO中的常用的类

    说明
    File 文件夹
    RandomAccessFile 随机存取文件夹
    InputStream 字节输入流
    OutputStream 字节输出流
    Reader 字符输入流
    Writer 字符输出流

    在整个java.io包中最重要的就是五个类和一个接口。五个类指的是File, InputStream, OutputStream, Reader, Writer;一个接口指的是Serializable,掌握了这些IO的核心操作那么对于Java中的IO体系也就有了一个初步的认识。

    Java IO主要包括如下几个层次,包含三个部分:

    1. 流式部分——IO的主体部分;
    2. 非流式部分——主要包含一些辅助流式部分的类,如File类、RandomAccessFile类和FileDescriptor等类;
    3. 其他类——文件读取部分的与安全相关的类,如SerializablePermission类,以及与本地操作系统相关的文件系统的类,如:FileSystem类和Win32FileSystem类等等。

    主要的类如下:

    1. File(文件特征与管理):用于文件或者目录的描述信息,例如生成新目录,修改文件名,删除文件,判断文件所在路径等。
    2. InputStream(二进制格式操作):抽象类,基于字节的输入操作,是所有输入流的父类。定义了所有输入流都具有的共同特征。
    3. OutputStream(二进制格式操作):抽象类,基于字节的输出操作,是所有输出流的父类。定义了所有输出流都具有的共同特征。
    4. Reader(文件格式操作):抽象类,基于字符的输入操作。
    5. Writer(文件格式操作):抽象类,基于字符的输出操作。
    6. RandomAccessFile(随机文件操作):一个独立的类,直接继承至Object,它的功能丰富,可以从文件的任意位置进行存取(输入输出)操作。

    Java中IO流的体系结构如图:

image-20200324145737944.png
  • Java 流类的类结构图:
image-20200324233249855.png

1. 流的概念和作用

流:代表任何有能力产出数据的数据源对象或者是有能力接受数据的接收段对象。

流的本质:数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。

流的作用:为数据源和目的地建立一个输送通道。

Java中将输入输出抽象称为流,就好像水管,将两个容器连接起来。流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两设备间的传输称为流。

2、Java IO所采用的模型

Java的IO模型设计非常优秀,它使用Decorator(装饰者)模式,按功能划分Stream,可以动态装配这些Stream,以便获得需要的功能。

​ 例如,需要一个具有缓冲的文件输入流,则应当组合使用FileInputStreamBufferedInputStream

3、IO流的分类

  • 根据处理数据类型的不同分为:字节流和字符流
  • 根据数据流向不同分为:输入流和输出流
  • 按数据来源(去向)分类:
    1. File(文件): FileInputStream, FileOutputStream, FileReader, FileWriter
    2. byte[]ByteArrayInputStream, ByteArrayOutputStream
    3. char[]: CharArrayReader,CharArrayWriter
    4. String:StringBufferInputStream, StringReader, StringWriter
    5. 网络数据流:InputStream,OutputStream, Reader, Writer

3.1字节流和字符流

流序列中的数据既可以是未经加工的原始二进制数据,也可以是经一定编码处理后符合某种格式规定的特定数据。因此Java中的流分为两种:

  • 字节流:数据流中最小的数据单元是字节
  • 字符流:数据流中最小的数据单元是字符, Java中的字符是Unicode编码,一个字符占用两个字节。

字符流的由来: Java中字符是采用Unicode标准,一个字符是16位,即一个字符使用两个字节来表示。为此,JAVA中引入了处理字符的流。因为数据编码的不同,而有了对字符进行高效操作的流对象。本质其实就是基于字节流读取时,去查了指定的码表。

3.2输入流和输出流

根据数据的输入、输出方向的不同对而将流分为输入流和输出流。

  • 输入流:程序从输入流读取数据源。数据源包括外界(键盘、文件、网络…),即是将数据源读入到程序的通信通道
  • 输出流:程序向输出流写入数据。将程序中的数据输出到外界(显示器、打印机、文件、网络…)的通信通道。

采用数据流的目的就是使得输出输入独立于设备。

输入流( Input Stream )不关心数据源来自何种设备(键盘,文件,网络)。
输出流( Output Stream)不关心数据的目的是何种设备(键盘,文件,网络)。

3.3特性

相对于程序来说,输出流是往存储介质或数据通道写入数据,而输入流是从存储介质或数据通道中读取数据,一般来说关于流的特性有下面几点:

  1. 先进先出,最先写入输出流的数据最先被输入流读取到。
  2. 顺序存取,可以一个接一个地往流中写入一串字节,读出时也将按写入顺序读取一串字节,不能随机访问中间的数据。(RandomAccessFile可以从文件的任意位置进行存取(输入输出)操作
  3. 只读或只写,每个流只能是输入流或输出流的一种,不能同时具备两个功能,输入流只能进行读操作,对输出流只能进行写操作。在一个数据传输通道中,如果既要写入数据,又要读取数据,则要分别提供两个流。

4. Java IO流对象

4.1 字节输入流InputStream

image-20200324190144476.png

IO 中输入字节流的继承图可见上图,可以看出:

  1. InputStream是所有的输入字节流的父类,它是一个抽象类。
  2. ByteArrayInputStreamStringBufferInputStreamFileInputStream是三种基本的介质流,它们分别从byte数组、StringBuffer和本地文件中读取数据。
  3. PipedInputStream是从与其它线程共用的管道中读取数据。
  4. 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() 测试当前流是否支持markreset方法。如果支持,返回true,否则返回false

流结束的判断:方法read()的返回值为-1时;readLine()的返回值为null时。

InputStream本身属于抽象类,需要子类的支持,子类从文件中读取肯定是FileInputStream

方法名称 描述
public FileInputStream(File file) throws FileNotFoundException 通过File类实例,创建文件输入流

4.2 字节输出流OutputStream

image-20200324192359100.png

IO 中输出字节流的继承图可见上图,可以看出:

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

  2. ByteArrayOutputStreamFileOutputStream是两种基本的介质流,它们分别向byte数组、和本地文件中写入数据。PipedOutputStream是向与其它线程共用的管道中写入数据。

  3. 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

image-20200324200059503.png

在上面的继承关系图中可以看出:

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

  2. CharArrayReaderStringReader是两种基本的介质流,它们分别将char数组、String中读取数据。PipedReader是从与其它线程共用的管道中读取数据。

  3. BufferedReader很明显就是一个装饰器,它和其子类负责装饰其它Reader对象。

  4. FilterReader是所有自定义具体装饰流的父类,其子类PushbackReader对Reader对象进行装饰,会增加一个行号。

  5. InputStreamReader是一个连接字节流和字符流的桥梁,它将字节流转变为字符流。FileReader可以说是一个达到此功能、常用的工具类,在其源代码中明显使用了将FileInputStream转变为Reader的方法。我们可以从这个类中得到一定的技巧。Reader中各个类的用途和使用方法基本和InputStream中的类使用一致。后面会有ReaderInputStream的对应关系。

使用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

image-20200324202108495.png

在上面的关系图中可以看出:

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

  2. CharArrayWriterStringWriter是两种基本的介质流,它们分别向char数组、String中写入数据。PipedWriter是向与其它线程共用的管道中写入数据,

  3. BufferedWriter是一个装饰器为Writer提供缓冲功能。

  4. PrintWriter和PrintStream极其类似,功能和使用也非常相似。

  5. OutputStreamWriterOutputStreamWriter转换的桥梁,它的子类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个字符写入输出流

FileWriterWriter的子类,构造方法如下:

方法 描述
public FileWriter(File file) throws IOException 根据File类构造FileWriter实例
public FileWriter(File file boolean append) throws IOException 根据File类构造FileWriter实例,可以追加内容

5. 输入与输出的对应

  • 字节流输入与输出的对应
image-20200324213526828.png

图中蓝色的为主要的对应部分,红色的部分就是不对应部分。从上面的图中可以看出JavaIO中的字节流是极其对称的。“存在及合理”我们看看这些字节流中不太对称的几个类吧!

  1. LineNumberInputStream主要完成从流中读取数据时,会得到相应的行号,至于什么时候分行、在哪里分行是由改类主动确定的,并不是在原始中有这样一个行号。在输出部分没有对应的部分,我们完全可以自己建立一个LineNumberOutputStream,在最初写入时会有一个基准的行号,以后每次遇到换行时会在下一行添加一个行号,看起来也是可以的。好像更不入流了。

  2. PushbackInputStream的功能是查看最后一个字节,不满意就放入缓冲区。主要用在编译器的语法、词法分析部分。输出部分的BufferedOutputStream几乎实现相近的功能。

  3. StringBufferInputStream已经被Deprecated,本身就不应该出现在InputStream部分,主要因为String应该属于字符流的范围。已经被废弃了,当然输出部分也没有必要需要它了!还允许它存在只是为了保持版本的向下兼容而已。

  4. SequenceInputStream可以认为是一个工具类,将两个或者多个输入流当成一个输入流依次读取。完全可以从IO包中去除,还完全不影响IO包的结构,却让其更“纯洁”――纯洁的Decorator模式。

  5. PrintStream也可以认为是一个辅助工具。主要可以向其他输出流,或者FileInputStream写入数据,本身内部实现还是带缓冲的。本质上是对其它流的综合运用的一个工具而已。一样可以踢出IO包!System.outSystem.out就是PrintStream的实例!

  • 字符流输入与输出的对应
image-20200324221704325.png

6. 字符流与字节流转换

转换流的特点:

  1. 其是字符流和字节流之间的桥梁

  2. 可对读取到的字节数据经过指定编码转换成字符

  3. 可对读取到的字符数据经过指定编码转换成字节

何时使用转换流?

  1. 当字节和字符之间有转换动作时;

  2. 流操作的数据需要编码或解码时。

具体的对象体现:

转换流:在IO中还存在一类是转换流,将字节流转换为字符流,同时可以将字符流转化为字节流。

  1. InputStreamReader:字节到字符的桥梁

  2. 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

下面是该类的两个构造方法:

image-20200324232359449.png

该类并不是流体系中的一员,其封装了字节流,同时还封装了一个缓冲区(字符数组),通过内部的指针来操作字符数组中的数据。该对象特点:

  1. 该对象只能操作文件,所以构造函数接收两种类型的参数:a.字符串文件路径;b.File对象。

  2. 该对象既可以对文件进行读操作,也能进行写操作,在进行对象实例化时可指定操作模式(r,rw)

注意:该对象在实例化时,如果要操作的文件不存在,会自动创建;如果文件存在,写数据未指定位置,会从头开始写,即覆盖原有的内容。 可以用于多线程下载或多个线程同时写数据到文件。

10. System类对IO的支持

针对一些频繁的设备交互,Java语言系统预定了3个可以直接使用的流对象,分别是:

  • System.in(标准输入),通常代表键盘输入。
  • System.out(标准输出):通常写往显示器。
  • System.err(标准错误输出):通常写往显示器。

附加

IOException异常类的子类:

  1. public class EOFException : 非正常到达文件尾或输入流尾时,抛出这种类型的异常。

  2. public class FileNotFoundException: 当文件找不到时,抛出的异常。

  3. public class InterruptedIOException: 当I/O操作被中断时,抛出这种类型的异常。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容