共计 11159 个字符,预计需要花费 28 分钟才能阅读完成。
IO 介绍
什么是 IO 流
在 Java
中提供了 IO
接口,次要用于读写,输入输出操作。IO
是以流为根底进行操作的,流相当于是间断的
数据流。能够从数据流中读取数据,也能够往流中写数据。
IO 分类
- 按流向分:输出流和输入流
- 按数据类型分:字节流和字符流
- 按性能类型分:管道流、文件流、数组流、缓冲流、对象流、转换流、根本数据类型流
-
依照角色类型:节点流和解决流
- 节点流:从源间接读取数据
- 解决流:对一个曾经存在的流连贯和封装,通过所封装的流的性能读写数据,解决流的构造方法都有一个 节点流参数
IO 的设计模式
在 javaIO
中有两个设计模式:装璜者模式 、 适配器模式。
-
装璜者模式
给一个对象减少一个些新的性能,而且是动静的,要求装璜对象和被装璜对象实现同一个接口,解决流装璜节点流。
-
适配器模式
适配器负责将一个类的接口转换成另外一个接口,个别是指节点流,比方
StringReader
继承了Reader
同时有一个String
对象,将String
对象的接口适配成Reader
类型的接口。在调用Reader
的read()
办法,实际上是在调用String
对象的办法。
IO 体系
IO
流的顶层抽象类能够分为四种:字节输出流InputStream
,字节输入流OutputStream
,字符输出流Reader
,字符输入流Writer
。
字节输出流
概述
字节输出流 InputStream
是一个抽象类,用于字节的输出。字节输出流能够读取一个字节或者一个字节数组。字节输出流读取字节之后,返回的是字节的 ASCII
码,如果读取的是字节数组,就将字节的 ASCII
寄存到数组当中,而后返回字节数组的长度。
public abstract class InputStream implements Closeable
实现类
办法
/**
* 从数据流中读取一个字节数据,返回的是字节的 ASCII 码
*/
public abstract int read();
/**
* 从数据流中读取一个字节数组长度的字节数据,并将字节数据放到字节数组中,返回读取的字节数据个数
*/
public int read(byte b[]);
/**
* 从数据流中读取 len 长度的字节数据,并从字节数组的 off 地位开始放数据,返回读取的字节数据个数
*/
public int read(byte b[], int off, int len);
/**
* 从数据流中跳过 n 长度的字节
*/
public long skip(long n);
/**
* 在不阻塞的状况下数据流中有多少个字节能够读取
*/
public int available();
字节输入流
概述
字节输入流 OutputStream
是一个抽象类,用于输入字节数据,它实现了 Flushable
接口,用于刷新流,然而 flush
并不是强制的,而是针对有缓冲区的。
public abstract class OutputStream implements Closeable, Flushable
实现类
办法
/**
* 向数据流中写一个字节数据
*/
public abtract void write(int b);
/**
* 向数据流中写一个字节数组的字节数据
*/
public void write(byte b[]);
/**
* 向数据流中写一个字节数组的字节数据,从字节数组 off 地位开始,len 长度
*/
public void write(byte b[], int off, int len);
字符输出流
概述
字符输出流 Reader
是一个抽象类,用于读取字符。
public abstract class Reader implements Readable, Closeable
实现类
办法
/**
* 用于同步此流上的操作对象
*/
protected Object lock;
/**
* 空构造方法,同步锁是以后流对象
*/
protected Reader();
/**
* 构造方法,指定对象为同步锁
*/
protected Reader(Object lock);
/**
* 读取一个字符,并返回 ascii 码
*/
public int read();
/**
* 读取一个字符数组,并返回字符数组的长度
*/
public int read(char cbuf[]);
/**
* 读取 len 长度的字符,并从字符数组的 off 地位处放到字符数组中
*/
abstract public int read(char cbuf[], int off, int len);
/**
* 在字符流中标记一个地位
*/
public void mark(int readAheadLimit);
/**
* 回到 mark 的地位
*/
public void reset();
字符输入流
概述
字符输入流 Writer
是一个抽象类,用于写字符,它实现了 Appendable
用于追加字符。
public abstract class Writer implements Appendable, Closeable, Flushable
实现类
办法
/**
* 往字符数组中写一个 ascii 码示意的字符
*/
public void write(int c);
/**
* 把 cbuf 中的元素写到字符数组中
*/
public void write(char cbuf[]);
/**
* 从字符数组 cbuf 的 off 地位开始,写 len 长度到字符数组中
*/
abstract public void write(char cbuf[], int off, int len);
/**
* 把字符串写入到字节数组中
*/
public void write(String str);
/**
* 从字符串的 off 开始写 len 长度到字符数组中
*/
public void write(String str, int off, int len);
/**
* 把字符序列 csq 加到字符数组元素的开端
*/
public Writer append(CharSequence csq);
/**
* 从字符序列 start 到 end 地位的字符加到字符数组元素的开端
*/
public Writer append(CharSequence csq, int start, int end);
/**
* 把字符加到字符数组的开端
*/
public Writer append(char c);
文件流
概述
文件流蕴含了字节文件流和字符文件流两种类型,其中字节文件流是节点流,字符文件流是解决流。
- FileInputStream
- FileOutputStream
- FileReader
- FileWriter
数据流向
字节文件流
字节流数据是从文件到输入输出程序。
字符文件流
字符流数据是先流向字节流再流向字符流。
源码解析
字节文件输出流
-
构造方法
字节输出流罕用的构造方法有两种,能承受文件和字符串的参数,字符串结构也是创立一个文件。
// 字符串结构,也是创立一个文件
public FileInputStream(String name) throws FileNotFoundException {this(name != null ? new File(name) : null);
}
// 文件构造方法
public FileInputStream(File file) throws FileNotFoundException {String name = (file != null ? file.getPath() : null);
// 创立一个文件描述符
fd = new FileDescriptor();
// 将文件描述符退出到 otherPath 汇合中,开释的时候能够一起开释
fd.attach(this);
path = name;
// 调用 native 的 open 办法
open(name);
}
-
读取数据
读取数据是调用
native
办法读取数据,能够每次读取一个字节,也能够读取一个字节数组的数据
字节文件输入流
-
构造方法
字节输入罕用的构造方法和字节输出相似,只不过是多了一个布尔类型的
append
参数,这个参数决定是在文件外面追加内容,还是从头开始写,默认是笼罩从头开始。 -
写数据
写数据也是调用的
native
办法,能够写一个字节的数据,也能够写一个字节数组的数据。除此之外写办法也有一个append
参数,示意是追加写还是笼罩写。
FileDescriptor:是一个文件描述符,关上一个文件或者套接字,操作系统内核都会返回一个文件描述符,文件描述符有三个值:0- 规范输出,1- 规范输入,2- 规范谬误输入
字符文件输出流
字符文件输出流只有构造方法,对数据的操作是借助转换流 InputStreamReader
来实现的,字符输出流的构造方法是创立一个字节文件输出流
字符文件输入流
字符文件输入流只有构造方法,对数据的操作是借助转换流 OutputStreamWriter
来实现的,字符输入流的构造方法是创立一个字节文件输入流
示例
- 字节文件流
/**
* 字节输出流
*/
FileInputStream fis = new FileInputStream("./file");
byte[] bytes = new byte[2];
int len;
// 能够一次读取一个字节或者多个字节
while((len = fis.read(bytes)) != -1) {System.out.println(new String(bytes, 0, len));
}
fis.close();
/**
* 字节输入流
*/
FileOutputStream fos = new FileOutputStream("./file");
fos.write("helloio".getBytes());
fos.close();
内存流
概述
内存流蕴含了字节数组流和字符数组流
- ByteArrayInputStream
- ByteArrayOutputStream
- CharArrayReader
- CharArrayWriter
数据流向
内存流是从内存到输入输出程序
源码剖析
字节数组输出流
-
构造方法
字节数组输出流有两个构造方法,初始化一个字节数组,同时能够指定字节数组的起始地位和长度
// 初始化一个字节数组,读取的时候从 0 的地位开始读取 public ByteArrayInputStream(byte buf[]) { this.buf = buf; this.pos = 0; this.count = buf.length; } // 初始化一个字节数组,读取的时候从 pos 的地位开始读取 public ByteArrayInputStream(byte buf[], int offset, int length) { this.buf = buf; this.pos = offset; this.count = Math.min(offset + length, buf.length); this.mark = offset; }
-
读取数据
读取数据能够每次读取一个字节,也能够读取一个字节数组,底层采纳
System.arraycopy
办法将字节数组的数据复制到传入的字节数组中
字节数组输入流
-
构造方法
字节数组输入流构造方法次要是创立一个字节数组,能够指定字节输出的长度,不指定的时候默认是 32.
// 默认长度 32 public ByteArrayOutputStream() {this(32); } public ByteArrayOutputStream(int size) {buf = new byte[size]; }
-
数组扩容
字节数组输出流,能够反对动静扩容,每次扩容为左移一位,相当于增加一倍。
-
输入数据
输入数据是将字节数据放到字节数组输入流的字节数组外面
字符数组输出流
-
构造方法
字符数组输出流构造方法创立一个字符数组
public CharArrayReader(char buf[]) { this.buf = buf; this.pos = 0; this.count = buf.length; }
-
读取数据
读取数据是返回字符数组中的元素
字符数组输入流
-
构造方法
创立一个指定长度的字符数组,默认是 32 位
// 默认 32 位 public CharArrayWriter() {this(32); } public CharArrayWriter(int initialSize) {buf = new char[initialSize]; }
-
输入数据
输入数据就是将字符放到字符数组中
示例
字节数组内存流
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream("helloid".getBytes(StandardCharsets.UTF_8));
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
int len;
while ((len = byteArrayInputStream.read()) != -1) {byteArrayOutputStream.write(len);
}
System.out.println(byteArrayOutputStream);
字符数组内存流
CharArrayReader charArrayReader = new CharArrayReader("helloio".toCharArray());
CharArrayWriter charArrayWriter = new CharArrayWriter();
int len;
while ((len = charArrayReader.read()) != -1) {charArrayWriter.write(len);
}
System.out.println(charArrayWriter);
缓冲流
概述
缓冲流蕴含了字节缓冲流和字符缓冲流
- BufferedInputStream
- BufferedOutputStream
- BufferedReader
- BufferedWriter
数据流向
字节缓冲流
字节缓冲流是解决流,是在节点流的根底上操作的
字符缓冲流
字符缓冲流是解决流,是在节点流的根底上操作的
源码解析
字节输出缓冲流
-
变量
// 字节数组的元素数量,范畴是[0, buf.length] protected int count; // 下一个读取字符的索引,范畴是[0, count] protected int pos; /** * 开启反复读取数据的变量 */ // 这个值是上次调用 mark 办法后 pos 的值,范畴是[-1, pos] protected int markpos = -1; // 限度可反复度的最大长度 protected int marklimit;
-
构造方法
字节输出缓冲流,在创立一个字节输出流对象的同时,也会初始化一个默认长度为 8192 的字节数组
public BufferedInputStream(InputStream in) {this(in, 8192); } public BufferedInputStream(InputStream in, int size) {super(in); // 初始化一个字节数组 buf = new byte[size]; }
-
读取数据
public synchronized int read() throws IOException { // 当索引的地位大于字节数组的元素个数,相当于字节数组中的元素曾经读取完了 if (pos >= count) { // 从数据源中读取数据填充到字节数组中 fill(); if (pos >= count) return -1; } // 从字节数组中返回元素 return getBufIfOpen()[pos++] & 0xff; } private void fill() throws IOException { // 获取字节数组对象 byte[] buffer = getBufIfOpen(); // 没有应用过 mark 标记,间接将 pos 指向字节数组的首位,就是将字节数组前面的元素间接抛弃 if (markpos < 0) pos = 0; // 曾经读取到字节数组的最大长度了,及 pos 曾经指向字节数组的最初一个元素了 else if (pos >= buffer.length) { // 调用过 mark 办法,将 markpos 地位开始的元素挪动到后面,markpos 后面的数据间接抛弃,前面的不 // 能抛弃所以要挪动到后面 if (markpos > 0) { int sz = pos - markpos; System.arraycopy(buffer, markpos, buffer, 0, sz); pos = sz; markpos = 0; // 因为:pos >= buffer.length,这里相当于有效 mark,因为曾经反复读取过数据了 } else if (buffer.length >= marklimit) { markpos = -1; pos = 0; // 字节数组一直扩容之后,曾经达到最大了,抛出异样 } else if (buffer.length >= MAX_BUFFER_SIZE) {throw new OutOfMemoryError("Required array size too large"); } else { // 2 倍的 pos,MAX_BUFFER_SIZE 最小值作为新的字节数组的长度 int nsz = (pos <= MAX_BUFFER_SIZE - pos) ? pos * 2 : MAX_BUFFER_SIZE; // 如果比 marklimit 大,间接扩容到 marklimit if (nsz > marklimit) nsz = marklimit; byte nbuf[] = new byte[nsz]; System.arraycopy(buffer, 0, nbuf, 0, pos); if (!bufUpdater.compareAndSet(this, buffer, nbuf)) {throw new IOException("Stream closed"); } buffer = nbuf; } } count = pos; // 从文件中持续读取数据放到字节数组中 int n = getInIfOpen().read(buffer, pos, buffer.length - pos); if (n > 0) count = n + pos; }
-
反复读取数据
// 标记一下 markPos 的地位为当初 pos 的地位,并且设置可反复读的最大长度 public synchronized void mark(int readlimit) { marklimit = readlimit; markpos = pos; } // 重置的时候将 pos 又还原到最开始的索引处 public synchronized void reset() throws IOException {getBufIfOpen(); if (markpos < 0) throw new IOException("Resetting to invalid mark"); pos = markpos; }
字节输入缓冲流
-
构造方法
字节输入缓冲流的构造方法是创立一个字节输入流对象,同时保护一个 8192 长度的字节数组缓冲区
public BufferedOutputStream(OutputStream out) {this(out, 8192); } public BufferedOutputStream(OutputStream out, int size) {super(out); buf = new byte[size]; }
-
写数据
写数据是先写到字节数组中,如果字节数组的元素达到了字节数组的长度,就写到文件外面去
public synchronized void write(int b) throws IOException {if (count >= buf.length) {flushBuffer(); } buf[count++] = (byte)b; } public BufferedReader(Reader in, int sz) {super(in); this.in = in; cb = new char[sz]; nextChar = nChars = 0; }
字符输出缓冲流
-
构造方法
字符缓冲流的构造方法和字节缓冲流相似,在创立一个流的同时,外部也保护了一个默认为 8192 长度的字符数组。
public BufferedReader(Reader in) {this(in, 8192); } public BufferedReader(Reader in, int sz) {super(in); this.in = in; cb = new char[sz]; nextChar = nChars = 0; }
-
读数据
读取数据是从字符数组中读取,读取完了之后从流中获取数据填充到字符数组中
public int read() throws IOException {synchronized (lock) {ensureOpen(); for (;;) { // 下一个要读取的 大于 字符数据中的元素个数,就填充数据 if (nextChar >= nChars) {fill(); if (nextChar >= nChars) return -1; } if (skipLF) { skipLF = false; if (cb[nextChar] == '\n') { nextChar++; continue; } } return cb[nextChar++]; } } } private void fill() throws IOException { int dst; if (markedChar <= UNMARKED) { // 未调用 mark 办法,间接舍弃数据,读取数据的指针索引指向字符数组第一位 dst = 0; } else { int delta = nextChar - markedChar; // 标记的数据曾经读取过了,间接舍弃字符数组的数据 if (delta >= readAheadLimit) { /* Gone past read-ahead limit: Invalidate mark */ markedChar = INVALIDATED; readAheadLimit = 0; dst = 0; } else {if (readAheadLimit <= cb.length) { // 将要读取的地位到标记的地位的字符串,这是没有读取的,挪动到后面 System.arraycopy(cb, markedChar, cb, 0, delta); markedChar = 0; dst = delta; } else { // 标记地位大于缓冲区大小,从新调整缓冲区大小,并将将要读取的地位到标记地位的 // 字符串挪动到后面 char ncb[] = new char[readAheadLimit]; System.arraycopy(cb, markedChar, ncb, 0, delta); cb = ncb; markedChar = 0; dst = delta; } nextChar = nChars = delta; } } // 读取数据放到字符数组中 int n; do {n = in.read(cb, dst, cb.length - dst); } while (n == 0); // 有读取到数据,从新设置将要读取的索引和字符数组中元素的个数 if (n > 0) { nChars = dst + n; nextChar = dst; } }
字符输入缓冲流
-
构造方法
字符输入缓冲流构造方法是新建一个输入流和一个字符缓冲数组
public BufferedWriter(Writer out) {this(out, 8192); } public BufferedWriter(Writer out, int sz) {super(out); this.out = out; cb = new char[sz]; nChars = sz; nextChar = 0; }
-
写数据
写输出是先往字符数组中写,字符数组满了之后往流外面写
示例
字节缓冲流
/**
* 字节输出缓冲流
*/
BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream("./file"));
int len;
while ((len = bufferedInputStream.read()) != -1) {System.out.println((char) len);
}
/**
* 字节输入缓冲流
*/
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream("./file"));
bufferedOutputStream.write(97);
bufferedOutputStream.flush();
bufferedOutputStream.close();
字符缓冲流
/**
* 字符输出缓冲流
*/
BufferedReader bufferedReader = new BufferedReader(new FileReader("./file"));
String str;
while ((str = bufferedReader.readLine()) != null) {System.out.println(str);
}
bufferedReader.close();
/**
* 字符输入缓冲流
*/
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("./file"));
bufferedWriter.write("helloio");
bufferedWriter.flush();
bufferedWriter.close();
对象流
概述
对象流次要是用于序列化对象,蕴含了对象输出流 ObjectInputStream
和对象输入流ObjectOutputStream
数据流向
示例
/**
* 对象输出流
*/
ObjectOutputStream objectOutputStream = new ObjectOutputStream (new FileOutputStream("./file"));
User user = new User("helloobject");
objectOutputStream.writeObject(user);
objectOutputStream.flush();
objectOutputStream.close();
/**
* 对象输入流
*/
ObjectInputStream objectInputStream = new ObjectInputStream (new FileInputStream("./file"));
User user = (User)objectInputStream.readObject();
System.out.println(JSONObject.toJSONString(user));
objectInputStream.close();
/**
* 对象
*/
class User implements Serializable{
private static final long serialVersionUID = 1L;
private String name;
public User(String name) {this.name = name;}
public String getName() {return this.name;}
}
转换流
概述
转换流是将字节和字符互相转换
- InputStreamReader
- OutputStreamRead
数据流向
示例
/**
* 转换输出流
*/
InputStreamReader inputStreamReader = new InputStreamReader(new FileInputStream("./file"));
int len;
while ((len = inputStreamReader.read()) != -1) {System.out.println((char) len);
}
/**
* 转换输入流
*/
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(new FileOutputStream("./file"));
outputStreamWriter.write("helloio");
outputStreamWriter.flush();
outputStreamWriter.close();