java.io 包含java经典的io api,从1.0引入,1.1时做了字符流方面的补充(reader writer)
主要是四部分,
- 文件系统的相关操作
- 文件内容的随机读写
- 基于字节流的读写
- 基于字符流的读写
文件系统
主要看一下 File, 用文件或者目录的路径初始化一个对象,并且提供了对文件的一些操作,例如是否可读,是文件还是目录,当前目录下的文件,父级目录等等。这些操作委托给了FileSystem来实现。
看构造函数和部分实现:
private static final FileSystem fs = DefaultFileSystem.getFileSystem();
private final String path;
private final transient int prefixLength;
//构造函数
public File(String pathname) {
if (pathname == null) {
throw new NullPointerException();
}
this.path = fs.normalize(pathname);
this.prefixLength = fs.prefixLength(this.path);
}
// 文件大小
public long length() {
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkRead(path);
}
if (isInvalid()) {
return 0L;
}
return fs.getLength(this);
}
// 文件是否存在
public boolean exists() {
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkRead(path);
}
if (isInvalid()) {
return false;
}
return ((fs.getBooleanAttributes(this) & FileSystem.BA_EXISTS) != 0);
}
由于Java的跨平台特性,FileSystem在windows和linux下的实现都不相同。
字节流的读写
抽象类是InputStream 和 outputstream,具体实现可以先从FileInputStream 和OutputStream阅读。
InputStream
java.io.InputStream#read()
java.io.InputStream#read(byte[])
java.io.InputStream#read(byte[], int, int)
java.io.InputStream#skip
java.io.InputStream#available
java.io.InputStream#close
java.io.InputStream#mark(int readLimit)
java.io.InputStream#reset
java.io.InputStream#markSupported
read 是 抽象方法,后面两个read都是基于前面那个read实现的,skip是跳过一些字节
mark相当于书签,reset会回到书签的位置,readLimit会设置读了多少个byte之后书签可能失效,markSupported代表是否支持这个功能,FileInputStream是不支持的,BufferedInputStream是支持的
OutputStream如下:
java.io.OutputStream#write(int)
java.io.OutputStream#write(byte[])
java.io.OutputStream#write(byte[], int, int)
java.io.OutputStream#flush
java.io.OutputStream#close
flush 操作是如果有一些output stream有 buffer,那么buffer会写到目标位置,例如 目标是文件,就把缓存写到文件。不过这个不保证写到物理盘上,只保证写给操作系统。
FileInputStream ,FileOutputStream 底层都采用native方法来实现。
ByteArrayInputStream 底层是对byte数组进行读操作,支持书签,由于byteArray采用的数组对象,一般就是利用内存进行读操作。 可以理解为适配器模式。
ByteArrayOutputStream 类似
PipeInputStream 和PipeOutStream配套使用,PipeInput的数据来源必须是PipeOut,而且建议在多线程环境下,分别给两个线程使用,单个线程使用容易产生死锁。两个类都是线程安全的。
PipeOutStream 只有一个域,PipedInputStream sink
public synchronized void connect(PipedInputStream snk) throws IOException {
if (snk == null) {
throw new NullPointerException();
} else if (sink != null || snk.connected) {
throw new IOException("Already connected");
}
sink = snk;
snk.in = -1;
snk.out = 0;
snk.connected = true;
}
public void write(int b) throws IOException {
if (sink == null) {
throw new IOException("Pipe not connected");
}
sink.receive(b);
}
因此PipeOut是使用connect和PipeIn 建立简介,并且把PipeInput的标志位 设置为连接了。在write的时候也是调用sink的receive方法,PipedInputStream 的方法就比较多。
java.io.PipedInputStream#PipedInputStream(java.io.PipedOutputStream)
java.io.PipedInputStream#PipedInputStream(java.io.PipedOutputStream, int)
java.io.PipedInputStream#PipedInputStream()
java.io.PipedInputStream#PipedInputStream(int)
// 初始化buff
java.io.PipedInputStream#initPipe
java.io.PipedInputStream#connect
java.io.PipedInputStream#receive(int)
java.io.PipedInputStream#receive(byte[], int, int)
java.io.PipedInputStream#checkStateForReceive
java.io.PipedInputStream#awaitSpace
java.io.PipedInputStream#receivedLast
java.io.PipedInputStream#read()
java.io.PipedInputStream#read(byte[], int, int)
java.io.PipedInputStream#available
java.io.PipedInputStream#close
java.io.PipedInputStream#closedByWriter
java.io.PipedInputStream#closedByReader
java.io.PipedInputStream#connected
java.io.PipedInputStream#readSide
java.io.PipedInputStream#writeSide
java.io.PipedInputStream#DEFAULT_PIPE_SIZE
java.io.PipedInputStream#PIPE_SIZE
java.io.PipedInputStream#buffer
java.io.PipedInputStream#in
java.io.PipedInputStream#out
数据都保存在 protected byte buffer[]; 这个循环buff内,而in 和out分别表示 写入和读取的索引位置, 相当于循环队列的头尾指针,当in小于0,是空buff, in等于out是满的buff。
protected synchronized void receive(int b) throws IOException {
checkStateForReceive();
writeSide = Thread.currentThread();
if (in == out)
awaitSpace();
if (in < 0) {
in = 0;
out = 0;
}
buffer[in++] = (byte)(b & 0xFF);
if (in >= buffer.length) {
in = 0;
}
}
private void checkStateForReceive() throws IOException {
if (!connected) {
throw new IOException("Pipe not connected");
} else if (closedByWriter || closedByReader) {
throw new IOException("Pipe closed");
} else if (readSide != null && !readSide.isAlive()) {
throw new IOException("Read end dead");
}
}
最终是把数据写到buffer 中,可以看到在写入前,要进行状态检查和容量检查,写入之后,如果in 越界,就绕回到 0,可能发现,如果写入太快,读很慢,buff是会被覆盖掉的,其实满的时候写线程会阻塞在awaitSpace,而且两个类都是全局加锁,所以读写之间是互相阻塞的。检查线程状态也非常简单,就是如果有方法调用recieve相关的方法,就把该线程记为writeSide,如果有方法调用读read,就记录为readSide。 并且记录 两个piped是否关闭。 另外看一下满的时候写者如何等待:
private void awaitSpace() throws IOException {
while (in == out) {
checkStateForReceive();
/* full: kick any waiting readers */
notifyAll();
try {
wait(1000);
} catch (InterruptedException ex) {
throw new java.io.InterruptedIOException();
}
}
}
经典的把wait套在while循环里,当等待超时,就会判断是否有空间。
syncronized(obj) {
while(条件){
obj.wait();
}
}
其他方法(){
obj.notifyAll();
}
并且读的时候,如果没有数据也会发生等待:
public synchronized int read() throws IOException {
if (!connected) {
throw new IOException("Pipe not connected");
} else if (closedByReader) {
throw new IOException("Pipe closed");
} else if (writeSide != null && !writeSide.isAlive()
&& !closedByWriter && (in < 0)) {
throw new IOException("Write end dead");
}
readSide = Thread.currentThread();
int trials = 2;
while (in < 0) {
if (closedByWriter) {
/* closed by writer, return EOF */
return -1;
}
if ((writeSide != null) && (!writeSide.isAlive()) && (--trials < 0)) {
throw new IOException("Pipe broken");
}
/* might be a writer waiting */
notifyAll();
try {
wait(1000);
} catch (InterruptedException ex) {
throw new java.io.InterruptedIOException();
}
}
int ret = buffer[out++] & 0xFF;
if (out >= buffer.length) {
out = 0;
}
if (in == out) {
/* now empty */
in = -1;
}
return ret;
}
并且,两个操作都会在循环里判断,是不是读写已经关闭,从而抛出异常,停止等待。
再看一下SequenceInputStream是多个流合并进行读取, 轮询读取,不常用。
public int read() throws IOException {
while (in != null) {
int c = in.read();
if (c != -1) {
return c;
}
nextStream();
}
return -1;
}
final void nextStream() throws IOException {
if (in != null) {
in.close();
}
if (e.hasMoreElements()) {
in = (InputStream) e.nextElement();
if (in == null)
throw new NullPointerException();
}
else in = null;
}
filterInputStream 和FilterOutputStream,这两个是几个装饰器类的基类,如BufferedInputStream, DataInputStream,PushbackInputStream ,BufferdOutputStream. DataOutputStream. PrintStream,可以继承或者组合,参数自己的类。
先看BufferedInputStream 和BufferdOutputStream,由于底层文件或者socket 可能存在堵塞延迟,写的时候可以先把数据写到缓存里,再从缓存把数据写入到目标,读取的时候可以先从缓存读取,再从目标取数据到缓存。 即在io和目标之间加了缓存。先看读
public synchronized int read() throws IOException {
if (pos >= count) {
fill();
if (pos >= count)
return -1;
}
return getBufIfOpen()[pos++] & 0xff;
}
pos 记录的是读缓存的位置,如果没有缓存可读,需要fill 装填, 如果可以读缓存,就返回缓存的结果。
我们看一下装填部分,状态部分如下:
private static final
AtomicReferenceFieldUpdater bufUpdater =
AtomicReferenceFieldUpdater.newUpdater
(BufferedInputStream.class, byte[].class, "buf");
private void fill() throws IOException {
byte[] buffer = getBufIfOpen();
if (markpos < 0)
pos = 0; /* no mark: throw away the buffer */
else if (pos >= buffer.length) /* no room left in buffer */
if (markpos > 0) { /* can throw away early part of the buffer */
int sz = pos - markpos;
System.arraycopy(buffer, markpos, buffer, 0, sz);
pos = sz;
markpos = 0;
} else if (buffer.length >= marklimit) {
markpos = -1; /* buffer got too big, invalidate mark */
pos = 0; /* drop buffer contents */
} else if (buffer.length >= MAX_BUFFER_SIZE) {
throw new OutOfMemoryError("Required array size too large");
} else { /* grow buffer */
int nsz = (pos <= MAX_BUFFER_SIZE - pos) ?
pos * 2 : MAX_BUFFER_SIZE;
if (nsz > marklimit)
nsz = marklimit;
byte nbuf[] = new byte[nsz];
System.arraycopy(buffer, 0, nbuf, 0, pos);
if (!bufUpdater.compareAndSet(this, buffer, nbuf)) {
// Can't replace buf if there was an async close.
// Note: This would need to be changed if fill()
// is ever made accessible to multiple threads.
// But for now, the only way CAS can fail is via close.
// assert buf == null;
throw new IOException("Stream closed");
}
buffer = nbuf;
}
count = pos;
int n = getInIfOpen().read(buffer, pos, buffer.length - pos);
if (n > 0)
count = n + pos;
}
在读取之前,判断是否存在mark,没有mark就直接调用被装饰的inputstream的read方法,如果存在mark,则判断是不是失效,保留mark之后的数据,而且如果要mark的数据满了,就会进行扩容操作,并替换掉buff这个域,这个使用cas原子操作,保证数据符合预期。
很奇怪的是BufferedInputStream几乎所有操作都加了锁,buff封闭在对象内,为啥要加原子操作呢,因为close方法会对buff进行操作,而且不加锁,如果在拷贝的过程中,buff成为null,而此处又把buff改成nbuff,close就会失败。 close 会自旋重试,而fill遇到这种情况会抛出异常。
再看BufferedOutputStream
public synchronized void write(int b) throws IOException {
if (count >= buf.length) {
flushBuffer();
}
buf[count++] = (byte)b;
}
/** Flush the internal buffer */
private void flushBuffer() throws IOException {
if (count > 0) {
out.write(buf, 0, count);
count = 0;
}
}
public synchronized void flush() throws IOException {
flushBuffer();
out.flush();
}
写入时,如果buff满了,就把buffer先写入,再进行操作,flush的时候也会把buff先写入,再进行flush操作。
DataInputStream 和DataOutputStream主要就是 针对基本类型进行的读写操作,扩展了一些api,因为之前read和write只能围绕byte进行操作,如果要写入一个long ,float ,double 都要自己封装,而Data的这两个类,进行这些封装。 还设计了相应的接口,DataInput,DataOutput
java.io.DataInputStream#DataInputStream
java.io.DataInputStream#read(byte[])
java.io.DataInputStream#read(byte[], int, int)
java.io.DataInputStream#readFully(byte[])
java.io.DataInputStream#readFully(byte[], int, int)
java.io.DataInputStream#skipBytes
java.io.DataInputStream#readBoolean
java.io.DataInputStream#readByte
java.io.DataInputStream#readUnsignedByte
java.io.DataInputStream#readShort
java.io.DataInputStream#readUnsignedShort
java.io.DataInputStream#readChar
java.io.DataInputStream#readInt
java.io.DataInputStream#readLong
java.io.DataInputStream#readFloat
java.io.DataInputStream#readDouble
java.io.DataInputStream#readLine
java.io.DataInputStream#readUTF()
java.io.DataInputStream#readUTF(java.io.DataInput)
java.io.DataInputStream#bytearr
java.io.DataInputStream#chararr
java.io.DataInputStream#readBuffer
java.io.DataInputStream#lineBuffer
在个部分唯一复杂的地方就是 readUTF 对应的DataOutputStream里面的writeUTF。
可以见文档和文档2,readUTF是按照UTF-8编码的形式 读取byte数组,按照UTF-8进行解码,最后保存到String中,而writeUTF8是编码的过程将String 编码成byte数组。String内部是char[];
因此,这里是 char[] 与byte[] 之间的编解码。这个编码在UTF-8的基础上稍作调整。一个char可能编码成1,2,3个字符,解码时可以获得字符数量。
PushbackInputStream 可以读取一些数据之后,又把数据unread,放回stream里,这个steam是不支持mark和reset的。实现上采用buff的形式,读取一些数据,unread的时候,修改buff的pos,下次读取可能会从buff里先读取。
public int read() throws IOException {
ensureOpen();
if (pos < buf.length) {
return buf[pos++] & 0xff;
}
return super.read();
}
public void unread(int b) throws IOException {
ensureOpen();
if (pos == 0) {
throw new IOException("Push back buffer is full");
}
buf[--pos] = (byte)b;
}
PrinterStream 提供了许多方便的打印操作, 将不同数据,转换成字符串, 调用BufferedWriter 的写方法,进行字符流的写操作。另外有两个功能,一 写的时候设置是否有错误setError
,二,允许开启自动flush,例如,出现换行回车的时候, 会一起写入,最后显示效果比较流畅。
private PrintStream(boolean autoFlush, OutputStream out) {
super(out);
this.autoFlush = autoFlush;
this.charOut = new OutputStreamWriter(this);
this.textOut = new BufferedWriter(charOut);
}
private void write(String s) {
try {
synchronized (this) {
ensureOpen();
textOut.write(s);
textOut.flushBuffer();
charOut.flushBuffer();
if (autoFlush && (s.indexOf('\n') >= 0))
out.flush();
}
}
catch (InterruptedIOException x) {
Thread.currentThread().interrupt();
}
catch (IOException x) {
trouble = true;
}
}
可以看到,写操作都是委托给BufferedWriter,中间使用适配器进行转换。
字符流的读写
前面的Stream类的读写单元都是围绕着byte或者byte[] 数组进行操作,要进行基本类型或者字符串的操作,要使用装饰器类。很多情况下,读写是围绕着字符串进行操作,因此基于字节流的操作非常不方便。
相应的JDK1.1给出了一套围绕着char或者char[] 数组进行操作的类。
Reader:BufferedReader, CharArrayReader, FilterReader, InputStreamReader, PipedReader, StringReader
Writer: BufferedWriter, CharArrayWriter, FilterWriter, OutputStreamWriter, PipedWriter, PrintWriter, StringWriter
字符流的类架构和直接类几乎相同,继承和装饰器模式,名称和对应的InputStream,OutputStream也一致。
先看基类Reader和Writer, Reader 把实例本身设置了一个域Object lock作为同步锁,这样有一个好处是子类也可以使用这个域作为同步锁,而不用分别使用this。
不过Writer顺便实现了Appendable接口,这样就可以流式append操作。
CharArrayReader,CharArrayWriter 内存读写操作, 都是直接放入char数组
public int read() throws IOException {
synchronized (lock) {
ensureOpen();
if (pos >= count)
return -1;
else
return buf[pos++];
}
}
public void write(int c) {
synchronized (lock) {
int newcount = count + 1;
if (newcount > buf.length) {
buf = Arrays.copyOf(buf, Math.max(buf.length << 1, newcount));
}
buf[count] = (char)c;
count = newcount;
}
}
再看BufferedWriter和BufferedReader 这个地方没有继承自FilterWriter,FilterReader。
采用的是数组做缓存,如果换成为空就进行装填。
再看FileWriter和FileReader, FileWriter 直接继承了OutputStreamWriter,参数都是构造一个FileOutputStream并传递给OutputStreamWriter的构造函数。FileReader 也是构造FileOutPutStream给OutputStreamWriter使用。
public class FileWriter extends OutputStreamWriter {
public FileWriter(String fileName) throws IOException {
super(new FileOutputStream(fileName));
}
}
相当于简化了下面的写法:
Writer writer = new OutputStreamWriter(new FileOutPutStream(fileName));
Reader reader = new InputStreamReader(new FileInputStream(fileName));
=》
Writer writer = new FileWriter(fileName);
Reader reader = new FileReader(fileName);
PipedWriter 和PipedReader 也是成对使用,实现方式和PipedInputStream,PipedOutputStream类似
看一下适配器的实现,InputStreamReader 和 OutputStreamWriter
//InputStreamReader.java
private final StreamDecoder sd;
public int read() throws IOException {
return sd.read();
}
//OutputStreamWriter
private final StreamEncoder se;
public void write(int c) throws IOException {
se.write(c);
}
具体的操作都委托给了StreamDecoder和StreamEncoder 对底层的字节流进行读写转换成字符流
文件随机读写
当使用FileInputStream或者FileOutPutStream,或者相应的Reader,Writer只能按照顺序进行读。实际上大部分操作系统,底层有接口支持读取文件的一部分,或者从文件的某个字节位置开始读写,因此可以和使用RandomAccessFile
部分接口:
java.io.RandomAccessFile#RandomAccessFile(java.lang.String, java.lang.String)
java.io.RandomAccessFile#RandomAccessFile(java.io.File, java.lang.String)
java.io.RandomAccessFile#getFD
java.io.RandomAccessFile#getChannel
java.io.RandomAccessFile#open0
java.io.RandomAccessFile#open
java.io.RandomAccessFile#read()
java.io.RandomAccessFile#read0
java.io.RandomAccessFile#readBytes
java.io.RandomAccessFile#read(byte[], int, int)
java.io.RandomAccessFile#read(byte[])
java.io.RandomAccessFile#getFilePointer
java.io.RandomAccessFile#seek
java.io.RandomAccessFile#seek0
java.io.RandomAccessFile#length
java.io.RandomAccessFile#setLength
java.io.RandomAccessFile#close
另外为了读写方便,还实现了DataInput和DataOutput接口,关键方法采用了native实现,即不同平台的实现不同。