高效易用的okio(二)

高效易用的okio(二)在上篇文章中,我们知道了一些JavaIO的概念,也了解了okio的用法,现在我们来分析一下源码Okio我们回到上篇的代码:Sourcesource=Okio.source(newFile(mPath));Stringread=Okio.buffer(source).readString(Charset.forName("utf-8"));显而易见Okio…

大家好,又见面了,我是你们的朋友全栈君。

在上篇文章中,我们知道了一些 Java IO 的概念,也了解了 okio 的用法,现在我们来分析一下源码

Okio

我们回到上篇的代码:

 Source source = Okio.source(new File(mPath));
 String read = Okio.buffer(source).readString(Charset.forName("utf-8"));

显而易见 Okio 是个入口类,里面方法如下:

在这里插入图片描述

Okio 里面的代码过一遍的话,就会发现其实 Okio 就是个工厂类,它的主要工作就是将 OutputStreamInputStream 转成 SinkSource

在这里插入图片描述

转换方法也是十分简单粗暴,就是直接 new 出来,这里就只贴 Source 的生成方法:

private static Source source(final InputStream in, final Timeout timeout) { 

if (in == null) throw new IllegalArgumentException("in == null");
if (timeout == null) throw new IllegalArgumentException("timeout == null");
return new Source() { 

@Override
public long read(Buffer sink, long byteCount) throws IOException { 

if (byteCount < 0)
throw new IllegalArgumentException("byteCount < 0: " + byteCount);
if (byteCount == 0) return 0;
try { 

timeout.throwIfReached();
Segment tail = sink.writableSegment(1);
int maxToCopy = (int) Math.min(byteCount, Segment.SIZE - tail.limit);
int bytesRead = in.read(tail.data, tail.limit, maxToCopy);
if (bytesRead == -1) return -1;
tail.limit += bytesRead;
sink.size += bytesRead;
return bytesRead;
} catch (AssertionError e) { 

if (isAndroidGetsocknameError(e)) throw new IOException(e);
throw e;
}
}
@Override
public void close() throws IOException { 

in.close();
}
@Override
public Timeout timeout() { 

return timeout;
}
@Override
public String toString() { 

return "source(" + in + ")";
}
};
}

Sink 和 Source

okio 中 ,SinkSource 分别是用来替代 Java 的 OutputStreamInputStream ,我们可以理解为

Sink 就是 输出流,Source 就是输入流,它们基本都是对称的,因此这里就继续用 Source 来进行分析了

先来看下 Source 的代码:

public interface Source extends Closeable { 

/** * 读取流里面的数据 * 返回读取的字节数,如果是-1则说明已经读取完成 */
long read(Buffer sink, long byteCount) throws IOException;
/** 超时机制 */
Timeout timeout();
/** * 关闭流并释放流所拥有的资源 */
@Override void close() throws IOException;
}

Source 中只包括了一些简单的方法,不过有一个需要注意的 timeout() ,这个是超时机制,后面再讲

我们来看下它的子类有那些:

在这里插入图片描述

可以看到还是有不少子类的,我们先来看一些熟悉的吧,BufferedSourceBuffer 都是上篇文章出现过的,那就先从这两个开始吧

BufferedSource

BufferedSource 接口在 Source 接口的基础上 多了下面这一堆方法:

在这里插入图片描述

BufferedSource 增加了许多读的方法,但是它依然还是个接口,它的真正实现类是 RealBufferedSource

这一点,可以在 okio 这个类里面找到依据:

在这里插入图片描述

bufferokio 的入口方法,它的作用就是生成一个 RealBufferedSource

###RealBufferedSource

虽然 RealBufferedSource 这个类名带有 real 这个单词,但是实际上它只是一个代理类和 Source 的一个装饰类,可以从下面的代码看出(下面的 SourceOkio 中生成的):

在这里插入图片描述

基本上,在 RealBufferedSource 实现了 BufferedSource 接口的方法,实际上都是调用了 Buffer 类中的对应方法,因此 RealBufferedSource 就仅是 Buffer 的一个代理类而已

BufferedSourceRealBufferedSourceBuffer 的关系如下图

在这里插入图片描述

Buffer

查看这个 Buffer 类,会发现它还同时继承了 BufferedSink ,也就是不管是 Source 还是 Sink ,最终都是要转换为 Buffer 的:

public final class Buffer implements BufferedSource, BufferedSink, Cloneable { 

.......
//全局对象
Segment head;
//全局对象,用于记录流的大小,下面会用到的
long size;
}

现在我们知道了 okio 最终都是要调用 Buffer 类里面方法,那我们先来看 RealBufferedSource 里面的 readString 方法吧,这个就是我们上篇调用的方法的底层实现:

String read = Okio.buffer(source).readString(Charset.forName("utf-8"));
........
//RealBufferedSource 最终调用的 buffer 类的方法
@Override
public String readString(Charset charset) throws IOException { 

//判断编码对象是否为null
if (charset == null) throw new IllegalArgumentException("charset == null");
//写入内存缓冲区中
buffer.writeAll(source);
//从内存内存缓冲区中读取数据
return buffer.readString(charset);
}

我们先去看下 buffer.writeAll(source) 方法吧,它到底是干了什么:

@Override
public long writeAll(Source source) throws IOException { 

if (source == null) throw new IllegalArgumentException("source == null");
long totalBytesRead = 0;
//source.read(this,Segment.SIZE),这里是获取sink来进入写入操作,写入大小为Segment.SIZE
for (long readCount; (readCount = source.read(this, Segment.SIZE)) != -1; ) { 

totalBytesRead += readCount;
}
return totalBytesRead;
}

这里的 sourceOkio 类中的生成的,我们再回顾一下具体的方法代码:

@Override
public long read(Buffer sink, long byteCount) throws IOException { 

if (byteCount < 0)
throw new IllegalArgumentException("byteCount < 0: " + byteCount);
if (byteCount == 0) return 0;
try{ 

timeout.throwIfReached();
//得到一个Segment对象
Segment tail = sink.writableSegment(1);
//获取InputStream每次读取数据的长度
int maxToCopy = (int) Math.min(byteCount, Segment.SIZE - tail.limit);
//InputStream.read(byte b[], int off, int len),熟悉的读取方法
//tail.data,这个是数组就是一个内存缓冲区
int bytesRead = in.read(tail.data, tail.limit, maxToCopy);
//-1表示流读取完成了
if (bytesRead == -1) return -1;
tail.limit += bytesRead;
//记录读取完成的流的数据大小
sink.size += bytesRead;
return bytesRead;
} catch (AssertionError e) { 

if (isAndroidGetsocknameError(e)) throw new IOException(e);
throw e;
}
}

接着看下 buffer.readString(charset) 方法:

@Override
public String readString(Charset charset) { 

try { 

//又看到全局的size,这里已经记录下刚才读取的流的打下了
return readString(size, charset);
} catch (EOFException e) { 

throw new AssertionError(e);
}
}
@Override
public String readString(long byteCount, Charset charset) throws EOFException { 

checkOffsetAndCount(size, 0, byteCount);
if (charset == null) throw new IllegalArgumentException("charset == null");
if (byteCount > Integer.MAX_VALUE) { 

throw new IllegalArgumentException("byteCount > Integer.MAX_VALUE: " + byteCount);
}
if (byteCount == 0) return "";
//获取全局的Segment对象
Segment s = head;
if (s.pos + byteCount > s.limit) { 

//如果内存缓冲区的流的长度超过了限制,那么使用readByteArray方法
return new String(readByteArray(byteCount), charset);
}
//将内存缓冲区的流(二进制数组)转为String
String result = new String(s.data, s.pos, (int) byteCount, charset);
s.pos += byteCount;
size -= byteCount;
//回收Segment
if (s.pos == s.limit) { 

head = s.pop();
SegmentPool.recycle(s);
}
//得到文本内容
return result;
}

我们看到如果超过了某个限制,会调用其他的方法,我们再追下去:

@Override
public byte[] readByteArray(long byteCount) throws EOFException { 

checkOffsetAndCount(size, 0, byteCount);
if (byteCount > Integer.MAX_VALUE) { 

throw new IllegalArgumentException("byteCount > Integer.MAX_VALUE: " + byteCount);
}
//生成和流一样大小的byte数组 
byte[] result = new byte[(int) byteCount];
//读取流并填入byte数组中 
readFully(result);
return result;
}
............
@Override
public void readFully(byte[] sink) throws EOFException { 

int offset = 0;
while (offset < sink.length) { 

//循环去将内存缓冲区的内存填入byte数组
int read = read(sink, offset, sink.length - offset);
if (read == -1) throw new EOFException();
offset += read;
}
}
.....
@Override
public int read(byte[] sink, int offset, int byteCount) { 

checkOffsetAndCount(sink.length, offset, byteCount);
Segment s = head;
if (s == null) return -1;
int toCopy = Math.min(byteCount, s.limit - s.pos);
//数组的内容复制方法
//将Segment中的缓存的流的数据复制到byte数组中
System.arraycopy(s.data, s.pos, sink, offset, toCopy);
s.pos += toCopy;
size -= toCopy;
if (s.pos == s.limit) { 

head = s.pop();
SegmentPool.recycle(s);
}
return toCopy;
}

整个 Okio 读取本地文本的数据的流程就是以上的过程了,这里我们看到了一个新的内容:Segment

它是一个内存的数据缓冲区,我们将会在下篇去分析

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/136369.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(2)
blank

相关推荐

  • java全局变量和局部变量的区别「建议收藏」

    java全局变量和局部变量的区别「建议收藏」java全局变量和局部变量全局变量全局变量又称实例变量,属性,成员变量。位置定义在类中,属于类中的一个成员作用范围因为全局变量是定义在类中的,所以可以使用四种访问权限控制修饰符修饰,但是不论使用哪种,全局变量在当前类中的每一个方法中都可以被访问到,(static修饰的静态方法只能访问static修饰的属性)默认值全局变量有默认值基本类型的实例变量:整型 :默认值为0浮点型 :默认值为0.0布尔型 :默认值为false字符型 :默认值为0或者‘\u0000’引用类型的实

  • 简单的批处理命令_bat批处理执行cmd命令

    简单的批处理命令_bat批处理执行cmd命令批处理命令简单教程转载来源:https://blog.csdn.net/Joker_N/article/details/89838719文章目录批处理命令简单教程一、常用命令简介1、echo命令2、rem命令3、pause命令4、call命令5、start命令6、goto命令7、set命令二、常用符号简介1、回显屏蔽@2、重定向1>;与>>3、重定向2<4、管道符号|5、转义符^6、逻辑命令符三、命令释义1、文件夹管理2、文件管理3、网络命令4、系统管理四

  • 查看redis版本命令_redis如何使用

    查看redis版本命令_redis如何使用Centos7查看redis版本redis安装成功后,查看redis版本命令:redis-server-V即可查看redis版本实际我们查看时都会遇到这个问题:redis-cli:commandnotfound(其实就和window电脑命令提示行中提示的:不是内部命令一个意思,配置环境变量即可使用)以上问题其实就是说明redis-server-V不是linux的全局命令,只需要我们做个软链接即可(类似于win电脑中的环境变量)软链接命令:ln-s/home/redis

  • MySQL Longtext字段优化记录(一)[通俗易懂]

    MySQL Longtext字段优化记录(一)[通俗易懂]Longtext优化记录

  • myeclipse中,项目上有个叉报错,文件没有错误[通俗易懂]

    myeclipse中,项目上有个叉报错,文件没有错误

  • Ubuntu “Failed to fetch”错误的解决方法

    Ubuntu “Failed to fetch”错误的解决方法设置妥当DNS服务器即可.$sudovim/etc/resolv.conf修改DNS:nameserver8.8.8.8nameserver8.8.4.4然后重启网络服务:sudo

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号