Java文件输入输出流及标准I/O流类知识总结
前几天参加了某企业的笔试,最后一道笔试题的题目大概是这样:
给定文件名filename以及一个字符串str,要求从文件中搜索该字符串,并且输出该字符串所在文件中的行数
背景
这道题考察的是对于文件输入输出流以及字符串的一道基本题目,可以算是比较基本的题目,但是由于太久没有复习,结果写的不是特别好,所以今天对文件输入输出流做一个复习和总结。
这是一张网上找的一张关于IO流操作的图片,这里我们可以看到I/O流分为字节流和字符流,从更加细致的划分,我们应该把IO流分为输入以及输出
编程语言的I/O类库中常使用流这个抽象概念,它代表任何有呢你产出数据的数据源对象或者是有能力接收数据的接收端对象。它屏蔽了实际I/O设备中处理数据的细节。
通过上述类图,我们可以知道我们需要关心的5个类应该有File、OutputStream、InputStream、Writer、Reader;
InputStream
InputSream:通过名称我们可以知道,他的作用是从数据源中获取数据,一共有7种不同类型的输入流:
类 | 功能 | 如何使用 |
---|---|---|
ByteArrayInputStream | 允许将内存缓冲区当作InputStream | 需要配合FilterInputStream来使用 |
StrirgBufferInputStream | 将String转换为输入流 | 需要配合StringBuffer来使用 |
FileInputStream | 从文件中读取信息 | 后面详解 |
PipedInputStream | 写入与PipedOutputStream相关数据 | 实现操作系统中“管道”概念的输入(多线程相关) |
SequencedInputStream | 将多个InputStream转换成单一InputStream | 实现多个输入流 |
FilterInputStream | 抽象类,作为“装饰器”接口,为其他InputStream提供功能 |
OutputStream
类 | 功能 | 如何使用 |
---|---|---|
ByteArrayOutputStream | 允许将内存缓冲区当作OutputStream | 需要配合FilterInputStream来使用 |
FileOutputStream | 从文件中写入信息 | 后面详解 |
PipedOutputStream | 写入与PipedOutputStream相关数据 | 实现操作系统中“管道”概念的输入(多线程相关) |
FilterOutputStream | 抽象类,作为“装饰器”接口,为其他OutputStream提供功能 |
InputStream实现了closeable接口,用来处理io流的关闭
从inputstream中读取下一个字节,返回值为从0-255.如果到达了输入流的结尾,那么方法返回值为-1。
这个方法会一直阻塞直至有可读的数据,当到达输入流结尾时,会抛出一个异常
public abstract int read() throws IOException;//抽象方法
从inputstream中读取n个字节到传入的byte数组中。会尝试一次读取len个长度的字节,但有可能读取
到<len个字节的数据,这个方法同样会阻塞,同时到达结尾时也会抛出异常。当len等于0时,不读取任何
字节并返回0.到达末尾时会返回0,否则至少一个字节的数据会被读取并传入b中。
第一个字节的输入被存入b[off]中,下一个字节存入b[off+1]的字节数组中,以此类推。读取的字节数
最多与len相等。k等于实际读取的字节数,此时b[off]到b[off+k-1]存储读取的字节数据
b[off+k]到b[off+len-1]不受影响
当读到最后一个字符时,或者输入流被关闭时,会抛出IOException
public int read(byte b[], int off, int len) throws IOException//抽象方法
从输入流中读取所有剩余的字节,同样会阻塞;当读到最后一个字符时,继续调用该方法会返回一个空的
字节数组
当输入流被关闭时,会抛出IOException
public byte[] readAllBytes() throws IOException
从字节流中读取n个字节,调用了上面的read方法
public byte[] readNBytes(int len) throws IOException
与上面的类似,不同点是将返回值传入byte数组b中,调用的同样是
public int readNBytes(byte[] b, int off, int len) throws IOException
跳过输入流中的N个字节
public long skip(long n) throws IOException
记录当前读到的位置
public synchronized void mark(int readlimit) {}
读取所有输入流中的数据并传输到输出流中
public long transferTo(OutputStream out) throws IOException
获取剩余可读字节数的估计值
public int available() throws IOException {
return available0();
}
private native int available0() throws IOException;
TODO:OutputStream
装饰者模式
I/O流的设计采用了装饰者模式,简单的来说就是为被装饰者添加新的功能,即加上一层“包装”,核心还是利用了java多态的性质,装饰者类和被装饰者都实现Component接口,同时装饰者内部持有被装饰者的引用,重写operation接口方法来实现对方法的增强。最后调用方法的时候,会按照装饰的顺序层层地来调用方法。
参考文章
Reader与Writer(字符输入流与输出流)
从上面的inputstream(outputstream也类似)我们可以知道其是将输入读取到一个字节数组中。但是在平时编程的情况中,我们很大一部分时间是在处理字符,因此字符输入流和输出流就提供了将字节流转化为字符流的功能:InputStreamReader可以把InputStream转化为Reader,而OutputStreamWriter可以把OutputStream转换为Writer
这里的处理运用到了适配器模式这里先不展开,以后有时间再做记录,适配器模式主要目的在于兼容
public InputStreamReader(InputStream in) {
super(in);
try {
sd = StreamDecoder.forInputStreamReader(in, this, (String)null); // ## check lock object
} catch (UnsupportedEncodingException e) {
// The default encoding should always be available
throw new Error(e);
}
}
这里是通过传入inputStream,通过StramDecoder也就是解码器来将输入流转化为字符串处理
所以一般输入输出的处理过程是:
输入流(inputstream)——>Reader——>Writer——>输出流(OutputStream)
处理流
- 文 件 :FileInputStream 、 FileOutputStrean 、FileReader 、FileWriter
- 数 组 :ByteArrayInputStream、 ByteArrayOutputStream、 CharArrayReader 、CharArrayWriter 对数组进行处理的节点流(对应的不再是文件,而是内存中的一个数组)
- 字符串 :StringReader、 StringWriter 对字符串进行处理的节点流
- 管 道 :PipedInputStream 、PipedOutputStream 、PipedReader 、PipedWriter 对管道进行处理的节点流
分别选取一个常见的Reader与Writer分析一下源码
BufferedReader
BufferedReader 是缓冲字符输入流。它继承于Reader。
BufferedReader 的作用是为其他字符输入流添加一些缓冲功能。
方法:
private Reader in;
private char cb[]; //字符缓冲区
private int nChars, nextChar;
//分别表示当前字符总数,下一个字符指针
private static final int INVALIDATED = -2;
private static final int UNMARKED = -1;
private int markedChar = UNMARKED;
private int readAheadLimit = 0; /* 只有在markedChar > 0的时候生效 */
/** 标志位,是否忽略换行符 */
private boolean skipLF = false;
/** The skipLF flag when the mark was set */
private boolean markedSkipLF = false;
//默认字符缓冲区大小
private static int defaultCharBufferSize = 8192;
//默认每一行的字符个数
private static int defaultExpectedLineLength = 80;
构造函数
public BufferedReader(Reader in, int sz) {
super(in);
if (sz <= 0)
throw new IllegalArgumentException("Buffer size <= 0");
this.in = in;
cb = new char[sz];
nextChar = nChars = 0;
}
调用了父类的构造函数
protected Reader(Object lock) {
if (lock == null) {
throw new NullPointerException();
}
this.lock = lock;
}
//这里是初始化了一个字符数组,同时也是使用了装饰者模式,装饰传进来的reader对象。将传进来的reader对象当作锁,这里应该是在对reader进行操作的时候加锁,防止多线程情况下出现的线程同步问题
这里回到我们一开始提到的题目:给定文件名filename以及一个字符串str,要求从文件中搜索该字符串,并且输出该字符串所在文件中的行数 思路:
- 构造InputStream
- 利用Reader读取,放入缓冲区
- 按行读取字符串
String str;
FileInputStream fileStream=new FileInputStream("/Users/wangzhiwang/fileName");//选定文件
InputStreamReader isr=new InputStreamReader(fileStream,"utf-8");//读入文件
BufferedReader br=new BufferedReader(isr);//将读入的文件放入缓冲区
String line;
int lineNum = 0;
while((line=br.readLine())!=null)//在缓冲区中不断取出一行数据
{
lineNum++;
if(line.indexOf(str)!=-1){
System.out.println(lineNum+":"+str)
}
}