第9章 - Java IO

9 - Java IO

 

1. IO模型介绍

1.1 Linux的网络IO模型

    Linux内核将所有外部设备都可以看成一个文件操作(那么对外部设备的操作都可以看成对文件进行操作)

    对一个文件的读写,都通过调用内核提供的系统调用

    内核给我们返回一个file descriptor(fd, 文件描述符)。而对一个socket的读写也会有相应的描述符,称为socketfd(socket描述符),描述符就是一个数字,指向内核中一个结构体(文件路径, 数据区等一些属性)

先对一些方法进行讲解:

1).recvfromSocket接收数据, 函数原型如下:

    ssize_t recvfrom(

       int sockfd, // 标识一个已连接套接口的描述字

       void *buf, // 接收数据缓冲区

       size_t len, // 缓冲区长度

       unsigned int flags,  // 调用操作方式(一个或多个标志组合)

       struct sockaddr *from, // [可选]指针, 指向装有源地址的缓冲区

       socket_t *fromlen // [可选]指针, 指向from缓冲区长度值

    );

2).select同步阻塞方式。几乎所有unixlinux都支持的一种多路IO方式, 通过select函数发出IO请求后, 线程阻塞, 一直到数据准备完毕, 然后才能把数据从核心空间拷贝到用户空间

3).pollpollselect的使用方法进行了一些改进, 突破了最大文件数的限制, 同时使用更加方便一些

4).epoll是为了解决select/poll的性能问题。基本思路如下:

       {1}.专门的内核线程来不停地扫描fd列表

       {2}.有结果后,把结果放到fd相关的链表中

       {3}.用户线程只需要定期从该fd对应的链表中读取事件就可以了

       {4}.为了节省把数据从核心空间拷贝到用户空间的消耗, 采用了mmap方式, 允许程序在用户空间直接访问数据所在的内核空间, 不需要把数据copy一份

服务器端编程经常需要构造高性能的IO模型, 常见的IO模型有四种, Unix提供了五种IO模型

IO模型

说明

同步阻塞IO

(Blocking IO)

传统的IO模型,Java中老的BIO便是这种模式。在接到事件(数据到达、数据拷贝完成等)前程序需阻塞等待.

优点:编码简单。缺点:效率低, 处理程序阻塞会导致cpu利用率很低

同步非阻塞IO

(Non-blocking IO)

默认创建的socket都是阻塞的,非阻塞IO要求socket被设置为NONBLOCK

在未接到事件时处理程序一直主动轮询, 这样处理程序无需阻塞, 可以在轮询间歇去干别的, 但是轮询会造成重复请求, 同样浪费资源. 以前Java中实现的的伪异步(AIO)模式就是采用这种思想

IO多路复用

(IO Multiplexing)

异步阻塞IO

经典的Reactor设计模式,Java中的SelectorLinux中的epoll都是这种模型。

增加了对socket的事件监听器(selector), 从而把处理程序和对应的socket事件解耦, 所用的socket连接都注册在监听器, 在等待阶段只有监听器会阻塞, 处理线程从监听器获取事件对socket连接处理即可, 而且一个处理线程可以对应多个连接(前两种一般都是一个socket连接起一个线程, 这就是为什么叫复用), 优点是节省资源, 由于处理程序能够被多个连接复用, 因此少数的线程就能处理大量连接. 缺点同样因为复用, 如果是大量费时处理的连接(如大量连接上传大文件), 很容易造成线程占满而导致新连接失败

信号驱动IO模型

在数据准别阶段无需阻塞, 只需向系统注册一个信号, 在数据准备好后, 系统会响应该信号. 该模型依赖于系统实现, 而且信号通信使用比较麻烦, 因此Java中未有对应实现

异步IO

(Asynchronous IO)

异步非阻塞IO

经典的Proactor设计模式,与信号驱动IO很类似,而且在数据拷贝阶段(指数据从系统缓冲区拷贝至程序自己的缓冲区,其他模型改阶段程序都需要阻塞等待)同样可以异步处理. 优点:效率很高;缺点:依赖系统底层实现。JDK1.7之后在concurrent包中提供

 

1.1.1 BIO - 同步阻塞I/O模型

    同步阻塞IO模型是最常用的IO模型, 用户线程在内核进行IO操作时被阻塞, 默认所有文件操作都是阻塞的.

    Socket(套接字)为例来讲解此模型. 在进程空间中调用recvfrom, 其系统调用直到数据报到达且被拷贝到应用进程的缓冲区或者发生错误才返回, 期间一直在等待. 我们就说进程从调用recvfrom开始到它返回的整段时间内是被阻塞的.

    用户线程通过系统调用read发起IO读操作,由用户空间转到内核空间。内核等到数据包到达后,然后将接收的数据拷贝到用户空间,完成read操作。所以用户需要等待readsocket中的数据读取到buffer后,才能继续处理数据。整个过程用户线程都是被阻塞的。则阻塞中,CPU的利用效率就很低了

    用户线程使用同步阻塞IO模型的伪代码描述为:

{

    read(socket, buffer);

    process(buffer);

}

 

1.1.2 N-BIO - 同步非阻塞IO模型

    同步非阻塞IO是在同步阻塞IO的基础上, socket设置为NONBLOCK. 这样做用户线程可以在发起IO请求后可以立即返回。recvfrom从应用层到内核的时候,如果缓冲区没有数据的话,就直接返回一个EWOULDBLOCK错误,一般都对非阻塞IO模型进行轮询检查这个状态,看内核是不是有数据到来

    由于socket是非阻塞的方式,用户线程需要不断read,尝试读取socket数据,直到读取成功,才能继续处理。

    优点:调用read立即返回,如果读取失败,可以做其他事

    缺点:需要不断轮询请求,直至成功才能读取数据继续处理,会消耗大量CPU资源

    用户线程使用同步非阻塞IO模型的伪代码描述为:

{

    while(read(socket, buffer) != SUCCESS);

    process(buffer);

}

 

1.1.3 IO多路复用模型

    建立在内核提供的多路分离函数select基础之上的,避免同步非阻塞IO模型中轮询等待的问题。

    Linux提供select/poll,进程通过将一个或多个fd传递给selectpoll系统调用,阻塞在select。由select/poll帮我们侦测fd是否就绪。但select/poll是顺序扫描fd是否就绪, 且支持的fd数量有限.

    用户线程,先将需要进行IO操作的socket添加到select中,然后阻塞等待select系统调用返回。当数据到达时,socket被激活,select函数返回。用户线程正式发起read请求,读取数据并继续执行

    优点:可以在一个线程内同时处理多个SocketIO请求,用户可以注册多个Socket,然后不断调用select读取被激活的Socket。可以提高CPU利用率

    缺点:使用select函数进行IO请求与BIO没什么区别,而且还多了监视Socket等操作。效率更差

    其中while循环前将socket添加到select监视中, 然后在while内一直调用select获取被激活的socket, 一旦socket可读, 便调用read函数将socket中的数据读取出来.

    用户线程使用select函数的伪代码描述为:

{

    select(socket);

    while(1) {

       sockets = select();

       for(socket in sockets) {

           if(can_read(socket)) {

              read(socket, buffer);

              process(buffer);

           }

       }

    }

}

 

1.1.3.1 Reactor模型

    IO多路复用模型使用了Reactor设计模式实现了这一机制。Linux还提供了一个epoll系统调用,epoll是基于事件驱动方式,而不是顺序扫描,当有fd就绪时,立即回调函数rollback.

    通过Reactor方式,用户线程可以将轮询IO操作状态的工作统一交给handle_events事件循环进行处理. 用户线程注册事件处理器之后可以继续执行做其他的工作(异步)

    Reactor负责调用内核的select函数检查socket状态. 当有socket被激活时, 则通知相应的用户线程(或执行用户线程的回调函数), 执行handle_event进行数据读取、处理的工作。由于select函数是阻塞的, 因此多路IO复用模型也被称为异步阻塞IO模型. 注意, 这里的所说的阻塞是指select函数执行时线程被阻塞, 而不是指socket. 一般在使用IO多路复用模型时, socket都是设置为NONBLOCK, 不过这并不会产生影响, 因为用户发起IO请求时, 数据已经到达了, 用户线程一定不会被阻塞

    IO多路复用是最常使用的IO模型, 但是其异步程度还不够"彻底", 因为它使用了会阻塞线程的select系统调用. 因此IO多路复用只能称为异步阻塞IO, 而非真正的异步IO

EventHandler表示IO事件处理器, 它拥有IO文件句柄Handle, 以及对Handle的操作

    1).get_handler():获取IO文件句柄

    2).handle_evnet():处理读/写事件

Concrete继承EventHandler。对事件处理器的行为进行定制

Reactor用于管理EventHandler,主要是注册、删除、和处理

    1).register_event():注册事件处理器

    2).remove_event():移除事件处理器

    3).handle_events():进行读取、处理工作。主要是调用Synchronous Event Demutiplexerselect()

Synchronous Event Demutiplexer同步事件多路分离器,一般是内核

    select():只要某个文件句柄被激活(可读/写等)select就返回(阻塞)Reactorhandle_events就会调用与文件句柄关联的事件处理器的handle_event进行相关操作.

    用户线程使用IO多路复用模型的伪代码描述为:

void UserEventHandler::handle_event() {

    if(can_read(socket)) {

       read(socket, buffer);

       process(buffer);

    }

}

{

    Reactor.register(new UserEventHandler(socket));

}

    用户需要重写EventHandlerhandle_event函数进行读取数据、处理数据的工作, 用户线程只需要将自己的EventHandler注册到Reactor即可. Reactorhandle_events事件循环的伪代码大致如下

Reactor::handle_events() {

    while(1) {

       sockets = select();

       for(socket in sockets) {

           get_event_handler(socket).handle_event();

       }

    }

}

 

1.1.4 信号驱动IO模型

    首先开启套接口信号驱动IO功能, 并通过系统调用sigaction执行一个信号处理函数(此系统调用立即返回, 进程继续工作, 它是非阻塞的). 当数据准备就绪时, 就为该进程生成一个SIGIO信号. 随即可以在信号处理程序中调用recvfrom来读数据, 并通知循环函数处理数据. 如图1-4所示

 

1.1.5 异步IO模型

    告知内核启动某个操作,并让内核在某个操作完成后(包括将数据从内核拷贝到用户自己的缓冲区)通知我们

    这种模型与信号驱动模型的主要区别是:

       1).IO复用:事件循环将文件句柄状态事件给用户线程, 由用户处理.

       2).信号驱动IO:由内核通知我们何时可以启动一个IO操作

       3).异步IO:由内核通知我们IO操作何时完成

    "真正"的异步IO需要操作系统更强的支持. IO多路复用模型中, 事件循环将文件句柄的状态事件通知给用户线程, 由用户线程自行读取数据、处理数据. 而在异步IO模型中, 当用户线程收到通知时, 数据已经被内核读取完毕, 并放在了用户线程指定的缓冲区内, 内核在IO完成后通知用户线程直接使用即可

    异步IO模型使用了Proactor设计模式实现了这一机制

 

    Proactor模式和Reactor模式在结构上比较相似

    在用户线程(Client)使用方式差别很大。

    Reactor模式中,用户线程向Reactor对象注册感兴趣的事件监听, 然后事件触发时调用事件处理函数.

    Proactor模式中,用户线程将AsynchronousOperation(/写等)Proactor以及操作完成时的CompletionHandler注册到AsynchronousOperationProcessorAsynchronousOperationProcessor使用Facade模式提供了一组异步操作API(/写等)供用户使用, 当用户线程调用异步API, 便继续执行自己的任务. AsynchronousOperationProcessor 会开启独立的内核线程执行异步操作, 实现真正的异步. 当异步IO操作完成时, AsynchronousOperationProcessor将用户线程与AsynchronousOperation一起注册的ProactorCompletionHandler取出, 然后将CompletionHandlerIO操作的结果数据一起转发给Proactor, Proactor负责回调每一个异步操作的事件完成处理函数handle_event. 虽然Proactor模式中每个异步操作都可以绑定一个Proactor对象, 但是一般在操作系统中, Proactor被实现为Singleton模式, 以便于集中化分发操作完成事件

    异步IO模型中, 用户线程直接使用内核提供的异步IO API发起read请求, 且发起后立即返回, 继续执行用户线程代码. 不过此时用户线程已经将调用的AsynchronousOperationCompletionHandler注册到内核, 然后操作系统开启独立的内核线程去处理IO操作. read请求的数据到达时, 由内核负责读取socket中的数据, 并写入用户指定的缓冲区中. 最后内核将read的数据和用户线程注册的CompletionHandler分发给内部Proactor, ProactorIO完成的信息通知给用户线程(一般通过调用用户线程注册的完成事件处理函数), 完成异步IO

    用户线程使用异步IO模型的伪代码描述为:

void UserCompletionHandler::handle_event(buffer) {

    process(buffer);

}

{

    aio_read(socket, new UserCompletionHandler);

}

    用户需要重写CompletionHandlerhandle_event函数进行处理数据的工作, 参数buffer表示Proactor已经准备好的数据, 用户线程直接调用内核提供的异步IO API, 并将重写的CompletionHandler注册即可

    相比于IO多路复用模型, 异步IO并不十分常用, 不少高性能并发服务程序使用IO多路复用模型 + 多线程任务处理的架构基本可以满足需求. 况且目前操作系统对异步IO的支持并非特别完善, 更多的是采用IO多路复用模型模拟异步IO的方式(IO事件触发时不直接通知用户线程, 而是将数据读写完毕后放到用户指定的缓冲区中).

 

1.2 epoll - IO复用技术

    Java NIO的核心类库多路复用器Selector就是基于epoll的多路复用技术实现 IO编程过程中。

    当需要处理多个请求时, 可以利用多线程或者IO多路复用技术进行处理. I/O多路复用技术通过把多个I/O的阻塞复用同一个select阻塞上, 从而使得系统在单线程的情况下可以同时处理多个客户端请求. 与传统的多线程/多进程模型比, I/O多路复用的最大是系统开销小, 系统不需要建立新的进程或线程, 也不需要维护这些线程和进程的运行, 降低了系统的维护工作量, 节省了系统资源. IO多路复用的主要应用场景:

       1).服务器需要同时处理多个处于监听状态和多个连接状态的套接字

       2).服务器需要处理多种网络协议的套接字

    目前支持I/O多路复用的系统调用有selectpselectpollepoll

    epoll是为了克服select/poll的缺点出现的,具体如下

1).支持一个进程打开的socket描述符(FD)不受限制(仅受限于操作系统的最大文件句柄数):

    select:一个进程打开的FD有一定限制,由FD_SETSIZE(默认2048)设置。对于那些需要支持的上万TCP连接数目的大型服务器来说显然太少了

    {1}.可以选择修改这个宏,重新编译内核。但会带来网络效率的下降.

    {2}.可以选多进程的解决方案。虽然Linux上面创建进程的代价比较小,但仍旧是不可忽视的。其次进程间的数据交换非常麻烦,Java由于没有共享内存,需要通过Socket通信或其他方式进行数据同步,带来了额外的性能损耗, 增加了程序复杂的, 所以也不是一种完美的解决方案.

    epoll:支持FD上限是最大可以打开文件的数目,这个数字一般远大于2048。一般在1GB内存的机器上大约是10万左右, 具体数目可以cat/proc/sys/fs/file- max察看,通常情况下这个数目和系统内存关系很大

2).I/O效率不会随着FD数目增加而线性下降:

    select/poll:当拥有很大的Socket集合,由于网络延时,任一时间只有部分的Socket"活跃", 但是select/poll每次调用都会线性扫描全部的集合,导致效率呈现线性下降。

    epoll:它只对"活跃"Socket进行操作。在内核实现中, epoll是根据每个fdcallback函数实现的。只有活跃的Socket才会主动调用callback函数, 其他idle状态Socket则不会. 在这点上, epoll实现了一个""AIO"

3).使用mmap加速内核与用户控件的消息传递:

    selectpollepoll:都需要内核把FD消息通知给用户空间,如何避免不必要的内存拷贝就很重要,epoll是通过内核与用户空间mmap同一块内存实现的

4).epollAPI更加简单:

    创建epoll描述符、添加监听事件、阻塞等待所监听的事件发生、关闭epoll描述符.

    当然用来克服select/poll缺点的方法不只有epoll(epollLinux的实现方案)

       1).freeBSD下有kqueue    2). Solaris下由dev/poll

 

 

 

2. Java IO概述

    Java I/O操作类在包java.io下,大概可以分为如下4组:

       1).基于 字节 操作的I/O操作:InputStreamOutputStream

       2).基于 字符 操作的I/O操作:WriterReader

       3).基于 磁盘 操作的I/O操作:File

       4).基于 网络 操作的I/O操作:Socket

    2组主要是传输数据的数据格式,后两组主要是传输数据的方式。

    I/O的核心问题在于,影响I/O操作因素,要么是数据格式,要么是传输数据方式

 

2.1 流概述

    流是一组有序的数据序列,I/O(Input/Output)流提供了一条通道程序,可以通过这条通道把源中的字节序列送到目的地。

    1).IO流用来处理设备之间的数据传输。Java用于操作流的对象都在IO包中

    2).Java对数据的操作是通过流的方式

    3).流按操作数据分为两种, 字节流(InputStreamOutputStream)、字符流(ReaderWriter)

    4).流按流向分为: 输入流(InputStreamReader)、输出流(OutputStreamWriter)

输入:外部媒介(文件、网络、压缩包、其他数据源) -> InputStream数据 -> 计算机,

输出:计算机 -> OutputStream数据 -> 外部媒介(文件、网络、压缩包、其他数据源)

    Java IO库虽然庞大,单在实际应用中,一般都是使用它们的子类,大致Java提供的各子类用途汇总:

    1).文件访问       2).网络访问      3).内存缓存访问      4).线程内部通信(管道)

    5).缓冲           6).过滤          7).解析             8).读写文本 (Reader/Writer)

    9).读写对象、基本类型数据

 

2.2 基于字节的IO操作接口

    只支持8位字节(1byte), 不能很好控制16Unicode字符

    基于字节IO操作接口输入和输出分别是InputStreamOutputStream

 

2.2.1 InputStream - 输入字节流

    InputStream类是字节输入流的抽象类, 是所有字节输入流的抽象父类

InputStream类常用的方法

(标记阻塞: 在某个字符可用、发生 I/O 错误或者已到达流的末尾前,方法一直阻塞)

方法

说明

public abstract int read()

throws IOException

阻塞,从输入流中读取数据的下一个字节. 返回0~255范围内的int字节值. 如果因为已经到达流末尾而没有可用的字节, 则返回值-1

public read(byte[] b) throws IOException

阻塞,从输入流中读入一定长度的字节, 并以整数形式返回字节数

public int read(byte b[], int off, int len) throws IOException

阻塞,在off偏移位置开始, 从输入流中读入长度len的字节, 并以整数形式返回字节数

public long skip(long n) throws IOException

跳过输入流上的n个字节并返回实际跳过的字节数

public int available() throws IOException

不阻塞,从输入流读取/跳过的估计字节数;如果到达输入流末尾,则返回 0

public void close() throws IOException

关闭此输入流并释放与该流关联的所有系统资源

public synchronized void mark(int readlimit) throws IOException

在输入流的当前位置放置一个标记, readlimit参数告知此输入流在标记位置失效之前允许读取的字节数

public synchronized void reset()

throws IOException

将输入流指针返回到当前所做的标记处

public boolean markSupported()

throws IOException

如果当前支持的n个字节并返回实际跳过的字节数

[注:不是所有InputStream类的子类都支持所有方法, skip(), mark(), reset(), 这些方法只对某些子类有用]

InputStream:是所有的输入字节流的抽象父类

    |-- ByteArrayInputStream:介质类,Byte数组

    |-- FileInputStream:介质类,本地文件读取数据

    |-- ObjectInputStream

    |-- PipedInputStream:是从与其他线程的管道中读取数据

    |-- SequenceInputStream

    |-- StringBufferInputStream:已过时,介质类,底层使用StringBuffer。推荐使用StringReader

    |-- FilterInputStream:装饰类(装饰器模式主角)

       |-- BufferInputStream

       |-- DataInputStream

       |-- LineNumberInputStream

       |-- PushbackInputStream

介绍

ByteArrayInputStream

功能:将内存中的Byte数组适配为一个InputStream

构造:从内存中的Byte数组创建该对象(2种方法)

使用:一般作为数据源, 会使用其他装饰流提供额外的功能, 一般都建议加个缓冲功能

FileInputStream

功能:最基本的文件输入流. 主要用于从文件中读取信息

构造:通过一个代表文件路径的 StringFile对象或者 FileDescriptor对象创建

使用:一般作为数据源, 同样会使用其它装饰器提供额外的功能

PipedInputStream

功能:读取从对应PipedOutputStream写入的数据. 在流中实现了管道的概念

构造:利用对应的PipedOutputStream创建

使用:在多线程程序中作为数据源, 同样会使用其它装饰器提供额外的功能

ObjectInputStream

功能:对象输入流

构造:

使用:

SequenceInputStream

功能:2个或者多个InputStream 对象转变为一个InputStream 

构造:使用两个InputStream(或子类)对象创建该对象

使用:一般作为数据源, 同样会使用其它装饰器提供额外的功能

装饰、输入字节流FilterInputStream:给其它被装饰对象提供额外功能的抽象类

DataInputStream

功能:一般与DataOutputStream配对使用,完成基本数据类型的读写

构造:利用一个InputStream构造

使用:提供了大量的读取基本数据类新的读取方法

BufferedInputStream

功能:阻止每次读取一个字节都会频繁操作IO. 将字节读取一个缓存区,从缓存区读取 

构造:利用一个InputStream、或者带上一个自定义的缓存区的大小构造

使用:使用InputStream的方法读取, 多一个缓存的功能. 设计模式中透明装饰器的应用

LineNumberInputStream

功能:跟踪输入流中的行号

构造:利用一个InputStream构造

使用:增加getLineNumber() setLineNumber(int)方法得到和设置行号  

PushbackInputStream

功能:可以在读取最后一个byte 后将其放回到缓存中 

构造:利用一个InputStream构造

使用:仅仅会在设计compilerscanner时会用到这个类, 在我们的java语言的编译器中使用它. 很多程序员可能一辈子都不需要

 

2.2.2 OutputStream - 输出字节流

    OutputStream类是字节输出流的抽象类, 是所有字节输出流的抽象父类

OutputStream类常用的方法

方法

说明

public abstract void write(int b) throws IOException

将指定的字节写入此输出流

public void write(byte b[]) throws IOException

将指定byte数组写入此输出流

public void write(byte b[], int off, int len) throws IOException

将指定byte数组中从偏移量off开始的len个字节写入此输出流

public void flush() throws IOException

彻底完成输出并清空缓存区

public void close() throws IOException

关闭输出流

OutputStream:是所有的输出字节流的父类

    |-- ByteArrayOutputStream:介质类,Byte数组

    |-- FileOutputStream:介质类,本地文件读取数据

    |-- ObjectOutputStream

    |-- PipedOutputStream:向与其它线程共用的管道中写入数据

    |-- FilterOutputStream:装饰类(装饰器模式主角)

       |-- BufferOutputStream

       |-- DataOutputStream

       |-- PrintStream

介绍

ByteArreaOutputStream

功能:在内存中创建一个buffer, 所有写入此流中的数据都被放入到此buffer 

构造:无参或者使用一个可选的初始化buffer的大小的参数构造

使用:将其和FilterOutputStream套接得到额外的功能, 建议首先和BufferedOutputStream套接实现缓冲功能. 通过toByteArray方法可以得到流中的数据. 不通过装饰器的用法

FileOutputStream

功能:将信息写入文件中 

构造:使用代表文件路径的StringFile对象或者 FileDescriptor对象创建. 还可以加一个代表写入的方式是否为append的标记

使用:一般将其和FilterOutputStream套接得到额外的功能

PipedOutputStream

功能:任何写入此对象的信息都被放入对应PipedInputStream 对象的缓存中, 从而完成线程的通信, 实现了"管道"的概念.具体在后面详细讲解 

构造:利用PipedInputStream构造, 在多线程程序中数据的目的地的

使用:一般将其和FilterOutputStream套接得到额外的功能

ObjectOutputStream

功能:对象输出流

构造:

使用:

装饰输出字节流FilterOutputStream:实现装饰器功能的抽象类, 为其它OutputStream对象增加额外的功能

DataOutputStream

功能:通常和DataInputStream配合使用, 使用它可以写入基本数据类新

构造:使用OutputStream构造

使用:包含大量的写入基本数据类型的方法

PrintStream

功能:产生具有格式的输出信息

构造:使用OutputStream和一个可选的表示缓存是否在每次换行时是否flush的标记构造. 还提供很多和文件相关的构造方法

使用:一般是一个终极("final")的包装器, 很多时候我们都使用它

BufferedOutputStream

功能:使用它可以避免频繁地向IO写入数据, 数据一般都写入一个缓存区, 在调用flush方法后会清空缓存、一次完成数据的写入  

构造:从一个OutputStream或者和一个代表缓存区大小的可选参数构造

使用:提供和其它OutputStream一致的接口, 只是内部提供一个缓存的功能

 

2.2.3 字节流输入与输出的对应

InputStream                                         OutputStream

    |-- ByteArrayInputStream       ByteArrayOutputStream --|

    |-- FileInputStream                  FileOutputStream --|

    |-- ObjectInputStream ---------- ObjectOutputStream --|

    |-- PipedInputStream ------------ PipedOutputStream --|

    |-- FilterInputStream              FilterOutputStream --|

       |-- BufferInputStream          BufferOutputStream --|

       |-- DataInputStream ----------- DataOutputStream --|

       |-- LineNumberInputStream             PrintStream --|

       |-- PushbackInputStream

    |-- SequenceInputStream

    |-- StringBufferInputStream

    蓝色:主要的对应部分。红色:不对应部分(不入流)。虚线:代表需要搭配使用

让我们看看字节流中不太对称(红色的)的几个类把

1).LineNumberInputStream

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

2).PushbackInputStream

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

3).StringBufferInputStream

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

4).SequenceInputStream

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

5).PrintStream

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

蓝色的部分是IO字节流的主要组成部分

    存在极强的对称关系. 关于搭配使用的三对类补充一下:

    ObjectInputStream/ObjectOutputStream:要求写/读对象的次序要保持一致, 否则轻则不能得到正确的数据, 重则抛出异常(一般会如此)

    DataInputStream/DataOutputStream主要是要求写/读数据的次序要保持一致, 否则轻则不能得到正确的数据, 重则抛出异常(一般会如此)

    PipedInputStream/PipedOutputStream在创建时一般就一起创建, 调用它们的读写方法时会检查对方是否存在, 或者关闭!需要双方管道都存在才能交互

 

2.2.4 ByteArray

(basic.io.bytestream.byteArray.ByteArrayStreamDemo)

 

2.2.4.1 ByteArrayInputStream

构造函数:使用buf作为其缓冲区数组

ByteArrayInputStream(byte buf[]) 

ByteArrayInputStream(byte buf[], int offset, int length) # pos=offset, count = Math.min(offset + length, buf.length)

内部维护属性:

    1).protected byte buf[]:流的创建者提供的byte数组

    2).protected int pos:要从输入流中读取的下一个字符的索引

    3).protected int mark:流中手动标记的位置,用于reset(),默认为0

    4).protected int countbuf最后一个有效字符的索引+1的索引值

方法:

    1).synchronized int read():读取下一个数据字节

    2).synchronized int read(byte b[], int off, int len):将最多len个数据字节从此输入流读入byte 数组

    3).synchronized long skip(long n):跳过n个输入字节

    4).synchronized int available():返回剩余字节数

    5).boolean markSupported():测试此 InputStream 是否支持 mark/reset

    6).void mark(int readAheadLimit):设置流中的当前标记位置,即mark

    7).synchronized void reset():将缓冲区的位置重置为标记位置,回滚到mark

    8).void close():关闭流无效, 关闭后仍可被调用

    字节数组流, 用于操作字节数组, 它的内部缓冲区就是一个字节数组

 

2.2.4.2 ByteArrayOutputStream

构造函数:创建一个新的byte输出流(默认size=32)

    ByteArrayOutputStream()

    ByteArrayOutputStream(int size)

内部维护属性:

    protected byte buf[] 存储数据的缓冲区

    protected int count:缓冲区中的有效字节数

扩容机制:当write超过设定的size。默认进行2倍扩容,最大数量 <= Integer.MAX_VALUE

方法:

    synchronized void write(int b):将指定的字节写入此byte数组输出流

    synchronized void write(byte b[], int off, int len):将指定byte数组中从偏移量off开始的len个字节写入此byte数组输出流

    synchronized void writeTo(OutputStream out):将此byte数组输出流的全部内容写入到指定的输出流参数中, 这与使用out.write(buf, 0, count)调用该输出流的write方法效果一样

    synchronized void reset():将此byte数组输出流的count字段重置为零, 从而丢弃输出流中目前已累积的所有输出

    synchronized byte toByteArray()[]:创建一个新分配的byte数组

    synchronized int size():返回缓冲区的当前大小

    synchronized String toString():使用平台默认的字符集, 通过解码字节将缓冲区内容转换为字符串

    synchronized String toString(String charsetName):使用指定的charsetName, 通过解码字节将缓冲区内容转换为字符串

    void close() 关闭流无效, 关闭后仍可被调用

    synchronized String toString(int hibyte):已过时, 此方法无法将字节正确转换为字符

 

2.2.5 文件I/O(FileInputStreamFileOutputStream)

(basic.io.bytestream.file.FileStreamDemo)

 

2.2.5.1 FileInputStream

构造函数:文件名/文件对象/文件描述对象,最终都是new一个FileDescriptor,然后调用open()

    public FileInputStream(String name) throws FileNotFoundException

    public FileInputStream(File file) throws FileNotFoundException

    public FileInputStream(FileDescriptor fdObj)

内部维护属性:

    private final FileDescriptor fd;

    private final String path;

    private FileChannel  channel = null;

    private final Object closeLock = new Object();

    private volatile boolean closed = false;

方法:

    public int read() throws IOException

    public int read(byte b[]) throws IOException

    public int read(byte b[], int off, int len) throws IOException

    public long skip(long n) throws IOException

    public int available() throws IOException

    public void close() throws IOException

    public final FileDescriptor getFD() throws IOException

    public FileChannel getChannel()

    protected void finalize() throws IOException

本地方法:

    private native void open0(String name) throws FileNotFoundException

    private void open(String name) throws FileNotFoundException

    private native int read0() throws IOException

    private native int readBytes(byte b[], int off, int len) throws IOException

    private native long skip0(long n) throws IOException;

    private native int available0() throws IOException

    private static native void initIDs():设置类中(也就是FileInputStream)的属性的内存地址偏移量,便于在必要时操作内存给它赋值

    private native void close0() throws IOException

 

2.2.5.2 FileOutputStream

构造函数:

    public FileOutputStream(String name) throws FileNotFoundException

    public FileOutputStream(String name, boolean append) throws FileNotFoundException

    public FileOutputStream(File file) throws FileNotFoundException

    public FileOutputStream(File file, boolean append) throws FileNotFoundException

    public FileOutputStream(FileDescriptor fdObj)

内部维护属性:

    private final FileDescriptor fd;

    private final boolean append;

    private FileChannel channel;

    private final String path;

    private final Object closeLock = new Object();

    private volatile boolean closed = false;

方法:

    public void write(int b) throws IOException

    public void write(byte b[]) throws IOException

    public void write(byte b[], int off, int len) throws IOException

    public void close() throws IOException

    public final FileDescriptor getFD()  throws IOException

    public FileChannel getChannel()

    protected void finalize() throws IOException

    private static native void initIDs();

    private void open(String name, boolean append) throws FileNotFoundException

本地方法:

    private native void write(int b, boolean append) throws IOException;

    private native void open0(String name, boolean append) throws FileNotFoundException;

    private native void writeBytes(byte b[], int off, int len, boolean append) throws IOException;

    private native void close0() throws IOException;

 

2.2.6 操作对象

(basic.io.bytestream.objects.ObjectStreamTest)

    ObjectInputStreamObjectOutputStream,被操作的对象需要实现Serializable(标记接口)

职责:从序列化文档中读取并解析出一个类。这个序列化文档是由 ObjectOutputStream 写入的。这一功能允许我们持久化一个内存对象,这便是序列化。Java IO 库规定了序列化的存储规范,从上层到下层是,对象,块存储,块内元素(块数据类型,块长度,数据)

实例:

/** Serializable:用于给被序列化的类加入ID, 用于判断类和对象是否是同一个版本. */

class Box implements Serializable {

    private int width;  

    private int height;

    private String name;  

    public Box(String name, int width, int height) {

        this.name = name;

        this.width = width;

        this.height = height;

    }

    @Override

    public String toString() {

        return "["+name+": ("+width+", "+height+") ]";

    }

}

对象序列化

    private static void testWrite() {  

        try {

            ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(TMP_FILE));

            out.writeBoolean(true);

            out.writeByte((byte)65);

            out.writeChar('a');

            out.writeInt(20131015);

            out.writeFloat(3.14F);

            out.writeDouble(1.414D);

            // 写入HashMap对象

            HashMap map = new HashMap();

            map.put("one", "red");

            map.put("two", "green");

            map.put("three", "blue");

            out.writeObject(map);

            // 写入自定义的Box对象,Box实现了Serializable接口(对象序列化, 被序列化的对象必须实现Serializable接口)

            Box box = new Box("desk", 80, 48);

            out.writeObject(box);

 

            out.close();

        } catch (Exception ex) {

            ex.printStackTrace();

        }

    }

对象反序列化

    private static void testRead() {

        try {

            ObjectInputStream in = new ObjectInputStream(new FileInputStream(TMP_FILE));

            System.out.printf("boolean:%b\n" , in.readBoolean());

            System.out.printf("byte:%d\n" , (in.readByte()&0xff));

            System.out.printf("char:%c\n" , in.readChar());

            System.out.printf("int:%d\n" , in.readInt());

            System.out.printf("float:%f\n" , in.readFloat());

            System.out.printf("double:%f\n" , in.readDouble());

            // 读取HashMap对象

            HashMap map = (HashMap) in.readObject();

            Iterator iter = map.entrySet().iterator();

            while (iter.hasNext()) {

                Map.Entry entry = (Map.Entry)iter.next();

                System.out.printf("%-6s -- %s\n" , entry.getKey(), entry.getValue());

            }

            // 读取Box对象,Box实现了Serializable接口

            Box box = (Box) in.readObject();

            System.out.println("box: " + box);

 

            in.close();

        } catch (Exception e) {

            e.printStackTrace();

        }

    }

 

2.2.7 管道流

(basic.io.bytestream.pipe.PipedStreamDemo)

    PipedInputStreamPipedOutputStream, 输入输出可以直接进行连接, 通过结合线程使用

 

2.2.8 装饰类流(FilterInputStream/FilterOutputStream)

2.2.8.1 缓存输入/输出流

(basic.io.bytestream.filter.buffered.BufferedStreamDemo)

    BufferedInputStreamBufferedOutputStream类。缓存可以说是I/O的一种性能优化, 缓存流为I/O流增加了内存缓存区, 有了缓存区, 使得在流上执行skip(), mark()reset()方法都称为了可能。

1).BufferedInputStream:可以对任意的InputStream类进行带缓存区的包装以达到性能的优化.

构造函数:传入InputStream,构建带有size(默认32)个字节的缓存流,最优的缓存区大小,取决于操作系统,可用内存空间及及其配置

    BufferedInputStream(InputStream in)

    BufferedInputStream(InputStream in, int size)

    BufferedInputStream读取文件过程:文件 -> InputStream -> BufferedInputStream --> 目的地

2).BufferedOutputStream:输出信息和向OutputStream输入信息完全一样, 只不过BufferedOutputStream有一个flush()方法用来将缓存区的数据强制输出完

构造函数:传入OutputStream,构建带有size(默认32)个字节的缓存流,最优的缓存区大小,取决于操作系统,可用内存空间及及其配置

    BufferedOutputStream(OutputStream out)

    BufferedOutputStream(OutputStream out, int size)

说明:flush()方法就是用于即使缓存区没有满的情况下, 也将缓存区的内容强制写入到外设, 习惯上称这个过程为刷新, flush方法只对使用缓存区的OutputStream类的子类有效, 当调用close()方法时, 系统在关闭流之前, 也会将缓存区中信息刷新到磁盘文件中

 

2.2.8.2 数据流

(basic.io.bytestream.filter.data.DataStreamDemo)

    数据输入流/输出流(DataInputStream/DataOutputStream), 允许应用程序以及其无关的方式从底层输入流中读取基本Java数据类型, 也就是说, 当读取一个数据时, 不必关心这个数值应当是什么字节

1.DataInputStream类与DataOutputStream类的构造方法如下:

    1).DataInputStream(InputStream in): 使用指定的基础InpuStream创建一个DataInputStream

    2).DataOutputStream(OutputStream out):创建一个新的数据输出流, 将数据写入指定基础输出流

2.DataOutputStream类提供如下3种写入字符串的方法

    1).writeBytes(String s)

    2).writeChars(String s)

    3).writeUTF(String s)

    由于Java中的字符是Unicode编码, 是双字节的, writeBytes只是将字符串中的每一个字符的低字节内容写入目标设备中. writeChars将字符串中的每一个字符的两个字节的内容都写到目标设备中, writeUTF将字符串按照UTF编码后的字节长度写入目标设备, 然后才是每一个字节的UTF编码

    DataInputStream类只提供了一个readUTF()方法返回字符串. 这是因为要在一个连续的字节流读取一个字符串, 如果没有特殊的标记作为一个字符串的结尾, 并且事先也不知道这个字符串的长度, 也就无法知道读取到什么位置才是这个字符串的结束

    DataOutputStream类中只有writeUTF()方法向目标设备写入字符串的长度, 所以只能准确地读回写入字符串

 

2.2.9 序列流SequenceInputStream

    对多个流进行合并:1).文件切割  2).文件合并 3).文件切割合并+配置文件

 

2.2.10 ZIP压缩输入/输出流

    使用ZIP压缩管理文件(ZIP archive), 是一种典型的文件压缩格式, 可以节省存储空间. 关于ZIP压缩的I/O实现, Java的内置类中, 提供了非常好用的相关类, 所以实际的实现方式非常容易

    Java实现了I/O数据流与网络数据流的单一接口, 因此数据的压缩、网络传输和解压缩的实现比较容易.

    ZipEntry类产生的对象, 是用来代表一个ZIP压缩文件内的进入点(entry), ZipInputStream类用来读取ZIP压缩格式的文件, 所支持的包括已压缩及未压缩的进入点(entry), ZipOutputStream类用来写出ZIP压缩格式的文件, 而且所支持的包括已压缩及未压缩的进入点(entry)

 

2.2.10.1 压缩文件

    利用ZipInputStream类对象,可将文件压缩为.zip文件。

ZipOutputStream类常用的方法

方法

说明

ZipOutputStream(OutputStream out)

构造函数

putNextEntry(ZipEntry e)

开始写一个新的ZipEntry, 并且将流内的位置移至此entry所指数据的开头

write(byte[] b, int off, int len)

将字节数组写入当前ZIP条目数据

finish()

完成写入ZIP输出流的内容, 无序关闭它所配合的OutputStream

setComment(String comment)

可设置此ZIP文件的注释文字

 

2.2.10.2 解压缩ZIP文件

    ZipInputStream类可读取ZIP压缩格式的文件,包括对已压缩和未压缩条目的支持(entry)

ZipInputStream类常用的方法

方法

说明

ZipInputStream(InputStream in)

构造函数

read(byte[] b, int off, int len)

读取目标b数组off偏移量的长度, 长度是len字节

available()

判断是否已读完目前entry所指定的数据, 已读完返回0, 否则返回1

closeEntry()

关闭目前ZIP条目并定位流以读取下一个条目

skip(long n)

跳过当前ZIP条目中指定的字节数

getNextEntry()

读取下一个ZipEntry, 并将流内的位置移至该entry所指数据的开头

createZipEntry(String name)

以指定的name参数新建一个ZipEntry对象

 

 

 

2.3 基于字符的IO操作接口

2.3.1 Reader - 输入字符流

    Java中的字符是Unicode编码(双字节). InputStream是用来处理字节的, 在处理字符文本时不是很方便.

    Java为字符文本的输入提供Reader,但Reader类并不是InputStream类的替换者。直是在处理字符串时简化了编程. Reader类是字符输入流的抽象类, 所有字符输入流的实现都是它的子类

Reader类常用的方法

(标记阻塞: 在某个字符可用、发生 I/O 错误或者已到达流的末尾前,方法一直阻塞)

方法

说明

public int read(java.nio.CharBuffer target) throws IOException

将字符读入指定的字符缓冲区。缓冲区可照原样用作字符的存储库:所做的唯一改变是 put 操作的结果。不对缓冲区执行翻转或重绕操作

public int read() throws IOException

阻塞,读取单个字符, 范围在 0 65535 之间 (0x00-0xffff), 如果位于缓冲区末端, 则返回-1

public int read(char cbuf[]) throws IOException

阻塞,将字符读入数组

abstract public int read(char cbuf[], int off, int len) throws IOException

阻塞,将字符读入数组的某一部分 [cbuf:目标缓冲区, off:开始存储字符处的偏移量, len:读取的最多字符数]

public long skip(long n) throws IOException

阻塞,跳过字符

public boolean ready() throws IOException

判断是否准备读取此流

public boolean markSupported()

判断此流是否支持mark()操作, 模式实现始终返回false, 子类应该重写此方法

public void mark(int readAheadLimit) throws IOException

标记流中的当前位置。对reset()的后续调用将尝试将该流重新定位到此点。如果该流支持mark()操作。

public void reset() throws IOException

配合mark()reset回到mark标记的位置。如果该流支持reset()操作

abstract public void close() throws IOException

关闭该流并释放与之关联的所有资源。在关闭该流后,再调用 read()ready()mark()reset() skip() 将抛出 IOException。关闭以前关闭的流无效

Reader:所有的输入字符流的抽象父类

    |-- BufferedReader:很明显就是一个装饰器, 它和其子类负责装饰其它Reader对象

       |-- LineNumberReader

    |-- CharArrayReader:基本的介质流,将Char数组中读取数据

    |-- InputStreamReader:一个连接字节流和字符流的桥梁, 它将字节流转变为字符流

       |-- FileReader:源码中明显使用了将FileInputStream转变为Reader的方法

    |-- PipedReader:是从与其它线程共用的管道中读取数据

    |-- StringReader:基本的介质流,将String中读取数据

    |-- FilterReader:所有自定义具体装饰流的父类

       |-- PushbackReader:对Reader对象进行装饰, 会增加一个行号

    Reader中各个类的用途和使用方法基本和InputStream中的类使用一致

 

2.3.2 Writer - 输出字符流

    Writer类是字符输出流的抽象类, 所有字符输出类的实现都是它的子类

Writer类常用的方法

(标记阻塞: 在某个字符可用、发生 I/O 错误或者已到达流的末尾前,方法一直阻塞)

方法

说明

public void write(int c) throws IOException

写入单个字符. 要写入的字符包含在给定整数值的16个低位中, 16 高位被忽略, 用于支持高效单字符输出的子类应重写此方法

public void write(char cbuf[]) throws IOException

写入字符数组

abstract public void write(char cbuf[], int off, int len) throws IOException

写入字符数组的某一部分

[cbuf: 目标数组, off:偏移量, len:写入长度]

public void write(String str) throws IOException

写入字符串

public void write(String str, int off, int len) throws IOException

写入字符串的某一部分

public Writer append(CharSequence csq) throws IOException

将指定字符序列添加到此 writer

public Writer append(CharSequence csq, int start, int end) throws IOException

将指定字符序列添加到此 writer

public Writer append(char c) throws IOException

将指定字符添加到此 writer

abstract public void flush() throws IOException

彻底完成输出并清空缓存区

abstract public void close() throws IOException

关闭此流,但要先刷新它

Writer:所有的输出字符流的抽象父类

    |-- BufferedWriter:是一个装饰器为Writer提供缓冲功能

    |-- CharArrayWriter:基本的介质流,将Char数组中写入数据

    |-- OutputStreamWriterOutputStreamWriter转换的桥梁, 它将字节流转变为字符流

       |-- FileWriter:源码中明显使用了将FileOutStream转变为Writer的方法

    |-- PipedWriter:是从与其它线程共用的管道中写入数据

    |-- PrintWriter:和PrintStream极其类似,功能和使用也非常相似

    |-- StringWriter:基本的介质流,将String中写入数据

    |-- FilterWriter:所有自定义具体装饰流的父类

    Writer中各个类的用途和使用方法基本和InputStream中的类使用一致.后面会有WriterInputStream的对应关系

 

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

Reader                                           Writer

    |-- BufferedReader          BufferedWriter --|

        |-- LineNumberReader                       |

    |-- CharArrayReader         CharArrayWriter--|

    |-- InputStreamReader    OutputStreamWriter--|

       |-- FileReader           FileWriter --|

    |-- PipedReader -------------- PipedWriter --|

    |-- StringReader               StringWriter --|

    |-- FilterReader               FilterWriter --|

       |-- PushbackReader.                        |

                                      PrintWriter --|

 

2.3.4 FileReaderFileWriter

    FileInputStreamFileOutputStream只体用了对字节或字节数组的读写方法, 由于汉子在文件中占用两个字节, 如果使用字节流, 读取不好可能会出现乱码现象.

    对于读取字符流, 采用字符流FileReaderFileWriter避免乱码的情况

       FileReader对应FileInputStreamFileWriter对应FileOutputStream

 

2.3.5 打印流

    PrintWriter(纯文本)PrintStream(二进制文件, 如图片、音乐、视频等)可以直接操作输入流和文件

       1).提供了打印方法可以对多重数据类型值进行打印. 并保持数据的表示形式

       2).它不抛IOException

    构造函数  1).字符串路径    2).File对象    3).字节输出流    4).字符输出流

 

2.3.6 BufferedReaderBufferedWriter

(basic.io.charstream.buffered.BufferedDemo)

    BufferedReader类与BufferedWriter类分别继承Reader类与Writer, 这两个类具有内部缓存机制, 并可以为单位进行输入/输出,BudderedReader类读取文件的过程:

    字符数据 -> BufferedWriter -> OutputStreamWriter -> OutputStream -> 文件

BufferedReader类常用方法:

    1).read(): 读取单个字符

    2).readLine(): 读取一个文本行, 并将返回为字符串; 若无数据可读, 则返回null

    3).write(String s, int off, int len): 写入字符串的某一部分

    4).flush(): 刷新该流的缓存

    5).newLine(): 写入一个行分隔符

    在使用BufferedWriter类的Write()方法时, 数据并没有立刻被写入至输出流中, 而是首先先进入缓存区中, 如果想立刻将缓存区中的数据写入输出流中, 要调用flush()

自定义BufferReader

(basic.io.charstream.buffered.CustomBufferedReader)

    自定义的读取缓冲区. 其实就是模拟一个BufferedReader

    分析: 缓冲区无非就是封装了一个数组, 并对外提供了更多的方法对数组进行访问.

    缓冲的原理: 其实就是从源中获取一批数据装进缓冲区中, 再从缓冲区中不断的取出一个一个数据, 在此次取完后, 再从源中继续取一批数据进缓冲区, 当源中的数据取光时, -1作为结束标记

 

 

2.4 字节流和字符流

2.4.1 输入的对应

InputStream                                             Reader

    |-- ByteArrayInputStream           CharArrayReader --|

    |-- PipedInputStream                    PipedReader --|

    |-- ObjectInputStream             InputStreamReader--|

    |-- FileInputStream                  FileReader --|

    |-- FilterInputStream                  FilterReader --|

       |-- PushbackInputStream      PushbackReader --|

       |-- BufferedInputStream         BufferedReader --|

       |-- LineNumberInputStream  LineNumberReader --|

       |-- DataInputStream                StringReader --|

    |-- SequenceInputStream

    |-- StringBufferInputStream

   蓝色的表示对应的部分, 红色的表示不对应的部分. 至于为什么不对应还是你自己多看看源代码、多考虑考虑吧! 还要强调一点就是即使对应, 它们的继承关系也是不太对应的

 

2.4.2 输出的对应

OutputStream                                         Writer

    |-- ByteArrayOutputStream      CharArrayWriter --|

    |-- ObjectOutputStream      OutputStreamWriter --|

    |-- FileOutputStream              FileWrite --|

    |-- PipedOutputStream               PipedWriter --|

    |-- FilterOutputStream             FilterWriter --|

       |-- BufferedOutputStream     BufferedWriter --|

       |-- PrintStream                  PrintWriter --|

       |-- DataOutputStream           StringWriter --|

 

2.4.3 各个类所负责的媒介

Java IO中各个类所负责的媒介

 

Byte Based

Character Based

 

Input

Output

Input

Output

Basic

InputStream

OutputStream

Reader

InputStreamReader

Writer

OutputStramWriter

Arrays

ByteArrayInputStream

ByteArrayOutputStream

CharArrayReader

CharArrayWriter

Files

FileInputStream

RandomAccessFile

FileOutputStream

RandomAccessFile

FileReader

FileWriter

Pipes

PipedInputStream

PipedOutputStream

PipedReader

PipedWriter

Buffering

BufferedInputStream

BufferOutputStream

BufferedReader

BufferedWriter

Fitering

FilterInputStream

FilterOutputStream

FilterReader

FilterWriter

Parsing

PushbackInputStream

StreamTokenizer

 

PushbackReader

LineNumberReader

 

Strings

 

 

StringReader

StringWriter

Data

DataInputStream

DataOutputStream

 

 

Data-Formatted

 

PrintStream

 

PrintWriter

Objects

ObjectInputStream

ObjectOutputStream

 

 

Utilities

SequenceInputStream

 

 

 

 

 

2.4.4 转换流(字节流->字符流)(TransStreamDemo)

    JavaI/O中存在输入、输出的对应和字节流和字符流的对应,它们之间的转化桥梁

       1).InputStreamReader:字节到字符的桥梁、解码

       2).OutputStreamWriter:字符到字节的桥梁、编码

public class ConvertDemo {

    public static void main(String[] args) throws IOException  {

        ConvertDemo demo = new ConvertDemo();

        demo.byte2char();

    }

   

    /** 字节流 -> 字符流  */

    public void byte2char() throws IOException  {

        // 源头,可以是System.in,可以是文件,可以是固定byte

//      InputStream inputStream = System.in;

        InputStream inputStream = new ByteArrayInputStream("Hello World".getBytes());

        // 将字节转成字符的桥梁, 装换流

        InputStreamReader reader = new InputStreamReader(inputStream);

//        InputStreamReader reader = new InputStreamReader(inputStream, "UTF-8");

        // int ch = isr.read();

        // System.out.println((char)ch);

 

        BufferedReader buffer = new BufferedReader(reader);

        // 目标,可以是System.out,可以是文件,可以是其他

        OutputStream out = System.out;

        OutputStreamWriter writer = new OutputStreamWriter(out);

        BufferedWriter bufferedWriter = new BufferedWriter(writer);

 

        String line = null;

        while ((line = buffer.readLine()) != null) {

            if ("over".equals(line))

                break;

            // System.out.println(line.toUpperCase());

            // osw.write(line.toUpperCase()+"\r\n");

            // osw.flush();

            bufferedWriter.write(line.toUpperCase());

            bufferedWriter.newLine();

            bufferedWriter.flush();

        }

    }

}

 

2.4.5 流的操作规律

流的操作规律: 明确4

1.明确源和目的()

    : InputStream(字节输入流)  Reader(字符输入流)

    : OutPutStream(字节输出流)  Writer(字符输出流)

2.明确数据是否纯文本数据(这步可以明确需求中具体要使用哪个体系)

    : 是纯文本:Reader    :InputStream

    : 是纯文本:Writer    :OutputStream

3.明确具体的设备

    硬盘:File     键盘:System.in    内存:数组      网络:Socket

4.是否需要其他额外功能

    1.是否需要高效(缓冲区),需要就加上Buffer

    2.转换流, 什么时候使用转换流呢?

       1).源或目的对应的设备是字节流, 但操作的却是文本数据, 使用转换作为桥梁. 提高对文本操作的便捷.

        2).一旦操作文本涉及到具体的指定编码表时, 必须使用转换流 .

 

 

2.5 File

(basic.io.FileDemo)

    File类是io包中唯一代表磁盘文件本身的对象. File类定义了一些与平台无关的方法, 可以通过调用File类种的方法, 实现创建, 删除, 重命名文件等.

    数据流可以将数据写入到文件中, 而文件也是数据流最常用的数据媒体

[: 对于Microsoft Windows平台, 包含盘符的路径名前缀由驱动器号和一个":"组成, 如果路径名是绝对路径名, 还可能后跟"\\"]

构造函数:文件名/文件对象/文件描述对象,最终都是new一个FileDescriptor,然后调用open()

    public File(String pathname)

    public File(String parent, String child)

    public File(File parent, String child)

    public File(URI uri)

    private File(String pathname, int prefixLength)

    private File(String child, File parent)

内部维护属性:

    private static final FileSystem fs = DefaultFileSystem.getFileSystem():对应JDK获取文件系统

    public static final char separatorChar = fs.getSeparator():路径分隔符(Unix:'/',Windows:'\\')

    public static final String separator = "" + separatorChar

    public static final char pathSeparatorChar = fs.getPathSeparator():path分割符(Unix:':', Windows:';')

    public static final String pathSeparator = "" + pathSeparatorChar

    private final String path

    private static enum PathStatus { INVALID, CHECKED }

    private transient PathStatus status = null

    private final transient int prefixLength

    int getPrefixLength():绝对路径的前缀长度

路径方法:

    String getParent() 获取上一级的目录相对路径

    String getPath()   获取文件的目录相对路径

    String getAbsolutePath()   获取文件的绝对路径

    String getCanonicalPath()  返回此抽象路径名的规范路径名字符串

判断方法:

    boolean isAbsolute()   是否为绝对路径名

    boolean canRead()  是否可读

    boolean canWrite() 是否可写

    boolean exists()   是否存在

    boolean isDirectory()  是否是目录

    boolean isFile()   是否是文件

    boolean isHidden() 是否是隐藏文件

    boolean canExecute()   是否可执行

    boolean equals(Object obj) 与文件对象比较是否相同

获取方法:

    long length()  文件的长度

    String getName()   文件或目录名称

    long lastModified() 最后修改时间

    long getTotalSpace()   指定的分区大小

    long getFreeSpace() 指定的分区中未分配的字节数

    long getUsableSpace()  指定的分区上可用于此虚拟机的字节数

    int compareTo(File pathname)  按字母顺序比较两个抽象路径名

    int hashCode() 哈希码

    String toString()  路径名字符串

    File getParentFile()   父目录的抽象路径名;如果此路径名没有指定父目录,则返回 null

    File getAbsoluteFile() 抽象路径名的绝对路径名形式

    File getCanonicalFile()   抽象路径名的规范形式

    URL toURL() 过时,

    URI toURI() 构造一个表示此抽象路径名的 file: URI

    String[] list() 此抽象路径名表示的目录中的文件和目录

    String[] list(FilenameFilter filter)  此抽象路径名表示的目录中满足指定过滤器的文件和目录

    File[] listFiles() 同上,返回File对象列表

    File[] listFiles(FilenameFilter filter)   同上

    File[] listFiles(FileFilter filter)   同上

    static File[] listRoots()  列出可用的文件系统根

    Path toPath() 

操作方法:

    boolean mkdir() 指定的目录

    boolean mkdirs()   指定的目录,包括所有必需但不存在的父目录

    boolean createNewFile()   创建文件

    boolean renameTo(File dest)   重命名

    boolean delete()   删除文件

    boolean setLastModified(long time) 设置最后修改时间

    boolean setReadOnly()  设置只读

    boolean setWritable(boolean writable, boolean ownerOnly) 设置写状态

    boolean setWritable(boolean writable) 设置写状态

    boolean setReadable(boolean readable, boolean ownerOnly) 设置读状态

    boolean setReadable(boolean readable) 设置读状态

    boolean setExecutable(boolean executable, boolean ownerOnly) 设置执行状态

    boolean setExecutable(boolean executable) 设置执行状态

    void deleteOnExit() 在虚拟机终止时,请求删除此抽象路径名表示的文件或目录

    synchronized void writeObject(java.io.ObjectOutputStream s) 

    synchronized void readObject(java.io.ObjectInputStream s)

静态方法:

    static File createTempFile(String prefix, String suffix, File directory) 在默认临时文件目录中创建一个空文件,使用给定前缀和后缀生成其名称

    static File createTempFile(String prefix, String suffix) 在指定目录中创建一个新的空文件,使用给定的前缀和后缀字符串生成其名称

 

2.6 磁盘IO工作机制

    读取和写入文件,IO操作都调用操作系统提供的接口。而只要是系统调用就涉及到内核空间地址和用户空间地址切换的问题,这是操作系统为了保护系统本身的运行安全而将内核程序运行使用内存空间和用户程序运行的内存空间隔离造成的。保证了内核程序运行的安全性,但也就存在,从内核空间向用户空间复制的问题。

    这里就会涉及到几个概念:

       1).物理磁盘:如硬盘

       2).内核地址空间:内核使用的地址空间

       3).内核地址空间的高速页缓存:为了减小IO响应时间,对磁盘读取的文件进行缓存,当读取同一个地址的空间数据,直接从缓存读取

       4).用户地址空间:用户线程(程序)操作的空间,与内核空间隔离

       5).用户地址空间的应用缓存:

应用程序访问文件的几种访问文件方式:

1).标准访问文件方式

    read():物理磁盘 -> 内存地址空间(高速页缓存) -> 用户地址空间(应用缓存) -> read()

    write()write() -> 内存地址空间(高速页缓存) -> 用户地址空间(应用缓存) -> 物理磁盘

2).直接IO的方式

    read():物理磁盘 -> 用户地址空间(应用缓存) -> read()

    write()write() -> 用户地址空间(应用缓存) -> 物理磁盘

3).同步访问文件方式:就是数据读取和写入都是同步操作的

4).异步访问文件方式

    当访问数据的线程发出请求后,线程会接着去处理其他事情,而不是阻塞等待,当请求的数据返回后继续处理下面的操作。提高应用程序的效率,而不会改变访问文件的效率

5).内存映射方式

    read():物理磁盘 -> 内存地址空间(高速页缓存) -- 地址映射 --> 用户地址空间(应用缓存) -> read()

    write()write() -> 内存地址空间(高速页缓存) -- 地址映射 --> 用户地址空间(应用缓存) -> 物理磁盘

    操作系统将内存中的某一块区域与磁盘中的文件关联起来,当要访问内存中一段数据时,转换为访问文件的某一段数据。减少数据从内核空间缓存到用户空间缓存的数据复制操作,因为这两个空间的数据时共享的。

 

2.6.1 Java访问磁盘文件

    数据在磁盘中的唯一最小描述就是文件,简单的说,上层应用程序只能通过文件来操作磁盘上的数据。文件也是操作系统和磁盘驱动器交互的最小单元。

    Java中,File类并不代表一个真实存在的文件对象,他只是代表这个路径的一个虚拟对象。当真正读取这个文件时,就会检查这个文件是否存在。

    当读取文件时,会根据File来创建真正读取文件的操作对象FileDescriptor。通过这个对象可以直接控制这个磁盘文件。[如:getFD()来获取真正操作的底层操作系统关联的文件描述。且sync()方法将操作系统缓存中的数据强制刷新到物理磁盘中]

    而由于需要读取的事字符格式,所以需要StreamDecoder类将byte解码为char格式。至于如何从磁盘驱动器上读取一段数据,操作系统会帮我们完成

 

 

2.7 IO包中的其他类

2.7.1 RandomAccessFile(不是IO体系中的子类, Object的子类)

特点:

    1).该对象既能读, 又能写

    2).该对象内部维护一个byte数组, 并通过指针可以操作数组中的元素

    3).可以通过getFilePointer方法获取指针的位置, 和通过seek方法设置指针的位置

    4).其实该对象就是将字节输入流和输出流进行了封装

    5).该对象的源或汇只能是文件. 通过构造函数就可以看出

(basic.demo18.RandomAccessFileDemo)

import java.io.IOException;

import java.io.RandomAccessFile;

 

/**

 * RandomAccessFile(不是IO体系中的子类, Object的子类)

 * 特点:

 * 1).该对象既能读, 又能写

 *  2).该对象内部维护一个byte数组, 并通过指针可以操作数组中的元素

 *  3).可以通过getFilePointer方法获取指针的位置, 和通过seek方法设置指针的位置

 *  4).其实该对象就是将字节输入流和输出流进行了封装

 *  5).该对象的源或汇只能是文件. 通过构造函数就可以看出

 */

public class RandomAccessFileDemo {

    public static void main(String[] args) throws IOException {

//     writeFile();

//     readFile();

       randomWrite();

    }

   

    public static void randomWrite() throws IOException {

       RandomAccessFile raf = new RandomAccessFile("ranacc.txt", "rw");

       // 往指定位置写入数据。

       raf.seek(3 * 8);

       raf.write("哈哈".getBytes());

       raf.writeInt(108);

       raf.close();

    }

   

    public static void readFile() throws IOException {

       RandomAccessFile raf = new RandomAccessFile("ranacc.txt", "r");

       // 通过seek设置指针的位置。

       raf.seek(1 * 8);// 随机的读取。只要指定指针的位置即可。

       byte[] buf = new byte[4];

       raf.read(buf);

       String name = new String(buf);

       int age = raf.readInt();

       System.out.println("name=" + name);

       System.out.println("age=" + age);

       System.out.println("pos:" + raf.getFilePointer());

       raf.close();

    }

 

    // 使用RandomAccessFile对象写入一些人员信息,比如姓名和年龄。

    public static void writeFile() throws IOException {

       // 如果文件不存在,则创建,如果文件存在,不创建

       RandomAccessFile raf = new RandomAccessFile("ranacc.txt", "rw");

       raf.write("张三".getBytes());

       raf.writeInt(97);

       raf.write("小强".getBytes());

       raf.writeInt(99);

       raf.close();

    }

}

 

转载于:https://my.oschina.net/vwFisher/blog/2209787

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值