一、StringReader类的用法
1.根据流的使用规律:
- 明确读操作还是写操作:
用来读取一个String类型的字符串 - 明确操作基类(操作字节还是字符)
StringReader操作的是字符 - 明确操作的具体类(具体介质)
操作的具体介质:内存
2.既然都有了Reader,还要StringReader干什么?(应用场景)
有的情况下使用一个Reader来作为参数传递参数,但数据源又仅仅是一个String类型数据,无需从文件中写出,那么此时就可以用到StringReader。
3.具体方法应用示例
方法介绍:
- 判断此流是否支持markSupported()
- 标记流中的当前位置 mark()
- 读取单个字符 read(),如果读到流的末尾,返回-1,操作不当会抛出IO异常
- 将字符读入数组的某一部分 read(char[] cbuf,int off,int len),off存开始在数组中存储数据的起点,操作不当会抛出IO异常
- 判断此流是否已经准备好用于读取 boolean ready()
- 将该流重置为最新的标记,如果从未标记,则将其重置到字符串开头 reset()→将当前读取位置回退到流中的上次标记的位置
- 跳过流中指定数量的字符返回跳过的字符数skip(long n),操作不当会抛出IO异常
- 关闭流close()
public class F_StringReader326 {
public static String Reader(String cont) throws IOException {
//只有一个构造方法 :StringReader(String s) 创建一个新字符串
StringReader stringReader = new StringReader(cont);
//判断是否准备好用于读取
System.out.println(stringReader.ready());
//判断是否支持标记
boolean a = stringReader.markSupported();
System.out.println(a);
stringReader.mark(2); //标记位置
stringReader.skip(3);//跳过3个字符
//读取单个字符
String s1 = "";
int s;
while ((s = stringReader.read())!= -1){
s1 += (char)s; //将读取的字符串进行拼接
}
//批量读取:读取到char类型的数组中,第二个参数为数组中的偏移量,第三个参数为读取的长度
char[] ch = new char[10];
stringReader.read(ch,0,5);
stringReader.close();//关闭流 ,在关闭之后调用流的其他方法时会抛出IO异常
return s1;
}
public static void main(String[] args) throws IOException {
System.out.println(Reader("StringReader"));//打印读取的数据
}
}
二、源码剖析
- 首先先看一下整体结构
- 属性分析:
private String str; //内置了一个String类型的变量。用于存储读取的内容
private int length; //读取数据的长度
private int next = 0; //下一个要读取的位置,相当于指针
private int mark = 0; //标记的位置
- 构造方法:
public StringReader(String s) { //通过String类型的s来初始化内置的str和length属性
this.str = s;
this.length = s.length();
}
- 具体方法介绍:
- ensureOpen() 判断当前流是否属于开启状态
private void ensureOpen() throws IOException {
if (str == null) //如果当前字符串为null,则抛出IO异常,声明流已经关闭!
throw new IOException("Stream closed");
}
- read() 读取一个字符
public int read() throws IOException {
synchronized (lock) { //有安全锁修饰,表示线程安全
ensureOpen(); //判断流是否处于开启状态
if (next >= length) //如果下一次读取的位置比数据的总长度还大,表示已经读到结尾
return -1;
return str.charAt(next++); //调用String的charAt方法,读取下一个数据
}
}
String的charAt方法:首先对下标进行安全校验,下标合格则直接返回当前下标所对应的字符
- read(char cbuf[], int off, int len) 批量读取数据
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;
//调用数学方法,求最小值:判断length-next和len的大小,
//(length-next:比如共有10个数据,参数给定一次读取6个,剩下4个数据,就是length-next。就可以将整个数据读完)
int n = Math.min(length - next, len);
//使用String类的getChars方法,将指定str从next到next+n位置的数据拷贝到传入cbuf中,拷贝位置从off开始
str.getChars(next, next + n, cbuf, off);
next += n;//更新下一次读取的位置
return n; //返回n(实际读取的数据长度)
}
}
String类的getChar()方法:底层调用了System.arrayCopy()方法进行批量拷贝
- skip(long ns) 跳过指定个数的字符
public long skip(long ns) throws IOException {
synchronized (lock) {
ensureOpen();
if (next >= length)
return 0;
//和批量读取的原理一样,为了防止最后的数据不够规定的长度。
long n = Math.min(length - next, ns);
//处理传入的ns为负数的情况,上一步如果ns为负数的话此时的n是ns
n = Math.max(-next, n);
next += n;//更新下一次读取的位置
return n;
}
}
- ready() 判断是否准备好用于读取
public boolean ready() throws IOException {
synchronized (lock) {
ensureOpen(); //判断流是否打开
return true;
}
}
- markSupported() 判断是否支持mark(标记)
public boolean markSupported() {
return true;
}
- mark() 标记位置,和reset方法联合使用,可以回退上一次标记的位置
public void mark(int readAheadLimit) throws IOException {
if (readAheadLimit < 0){ //参数校验
throw new IllegalArgumentException("Read-ahead limit < 0");
}
synchronized (lock) {
ensureOpen();
mark = next; //将当前读取的位置用mark标记
}
}
- reset() 重置标记
public void reset() throws IOException {
synchronized (lock) {
ensureOpen();
next = mark; //将mark标记的位置赋给next,将当前位置改为为上一次读取的位置
}
}
- close() 关闭流
public void close() {
str = null; //直接将内置的字符串置为null
}