Java速通13:IO

129 阅读6分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第11天,点击查看活动详情

一、简介

I/O主要解决从数据源读入数据和将数据写入到目的地问题。数据源和目的地可以理解为IO流的两端。(这两端可以是文件或者网络连接)

字节流: InputStream/OutputStream。
字符流: Reader/Writer。

数据源与程序内存读写操作:

13-1.png

二、分类

2.1、按流的方向上

分为输入流InputStream或Reader,输出流OutputStream或Writer
任何从InputStream或Reader派生的类都有read()方法,用于读取单个字节或字节数组。
任何从OutputSteam或Writer派生的类都有write()方法,用于写单个字节或字节数组。

2.2、从操作字节还是操作字符的角度

面向字节流的类,基本都以XxxStream结尾。
面向字符流的类都以XxxReader或XxxWriter结尾。
这两种类型的流是可以转化的,有两个转化流的类,这个后面会说到。

2.3、一般在使用IO流的时候会有下面类似代码:

FileInputStream inputStream = new FileInputStream(new File("a.txt"));
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);

这其实是一种装饰器模式的使用,IO流体系中使用了装饰器模式包装了各种功能流类
IO流体系中FilterInputStream/FilterOutStreamFilterReader/FilterWriter就是装饰器模式的接口类。
从该类向下包装了一些功能流类。有DataInputStream、BufferedInputStream、LineNumberInputStream、PushbackInputStream等,当然还有输出的功能流类;面向字符的功能流类等。
下面几张图描述了整个IO流的继承体系结构:

InputStream流体系

13-2.png

OutputStream流体系

13-3.png

Reader体系

13-4.png

Writer体系

13-5.png

三、File其实是个工具类

File类其实不止是代表一个文件,它也能代表一个目录下的一组文件(代表一个文件路径)。
File类中最常用的方法:

File.delete()        // 删除文件或文件夹目录。
File.createNewFile() // 创建一个新的空文件。
File.mkdir()         // 创建一个新的空文件夹。
File.list()          // 获取指定目录下的文件和文件夹名称。
File.listFiles()     // 获取指定目录下的文件和文件夹对象。
File.exists()        // 文件或者文件夹是否存在

String   getAbsolutePath()   // 获取绝对路径
long     getFreeSpace()      // 返回分区中未分配的字节数。
String   getName()           // 返回文件或文件夹的名称。
String   getParent()         // 返回父目录的路径名字符串;如果没有指定父目录,则返回 null。
File     getParentFile()     // 返回父目录File对象
String   getPath()           // 返回路径名字符串。
long     getTotalSpace()     // 返回此文件分区大小。
long     getUsableSpace()    //返回占用字节数。
int      hashCode()          //文件哈希码。
long     lastModified()      // 返回文件最后一次被修改的时间。
long     length()            // 获取长度,字节数。
boolean  canRead()   //判断是否可读
boolean  canWrite()  //判断是否可写
boolean  isHidden()  //判断是否隐藏


// 成员函数
static File[]    listRoots()    // 列出可用的文件系统根。
boolean    renameTo(File dest)  // 重命名
boolean    setExecutable(boolean executable)    // 设置执行权限。
boolean    setExecutable(boolean executable, boolean ownerOnly)  // 设置其他所有用户的执行权限。
boolean    setLastModified(long time)       // 设置最后一次修改时间。
boolean    setReadable(boolean readable)    // 设置读权限。
boolean    setReadable(boolean readable, boolean ownerOnly)  // 设置其他所有用户的读权限。
boolean    setWritable(boolean writable)    // 设置写权限。
boolean    setWritable(boolean writable, boolean ownerOnly)  // 设置所有用户的写权限。

需要注意的是,不同系统对文件路径的分割符表是不一样的,比如Windows中是“\”,Linux是“/”。
而File类给我们提供了抽象的表示File.separator,屏蔽了系统层的差异。因此平时在代码中不要使用诸如“\”这种代表路径,可能造成Linux平台下代码执行错误。

示例:根据传入的规则,遍历得到目录中所有的文件构成的File对象数组

public class Directory {
    public static File[] getLocalFiles(File dir, final String regex){
        // dir.listFiles(FilenameFilter ) 是策略模式的一种实现,
        // 而且使用了匿名内部类的方式。
        return dir.listFiles(new FilenameFilter() {
            private Pattern pattern = Pattern.compile(regex);
            public boolean accept(File dir, String name) {
                return pattern.matcher(new File(name).getName()).matches();
            }
        });
    }

    // 重载方法
    public static File[] getLocalFiles(String path, final String regex){
        return getLocalFiles(new File(path),regex);
    }

    public static void main(String[] args) {
        String dir = "d:";
        File[] files = Directory.getLocalFiles(dir,".*\\.txt");
        for(File file : files){
            System.out.println(file.getAbsolutePath());
        }
    }
}

// 输出结果:
d:\\1.txt
d:\\新建文本文档.txt

四、InputStream和OutputStream

4.1、InputStream从数据源对象中将数据读入程序内容。

通过看InputStream的源码,可以看出它是一个抽象类:

public abstract class InputStream  extends Object  implements Closeable{}

提供了一些基础的输入流方法:

//从数据中读入一个字节,并返回该字节,遇到流的结尾时返回-1
abstract int read() 

//读入一个字节数组,并返回实际读入的字节数,最多读入b.length个字节,遇到流结尾时返回-1
int read(byte[] b)

// 读入一个字节数组,返回实际读入的字节数或者在碰到结尾时返回-1.
//b:代表数据读入的数组, off:代表第一个读入的字节应该被放置的位置在b中的偏移量,len:读入字节的最大数量
int read(byte[],int off,int len)

// 返回当前可以读入的字节数量,如果是从网络连接中读入,这个方法要慎用,
int available() 

//在输入流中跳过n个字节,返回实际跳过的字节数
long skip(long n)

//标记输入流中当前的位置
void mark(int readlimit) 

//判断流是否支持打标记,支持返回true
boolean markSupported() 

// 返回最后一个标记,随后对read的调用将重新读入这些字节。
void reset() 

// 关闭输入流,这个很重要,流使用完一定要关闭
void close()

直接从InputStream继承的流,基本上对应了每种数据源类型。

功能
ByteArrayInputStream将字节数组作为InputStream
StringBufferInputStream将String转成InputStream
FileInputStream从文件中读取内容
PipedInputStream产生用于写入相关PipedOutputStream的数据。实现管道化
SequenceInputStream将两个或多个InputStream对象转换成单一的InputStream
FilterInputStream抽象类,主要是作为“装饰器”的接口类,实现其他的功能流

4.2、OutputStream将程序内存中的数据写入到接收数据的一端。

public abstract class OutputStream implements Closeable, Flushable {}

提供了基础方法相比输入流来说简单多了,主要就是write写方法(几种重载的方法)、flush冲刷和close关闭。

// 写出一个字节的数据
abstract void write(int n) 

// 写出字节到数据b 
void write(byte[] b) 

// 写出字节到数组b,off:代表第一个写出字节在b中的偏移量,len:写出字节的最大数量
void write(byte[] b, int off, int len)

//刷新输出流,也就是将所有缓冲的数据发送到目的地
void flush()

// 关闭输出流
void close()

同样地,OutputStream也提供了一些基础流的实现,这些实现也可以和特定的目的地(接收端)对应起来,比如输出到字节数组或者是输出到文件/管道等。

功能
ByteArrayOutputStream在内存中创建一个缓冲区,所有送往“流”的数据都要放在此缓冲区
FileOutputStream将数据写入文件
PipedOutputStream和PipedInputStream配合使用。实现管道化
FilterOutputStream抽象类,主要是作为“装饰器”的接口类,实现其他的功能流