Java - IO

作者: 齐晋 | 来源:发表于2019-03-01 19:06 被阅读0次

对于程序语言的设计者来说,创建一个好的I/O系统是一项很艰难的任务。挑战不仅存在于IO多样的输入/输出端,还在于多种不同的通信方式,如顺序存取、缓冲、二进制、按字符/按行/按字存取等。Java类库的设计者创建了大量的类来解决这个困难。

Java I/O设计初衷是避免使用过多的类,但讽刺的是,Java I/O系统提供的类库依旧很多。

Java 的 I/O 操作类在包 java.io 下,大概有将近 80 个类,但是这些类大概可以分成四组,分别是:

  • 基于字节操作的 I/O 接口:InputStream 和 OutputStream
  • 基于字符操作的 I/O 接口:Writer 和 Reader
  • 基于磁盘操作的 I/O 接口:File @Deprecated
  • 基于网络操作的 I/O 接口:Socket @Deprecated

典型的数据源及媒介有:

  • 文件
  • 控制台
  • 网络
  • 设备

前三个位于package java.io下,最后一个位于package java.net下。

File

File类不负责具体文件内容的读写,但是负责文件及文件目录的操作,是基于磁盘操作的I/O。
File类名字有一定的误导性。我们可能会认为它指代的是文件,实际上并非如此。它既能指代一个具体的文件,也能代表一个目录。

常用File类操作:

//创建文件
public boolean createNewFile() throws IOException;

//列举目录列表
public String[] list();
//列举目录列表,FilenameFilter用来辅助过滤。如列举以`.java`结尾的文件,则可通过implements FilenameFilter来实现
public String[] list(FilenameFilter filter);

//获取文件/目录名
public String getName();

//获取文件父目录路径
public String getParent();

//删除文件/目录
public boolean delete()

更多其他操作可参考JDK文档。

基于字节和字符的I/O操作

是Java IO的核心。
分为输入流(Input)和输出流(Output)两部分。输入流从数据源读取数据,输出流将数据写入到特定的目标。

常见数据源

  • 字节数组
  • String对象
  • 文件
  • 管道
  • 其他种类的输出流
  • 网络

常见数据输出端

  • 字节数组
  • 文件
  • 管道
  • 其他种类的输入流
  • 网络

所有的输入流都有read()方法,所有的输出流都有write()方法。

不管是磁盘还是网络传输,最小的存储单元都是字节,因此支持基于字节的I/O就显然而已了。但是我们的程序中通常操作的数据都是以字符形式,为了操作方便当然要提供一个直接写字符的 I/O 接口,因此又产生了基于字符的I/O。

基于字节的IO

计算机识别数据的基本单位就是字节。
基于字节的 I/O 操作接口输入和输出分别是:InputStreamOutputStream

InputStream

InputStream是所有输入类的接口,部分方法如下:

public abstract class InputStream implements Closeable {
    public int available() throws IOException;
    public abstract int read() throws IOException;
    public int read(byte b[]) throws IOException;
    public int read(byte b[], int off, int len) throws IOException;
}

常见InputStream实现类

image.png

ByteArrayInputStream
将内存当做数据源,构造函数需要传入byte数组。

public class ByteArrayInputStream extends InputStream {
    public ByteArrayInputStream(byte buf[]);
}

FileInputStream
数据源是文件,构造函数如下:

public class FileInputStream extends InputStream{
    public FileInputStream(File file) throws FileNotFoundException;
    public FileInputStream(FileDescriptor fdObj)
    public FileInputStream(String name) throws FileNotFoundException;
}

PipedInputStream
数据源是PipedOutputStream,常用在多线程中

public class PipedInputStream extends InputStream {
    public PipedInputStream(PipedOutputStream src) throws IOException;
}

FilterInputStream
这里使用了装饰者模式,做为装饰器的接口。可以从构造函数看出,只支持传入InputStream类型的对象。

public class FilterInputStream extends InputStream {
    protected FilterInputStream(InputStream in);
}

不太了解装饰者模式的可以看下Design Pattern - 装饰者模式

为什么需要装饰者模式呢?JDK的设计者主要是为了给不同的I/O提供更多的灵活性。
举个例子,传统的I/O是无缓冲的,也就是说每次读或者写哪怕一个字节,都需要调用底层操作系统接口,这是个效率很低的操作。为传统I/O设计一个缓冲区,是非常必要的。但是读取文件的FileInputStream需要缓冲区,读取内存的ByteArrayInputStream需要缓冲区,其他的InputStream的实现类也需要缓冲区。怎么设计才能支持所有的InputStream都能支持缓冲区呢?JDK的设计者就想到了使用装饰者模式。使用BufferedInputStream来装饰任意InputStream对象,该输入流就能支持缓冲区了!
如:

byte[] buff = new byte[1024];
File file = new File(filePath);
BufferedInputStream in = new BufferedInputStream(new FileInputStream(file));
in.read(buff);  //支持了缓冲的read操作

OutputStream

OutputStream是所有输出类的接口,部分方法如下:

public abstract class OutputStream implements Closeable, Flushable{
    public abstract 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 flush() throws IOException;
}

常见OutputStream实现类

image.png

OutputStream的实现类大部分是跟InputStream一一对应的,这个从名字上就能看出来。二者的原理类似,因此这里就不再做过多介绍了。

基于字符的IO

基于字符的 I/O 操作接口输入和输出分别是:ReaderWriter

Reader

Reader的继承层级:

image.png

StringReader
String做为输入源

public class StringReader extends Reader{
    public StringReader(String s);
}

BufferedReader
从其他Reader读取数据到缓冲区。类似BufferedInputStream的功能

public class BufferedReader extends Reader{
    public BufferedReader(Reader in, int sz);
}

FileReader
从文件中读取数据

public class FileReader extends InputStreamReader {
    public FileReader(File file) throws FileNotFoundException;
}

PipedReader
从管道中读取数据。跟PipedWriter联合使用。

例子:

public void read(){
    String hello= new String( "hello word!");
    File file= new File( "d:/test.txt");
    //因为是用字符流来读媒介,所以对应的是Writer,又因为媒介对象是文件,所以用到子类是FileWriter
    Writer os= new FileWriter( file);
    os.write( hello);
    os.close();
}

Writer
Writer的实现类同Reader对应,从名字上就能很容易找到联系。

image.png

例子:

public static void write() throws IOException{
    File file= new File( "d:/test.txt");
    //因为是用字符流来读媒介,所以对应的是Reader
    //又因为媒介对象是文件,所以用到子类是FileReader
    Reader reader= new FileReader( file);
    char [] byteArray= new char[( int) file.length()];
    int size= reader.read( byteArray);
    System. out.println( "大小:"+size +";内容:" +new String(byteArray));
    reader.close();
  }

字节和字符的转换

数据持久化或网络传输都是以字节进行的,所以必须要有字符到字节或字节到字符的转化。字符到字节需要转化,其中读的转化过程如下图所示:


image.png

InputStreamReader类是字节到字符的转化桥梁,InputStreamReader 的过程要指定编码字符集,否则将采用操作系统默认字符集,很可能会出现乱码问题。StreamDecoder 正是完成字节到字符的解码的实现类。也就是当你用如下方式读取一个文件时:

try { 
     StringBuffer str = new StringBuffer(); 
     char[] buf = new char[1024]; 
     FileReader f = new FileReader("file"); 
     while(f.read(buf)>0){ 
         str.append(buf); 
     } 
     str.toString(); 
} catch (IOException e) {}

写入也是类似的过程如下图所示:


image.png

通过 OutputStreamWriter 类完成,字符到字节的编码过程,由 StreamEncoder 完成编码过程。

基于网络的IO

网络编程依赖于ServerSocketSocket。同File类似,只是表示网络的描述符。真正的数据传输依旧依赖于InputStreamOutputStream!!!
在互联网时代,网络编程在任何一个编程语言中都是一个极其重要的内容。因此,这部分的内容会在一个专门的文中来讲。

Java1.4引入了NIO
Java NIO

参考

相关文章

网友评论

      本文标题:Java - IO

      本文链接:https://www.haomeiwen.com/subject/rqsjuqtx.html