介绍:
Writer是字符输出流的基类,Reader是字符输入流的基类。这两个类的实现类基本都是成对存在的,下面就成对讲解。
字节流与字符流区别请查看:字节流和字符流的区别
体系图:
对常见实现类API的讲解:
OutputStreamWriter和InputStreamReader:
这两个类是转换流类,OutputStreamWriter可以将字节输出流转换成字符输出流,InputStreamReader可以将字节输入流转换成字符输入流。
//以下是InputStreamReader类的API解析:
//构造器:
//传入一个字节输入流,就是把该输入流转换成字符输入流。
public InputStreamReader(InputStream in)
//传入一个字符输入流并编码编码规则。
public InputStreamReader(InputStream in, String charsetName)
//传入一个字符输入流并编码编码规则。
public InputStreamReader(InputStream in, Charset cs)
//获取该流是否已经准备好可以读取,当缓冲区数据不为空或者底层有数据可供读取
//(也就是没有达到文件尾)时返回true。
public boolean java.io.InputStreamReader.ready() throws java.io.IOException
/**
* 从缓冲区读取若干数据到char数组中。
* 参数:
* 1. char[]数组 缓冲区的数据会读取到该数组里面。
* 2. int : 读取到char[]数组的偏移量。
* 3. int : 要读取的数据的长度。
* 返回的是读取到的数据长度,-1表示文件尾。
*/
public int java.io.InputStreamReader.read(char[],int,int) throws java.io.IOException
/**
* 从缓冲区中读取一个字符。
* 返回值是读取到的数据,-1表示到达文件尾。
*/
public int java.io.InputStreamReader.read() throws java.io.IOException
//关闭流
public void java.io.InputStreamReader.close() throws java.io.IOException
//获取编码规则名称
public java.lang.String java.io.InputStreamReader.getEncoding()
//读取若干个字符到char数组中,若干个是指 char数组长度与缓冲区大小取小值。
public int java.io.Reader.read(char[]) throws java.io.IOException
//读取数据到charBuffer中。
public int java.io.Reader.read(java.nio.CharBuffer) throws java.io.IOException
//这个方法不可用,直接抛异常。
public void java.io.Reader.mark(int) throws java.io.IOException
//是否支持mark,总是返回false。
public boolean java.io.Reader.markSupported()
//这个方法不可用,直接抛异常。
public void java.io.Reader.reset() throws java.io.IOException
//过滤一定长度的数据,底层是使游标移动一定长度。
public long java.io.Reader.skip(long) throws java.io.IOException
//以下是OutputStreamWriter类的API解析:
//构造器:
//传入一个字节输出流,把这个字节输出流转成字节输入流
public OutputStreamWriter(OutputStream out)
//传入一个字节输出流,并指定编码规则
public OutputStreamWriter(OutputStream out, String charsetName)
//传入一个字节输出流,并指定编码规则
public OutputStreamWriter(OutputStream out, Charset cs)
/**
*把char数组的数据写到缓冲区中。
*参数:
*1. char[] : 要写到缓冲区的字符数组。
*2. int off: char[] 的偏移量。
*3. int len: 写入数据的长度,写入的数据时chars[off]开始len长度的数据。
*/
public void java.io.OutputStreamWriter.write(char[],int,int) throws java.io.IOException
//把一个字符数据写到缓冲区,参数就是要写入的字符数据。
public void java.io.OutputStreamWriter.write(int) throws java.io.IOException
/**
* 把一个String对象写入到缓冲区(会转换成char数组的)
* 参数:
* 1. String : 要写入的字符串。
* 2. off:偏移量
* 3. len场地
*/
public void java.io.OutputStreamWriter.write(java.lang.String,int,int) throws java.io.IOException
//关闭流,伴随刷盘行为
public void java.io.OutputStreamWriter.close() throws java.io.IOException
//强制把缓冲区数据刷到存储设备上
public void java.io.OutputStreamWriter.flush() throws java.io.IOException
//获取该字符流的编码规则
public java.lang.String java.io.OutputStreamWriter.getEncoding()
//返回自身writer,可以链式调用writer.append("abcd").append("efg")
//进行向缓冲区添加字符
public java.io.Writer java.io.Writer.append(java.lang.CharSequence) throws java.io.IOException
//链式添加,可以指定开始位置和结束位置。
public java.io.Writer
java.io.Writer.append(java.lang.CharSequence,int,int) throws java.io.IOException
//链式添加一个字符
public java.io.Writer java.io.Writer.append(char) throws java.io.IOException
//链式添加
public java.io.Writer java.io.Writer.append(java.lang.CharSequence) throws java.io.IOException
//向缓冲区写入多个字符
public void java.io.Writer.write(char[]) throws java.io.IOException
//向缓冲区写入字符
public void java.io.Writer.write(java.lang.String) throws java.io.IOException
以下是使用OutputStreamWriter 和 InputStreamReader将一个文件字母转换成大写,并生成另一个文件的demo:
public class StreamConvertToCharDemo {
public static void main(String[] args) {
try (InputStreamReader reader = new InputStreamReader(new FileInputStream("1.txt"));
OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream("3.txt"))){
int len = -1;
while ((len = reader.read()) != -1){
writer.write(Character.toUpperCase(len));
}
}catch (IOException e){
}
}
}
运行结果:
CharArrayWriter和CharArrayReader:
这个是字符数组的输出流和输入流,是纯内存的操作,不涉及到磁盘。主要用途是在要创建临时文件,数据网络传输等情况下可以避免访问磁盘,直接在内存操作,提高运行效率。该类也不涉及底层操作,纯java代码。这里直接就用源码来讲吧。
CharArrayReader:
public class CharArrayReader extends Reader {
//缓冲区
protected char buf[];
//游标,也就是buf[]下一个要读取的字符的下标值。
protected int pos;
//标记为,重置时游标会重置到该值
protected int markedPos = 0;
//数据长度
protected int count;
//指定字符数组作为缓冲区
public CharArrayReader(char buf[]) {
this.buf = buf;
this.pos = 0;
this.count = buf.length;
}
//指定buf字符缓冲区,并且指定传进来的字符数组的偏移量和长度
public CharArrayReader(char buf[], int offset, int length) {
//参数校验
if ((offset < 0) || (offset > buf.length) || (length < 0) ||
((offset + length) < 0)) {
throw new IllegalArgumentException();
}
this.buf = buf;
//将游标设为指定的偏移量
this.pos = offset;
//取偏移量+长度 和 传进来的buf数组的长度的较小值作为缓冲区的数据长度。
this.count = Math.min(offset + length, buf.length);
//将重置标记置为偏移量。
this.markedPos = offset;
}
//判断流是否已经打开了,缓冲区为空表示未打开。抛出IOException
private void ensureOpen() throws IOException {
if (buf == null)
throw new IOException("Stream closed");
}
//同步方法,读取一个字符
public int read() throws IOException {
synchronized (lock) {
//验证流是否已经打开
ensureOpen();
//判断是否到达文件尾
if (pos >= count)
return -1;
else
//返回
return buf[pos++];
}
}
//读取多个字符到 b[]字符数组中,指定偏移量和读取长度。
public int read(char b[], int off, int len) throws IOException {
//同步方法
synchronized (lock) {
ensureOpen();
//边界检查
if ((off < 0) || (off > b.length) || (len < 0) ||
((off + len) > b.length) || ((off + len) < 0)) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
//读取长度为0时也是直接返回0
return 0;
}
//判断是否到达文件尾
if (pos >= count) {
return -1;
}
//如果读取的长度大于可读取长度,那就把读取长度设置为可读取长度。
if (pos + len > count) {
len = count - pos;
}
if (len <= 0) {
return 0;
}
//进行复制
System.arraycopy(buf, pos, b, off, len);
pos += len;
return len;
}
}
//跳过一定长度的数据,同步方法。
public long skip(long n) throws IOException {
synchronized (lock) {
ensureOpen();
//如果过滤的长度大于可读取长度,那就把过滤长度设置为可读取长度。
if (pos + n > count) {
n = count - pos;
}
if (n < 0) {
return 0;
}
//直接游标+n过滤掉n个数据。
pos += n;
return n;
}
}
//判断是否可用,未到达文件尾就返回true。
public boolean ready() throws IOException {
synchronized (lock) {
ensureOpen();
return (count - pos) > 0;
}
}
//是否支持标记。总是true支持。
public boolean markSupported() {
return true;
}
//标记当前游标,参数无作用。
public void mark(int readAheadLimit) throws IOException {
synchronized (lock) {
ensureOpen();
markedPos = pos;
}
}
//重置游标到markedPos标记处。
public void reset() throws IOException {
synchronized (lock) {
ensureOpen();
pos = markedPos;
}
}
//关闭流。将close置空。
public void close() {
buf = null;
}
}
CharArrayWriter:
public
class CharArrayWriter extends Writer {
//缓冲区
protected char buf[];
//缓冲区数据长度
protected int count;
//默认构造器,默认缓冲区大小为32
public CharArrayWriter() {
this(32);
}
//指定缓冲区大小的构造器
public CharArrayWriter(int initialSize) {
if (initialSize < 0) {
throw new IllegalArgumentException("Negative initial size: "
+ initialSize);
}
buf = new char[initialSize];
}
//往缓冲区中写入一个字符
public void write(int c) {
synchronized (lock) {
int newcount = count + 1;
if (newcount > buf.length) {
//两倍扩容或者扩容到newcount 大小,具体看哪个大,取大的那个。
buf = Arrays.copyOf(buf, Math.max(buf.length << 1, newcount));
}
buf[count] = (char)c;
count = newcount;
}
}
/**
* 往缓冲区写入任意个字符
* @param c 要写入的数据
* @param off c字符数组的偏移量
* @param len 要写入的数据长度
*/
public void write(char c[], int off, int len) {
if ((off < 0) || (off > c.length) || (len < 0) ||
((off + len) > c.length) || ((off + len) < 0)) {
//边界检查
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return;
}
synchronized (lock) {
int newcount = count + len;
if (newcount > buf.length) {
//两倍扩容或者扩容到newcount 大小,具体看哪个大,取大的那个。
buf = Arrays.copyOf(buf, Math.max(buf.length << 1, newcount));
}
//将c数组中的指定数据复制到缓冲区中。
System.arraycopy(c, off, buf, count, len);
//更新缓冲区大小
count = newcount;
}
}
/**
* 将字符串的一部分写入的缓冲区中,由off和len决定
* @param 要写入的字符串
* @param 偏移量
* @param 长度
*/
public void write(String str, int off, int len) {
synchronized (lock) {
int newcount = count + len;
if (newcount > buf.length) {
buf = Arrays.copyOf(buf, Math.max(buf.length << 1, newcount));
}
str.getChars(off, off + len, buf, count);
count = newcount;
}
}
/**
* 将缓冲区数据写入到其他字符输出流中
*
* @param out 要写入的字符输出流
*
*/
public void writeTo(Writer out) throws IOException {
synchronized (lock) {
out.write(buf, 0, count);
}
}
/**
* 链式追加缓冲区,writer.append("dsds").append("ddd"),
* 与StringBuilder的append方法类似。
*/
public CharArrayWriter append(CharSequence csq) {
String s = (csq == null ? "null" : csq.toString());
write(s, 0, s.length());
return this;
}
/**
* 链式追加到缓冲区,可以指定追加内容的范围。
*/
public CharArrayWriter append(CharSequence csq, int start, int end) {
String s = (csq == null ? "null" : csq).subSequence(start, end).toString();
write(s, 0, s.length());
return this;
}
/**
* 链式追加一个字符到缓冲区
*/
public CharArrayWriter append(char c) {
write(c);
return this;
}
/**
*重置缓冲区,实际上就是把缓冲区大小count 置0,重新写入。
*/
public void reset() {
count = 0;
}
/**
* 返回缓冲区char数组。但不是返回缓冲区,而是将缓冲区0-count的数据返
* 回
*/
public char toCharArray()[] {
synchronized (lock) {
return Arrays.copyOf(buf, count);
}
}
/**
* 返回缓冲区大小
*/
public int size() {
return count;
}
/**
* 将缓冲区字符转化为String对象返回
*/
public String toString() {
synchronized (lock) {
return new String(buf, 0, count);
}
}
/**
* 刷盘,纯内存操作,空方法
*/
public void flush() { }
/**
* 关闭流,空方法。
*/
public void close() { }
}
FilterWriter和FilterReader:
FilterWriter是字符输出流包装器的基类,FilterReader是字符输入流的包装器的基类,这两个是抽象类,对流进行包装的类可以继承该类。这两个类可以看成是辅助类。
FileWriter和FileReader:
FileWriter和FileReader是对磁盘文件操作的字符输出流和字符输入流,连通着磁盘与内存。
FileWriter继承自OutputStreamWriter,FileReader继承自InputStreamReader,除了写了一些构造器外,剩下的所有方法都没有重写,而是直接复用父类的方法,所以下面就讲讲构造器,具体api查看上面的OutputStreamWriter和InputStreamWriter即可。
以下是FileReader源码:
public class FileReader extends InputStreamReader {
//参数传入的是文件路径
public FileReader(String fileName) throws FileNotFoundException {
//也是调用父类的构造器,跟 new InputStreamReader(new FileInputStream(fileName)) 没区别。
super(new FileInputStream(fileName));
}
//直接传入文件
public FileReader(File file) throws FileNotFoundException {
super(new FileInputStream(file));
}
//传入FileDescriptor 对象。
public FileReader(FileDescriptor fd) {
super(new FileInputStream(fd));
}
}
以下是FileWriter的源码:
public class FileWriter extends OutputStreamWriter {
//文件路径
public FileWriter(String fileName) throws IOException {
super(new FileOutputStream(fileName));
}
//append表示是否进行追加还是重写文件
public FileWriter(String fileName, boolean append) throws IOException {
super(new FileOutputStream(fileName, append));
}
//直接传入文件
public FileWriter(File file) throws IOException {
super(new FileOutputStream(file));
}
//
public FileWriter(File file, boolean append) throws IOException {
super(new FileOutputStream(file, append));
}
public FileWriter(FileDescriptor fd) {
super(new FileOutputStream(fd));
}
}
BufferedWriter和BufferedReader:
BufferedWriter是Writer类的装饰器,BufferedReader是Reader类的装饰器,添加了缓冲区功能,提高读写的效率。
//写一个字符数组到缓冲区中,可以设置偏移量和要写入长度
public void java.io.BufferedWriter.write(char[],int,int) throws java.io.IOException
//写一个字符串到缓冲区中,可以设置偏移量和要写入长度
public void
java.io.BufferedWriter.write(java.lang.String,int,int) throws java.io.IOException
//写一个字符到缓冲区
public void java.io.BufferedWriter.write(int) throws java.io.IOException
//关闭流,伴随刷盘操作
public void java.io.BufferedWriter.close() throws java.io.IOException
//刷盘,把缓冲区的数据强制刷到磁盘或其他设备
public void java.io.BufferedWriter.flush() throws java.io.IOException
//新建一行,其实就是写一个换行符到缓冲区中。
public void java.io.BufferedWriter.newLine() throws java.io.IOException
//链式添加,与StringBuilder类似。把CharSequence写到缓冲区,并可以指定开始和结束位置。
public java.io.Writer java.io.Writer.append(java.lang.CharSequence,int,int) throws java.io.IOException
//链式添加,把一个字符添加到缓冲区。
public java.io.Writer java.io.Writer.append(char) throws java.io.IOException
//链式添加。把CharSequence写到缓冲区。
public java.io.Writer java.io.Writer.append(java.lang.CharSequence) throws java.io.IOException
//把一个字符数组添加到缓冲区中
public void java.io.Writer.write(char[]) throws java.io.IOException
//把一个字符串添加到缓冲区中
public void java.io.Writer.write(java.lang.String) throws java.io.IOException
//判断流是否已经关闭
public boolean java.io.BufferedReader.ready() throws java.io.IOException
//从缓冲区读取一个字符。
public int java.io.BufferedReader.read() throws java.io.IOException
//从缓冲区读取若干个字符到char[] 数组中。
//第二个int参数:偏移量,读取缓冲区的数据到char数组的从该下标开始。
//第三个int参数:len ,读取数据的长度。
public int java.io.BufferedReader.read(char[],int,int) throws java.io.IOException
//关闭流
public void java.io.BufferedReader.close() throws java.io.IOException
//读取一行数据
//如果到达文件尾,则返回null。
public java.lang.String java.io.BufferedReader.readLine() throws java.io.IOException
//过滤掉一定字符长度的数据
public long java.io.BufferedReader.skip(long) throws java.io.IOException
//从缓冲区读取数据到char[]数组中。返回读取到的长度,文件尾-1.
public int java.io.Reader.read(char[]) throws java.io.IOException
//小demo
public class BufferedWriterReaderDemo {
public static void main(String[] args) {
try (BufferedReader reader = new BufferedReader(new FileReader("1.txt"));
BufferedWriter writer = new BufferedWriter(new FileWriter("5.txt"))){
String s = null;
while ((s = reader.readLine())!=null){
writer.write(s);
}
}catch (IOException e){
}
}
}
StringReader和StreamWriter:
这个类似于CharArrayWriter和CharArrayReader,不过StringReader和StreamWriter是对字符串的操作,CharArrayWriter和StreamWriter是对字符的操作。是纯内存的操作,不涉及到磁盘。主要用途是在要创建临时文件,数据网络传输等可以避免访问磁盘,直接在内存操作,提高运行效率。该类也不涉及底层操作,纯java代码。这里直接就用源码来讲:
StringWriter
public class StringWriter extends Writer {
//底层是StringBuffer。也就是说是线程安全的,因为写入是添加的,所以避免
//String对象加号添加,提高效率。
private StringBuffer buf;
public StringWriter() {
buf = new StringBuffer();
lock = buf;
}
//可以设定StringBuffer的初始值
public StringWriter(int initialSize) {
if (initialSize < 0) {
throw new IllegalArgumentException("Negative buffer size");
}
buf = new StringBuffer(initialSize);
lock = buf;
}
//往缓冲区中添加一个字符
public void write(int c) {
buf.append((char) c);
}
//往缓冲区中添加一个字符数组,并可以指定偏移量和长度
public void write(char cbuf[], int off, int len) {
if ((off < 0) || (off > cbuf.length) || (len < 0) ||
((off + len) > cbuf.length) || ((off + len) < 0)) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return;
}
buf.append(cbuf, off, len);
}
//往缓冲区中添加一个字符串
public void write(String str) {
buf.append(str);
}
//往缓冲区中添加一个字符串,并可以指定偏移量和长度
public void write(String str, int off, int len) {
buf.append(str.substring(off, off + len));
}
//链式添加
public StringWriter append(CharSequence csq) {
if (csq == null)
write("null");
else
write(csq.toString());
return this;
}
//链式添加,并可以指定开始和结束的偏移量
public StringWriter append(CharSequence csq, int start, int end) {
CharSequence cs = (csq == null ? "null" : csq);
write(cs.subSequence(start, end).toString());
return this;
}
//链式添加一个字符
public StringWriter append(char c) {
write(c);
return this;
}
//获取缓冲区并转换成字符串
public String toString() {
return buf.toString();
}
//换区缓冲区
public StringBuffer getBuffer() {
return buf;
}
//刷盘,因为是纯内纯操作,所以空方法
public void flush() {
}
//关闭流,空方法
public void close() throws IOException {
}
}
StringReader:
public class StringReader extends Reader {
//底层
private String str;
//长度
private int length;
//下一个读取的字符的游标
private int next = 0;
//重置时重置标记
private int mark = 0;
/**
* 传入一个字符串
*/
public StringReader(String s) {
this.str = s;
this.length = s.length();
}
//是否已经打开,也就是底层str为空时就抛出异常。
private void ensureOpen() throws IOException {
if (str == null)
throw new IOException("Stream closed");
}
//读取一个字符
public int read() throws IOException {
synchronized (lock) {
ensureOpen();
if (next >= length)
return -1;
//读取下标字符
return str.charAt(next++);
}
}
/**
* 读取字符串中的字符到字符数组中,并制定偏移量和读取数据长度,不是缓冲区的
* 偏移量是读取到cbuf的偏移量。
*/
public int read(char cbuf[], int off, int len) throws IOException {
synchronized (lock) {
ensureOpen();
if ((off < 0) || (off > cbuf.length) || (len < 0) ||
((off + len) > cbuf.length) || ((off + len) < 0)) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
}
if (next >= length)
return -1;
int n = Math.min(length - next, len);
str.getChars(next, next + n, cbuf, off);
next += n;
return n;
}
}
/**
* 跳过一定长度数据,返回跳过的长度。
*/
public long skip(long ns) throws IOException {
synchronized (lock) {
ensureOpen();
//达到尾,返回0.
if (next >= length)
return 0;
// Bound skip by beginning and end of the source
//取可读取长度和期望跳过长度的较小值
long n = Math.min(length - next, ns);
n = Math.max(-next, n);
next += n;
return n;
}
}
/**
* 流是否已经准备好
*/
public boolean ready() throws IOException {
synchronized (lock) {
ensureOpen();
return true;
}
}
//重置游标到mark值
public void reset() throws IOException {
synchronized (lock) {
ensureOpen();
next = mark;
}
}
/**
* 关闭流,把缓冲区置空
*/
public void close() {
str = null;
}
}