Okio基础

Okio

封装InputStream和OutputStream为Source和Sink,提供了一些列高效读写字节和字符的方法;

image

基本使用

  • 向File中写入数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    String name = "xiaoming";
    int age = 11;

    try {
    Okio.buffer(Okio.sink(file)).writeUtf8(name).writeInt(age).close();
    } catch (FileNotFoundException e) {
    e.printStackTrace();
    }catch (IOException ex){
    ex.printStackTrace();
    }
  • 文件拷贝

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    try {
    /**
    * 当同时存在Source和Sink时,不建议完全采用链式的调用,因为source、sink在结束时必须关闭;
    * 小心使用writeAll,因为writeAll并不会返回Sink,所以采用链式调用最后没法直接close;
    *
    * 对于压缩文件,如果在最后没有调用close将流正确关闭,则会损坏到压缩文件
    */
    // Okio.buffer(Okio.sink(destFile)).writeAll(Okio.buffer(Okio.source(srcFile)));
    BufferedSink sink = Okio.buffer(Okio.sink(new File(filePath)));

    BufferedSource source = Okio.buffer(Okio.source(in));

    sink.writeAll(source);

    sink.close();
    source.close();

    } catch (FileNotFoundException e) {
    e.printStackTrace();
    }catch (IOException ex){
    ex.printStackTrace();
    }

Source

  • 对应原生IO的InputStream,提供字节流数据,通过source,可以从网络、存储、内存等地方读取到数据流;

  • 提供了read方法,支持Sink读取数据到其Buffer缓存中;

  • 一般情况下,不要直接操作Source,而是操作BufferedSource,它提供了更丰富、高效的接口;

  • Source还提供了一个强大的skip方法(BufferedSource),能让我们跳过指定数量的字节后,再读字节流;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public interface Source extends Closeable {
/**
* Removes at least 1, and up to {@code byteCount} bytes from this and appends
* them to {@code sink}. Returns the number of bytes read, or -1 if this
* source is exhausted.
*/
// 将字节数据从source中移除,并添加到Sink中
long read(Buffer sink, long byteCount) throws IOException;

/** Returns the timeout for this source. */
Timeout timeout();

/**
* Closes this source and releases the resources held by this source. It is an
* error to read a closed source. It is safe to close a source more than once.
*/
@Override void close() throws IOException;
}

BufferedSource

带有缓存的Source接口,实际实现是RealBufferedSource;

Sink

  • 对应原生IO的OutputStream,提供了write方法,用来从Source缓存中读取缓存数据,写入到自己的
    Buffer缓存中;

  • 一般情况下,不要直接操作Sink,而是操作BufferedSink,它提供了更丰富、高效的接口;

  • Sink能够代替DataOutputStream(写入原生数据)、BufferedOutputStream(写入缓存数据)、
    OutputStreamWriter(数据流字符编码);

  • 在结束时,必须调用Sink的close方法,将缓存数据推送进目标中,并且释放调用持有的资源(在采用
    链式调用时,需要注意最后也需要关闭Sink);

通过:

1
Okio.buffer(Sink);

可以得到BufferedSink实例;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public interface Sink extends Closeable, Flushable {
/** Removes {@code byteCount} bytes from {@code source} and appends them to this. */
// 基础的write操作,该方法将指定数量的字节从source中移出,并写入自己的Buffer
void write(Buffer source, long byteCount) throws IOException;

/** Pushes all buffered bytes to their final destination. */
@Override void flush() throws IOException;

/** Returns the timeout for this sink. */
Timeout timeout();

/**
* Pushes all buffered bytes to their final destination and releases the
* resources held by this sink. It is an error to write a closed sink. It is
* safe to close a sink more than once.
*/
// Sink可以多次调用close
@Override void close() throws IOException;
}

BufferedSink

带有缓存的Sink接口,实际实现是BufferedSink类;

Segment

Segment字面的意思就是片段,okio将数据也就是Buffer分割成一块块的片段,内部维护者固定长度的
byte[]数组,同时segment拥有前面节点和后面节点,构成一个双向循环链表;

image

  • 采用双向链表的解构,每一个Segment都代表一个节点,并且保存了前后两个节点的指针,
    提供了pop(出栈)和push(入栈)方法;
  • 封装了一个byte数组,用来保存一小段缓存数据;
  • 多个Segment之间可能会分享数据流,在这种情况下的Segment,可能无法被回收和修改;
  • pop方法用来将当前Segment从当前链表中移除,push方法用来将一个Segment添加到当前Segment的
    后面;

属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/** The size of all segments in bytes. */
static final int SIZE = 8192;

/** Segments will be shared when doing so avoids {@code arraycopy()} of this many bytes. */
// 在调用split分割Segment时,如果要求分割的字节数超过了该值,则不会采用复制数组的方式进行
// 分割,而是采用共享的方式
static final int SHARE_MINIMUM = 1024;

final byte[] data;

// Segment保存的字节数组中,第一个可读的位置
/** The next byte of application data byte to read in this segment. */
int pos;

// Segment的剩余可写的字节数组的第一个位置
// 所以Segment实际保存的数据为pos ~ limit-1
/** The first byte of available data ready to be written to. */
int limit;

// Segment的字节数据是不是与别的Segment共享的,默认情况下是false
// 当在split时采用的是共享的方式,则分割出来的Segment该变量为true
/** True if other segments or byte strings use the same byte array. */
boolean shared;

// 当前Segment是否是其中字节数据的拥有者
/** True if this segment owns the byte array and can append to it, extending {@code limit}. */
boolean owner;

/** Next segment in a linked or circularly-linked list. */
Segment next;

/** Previous segment in a circularly-linked list. */
Segment prev;

pos和limit变量类似于两个指针,在Segment进行字节数据共享时,这两个变量用来指定当前Segment
有效的数据域(范围),这也是为什么,当多个Segment共享字节数据时,Segment无法被修改。如果修改了,
那么其他的Segment的有效数据域可能会受到影响;

  • sharedCopy

创建一个新的Segment,这个与创建它的Segment共享同一个字节数组数据,并且标明这个新的Segment不是
字节数组数据的拥有者;

1
2
3
4
Segment sharedCopy() {
shared = true;
return new Segment(data, pos, limit, true, false);
}

  • unsharedCopy

创建一个全新的Segment,并且这个Segment的数据与创建它的Segment是相同的;

1
2
3
Segment unsharedCopy() {
return new Segment(data.clone(), pos, limit, false, true);
}

  • pop

将当前Segment从链表中移除;

1
2
3
4
5
6
7
8
public @Nullable Segment pop() {
Segment result = next != this ? next : null;
prev.next = next;
next.prev = prev;
next = null;
prev = null;
return result;
}

  • push

将Segment添加到当前Segment之后;

1
2
3
4
5
6
7
public Segment push(Segment segment) {
segment.prev = this;
segment.next = next;
next.prev = segment;
next = segment;
return segment;
}

  • split

根据传入的字节数,分割Segment,将新分割出来的Segment添加到当前Segment之前;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public Segment split(int byteCount) {
if (byteCount <= 0 || byteCount > limit - pos) throw new IllegalArgumentException();
Segment prefix;

// We have two competing performance goals:
// - Avoid copying data. We accomplish this by sharing segments.
// - Avoid short shared segments. These are bad for performance because they are readonly and
// may lead to long chains of short segments.
// To balance these goals we only share segments when the copy will be large.
// 如果分割的字节数超过了1024,则采用共享字节数组的方式
if (byteCount >= SHARE_MINIMUM) {
prefix = sharedCopy();
} else {
// 否则直接创建一个新的Segment,并复制数据
prefix = SegmentPool.take();
System.arraycopy(data, pos, prefix.data, 0, byteCount);
}

// 如果采用共享数据的方式,data数据拥有者的pos最大不会超过limit
// 如果是共享数据的方式,分割后的两个Segment实际是通过pos和limit来分别指向同一个字节数组的
// 不同数据段
prefix.limit = prefix.pos + byteCount;
// 当前Segment的pos重新指向
pos += byteCount;
// 将分割出去的Segment放到前一个Segment的后面,也就是当前Segment的前面
prev.push(prefix);
return prefix;
}

  • compact

合并当前Segment和它之前的Segment为一个;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void compact() {
if (prev == this) throw new IllegalStateException();
if (!prev.owner) return; // Cannot compact: prev isn't writable.
int byteCount = limit - pos;
// 如果prev是与别的Segment共享字节数组的,则其只能使用从limit~SIZE的部分
// 否则,0~pos部分也是可以使用的,当然,一般情况下,pos是等于0的
int availableByteCount = SIZE - prev.limit + (prev.shared ? 0 : prev.pos);
if (byteCount > availableByteCount) return; // Cannot compact: not enough writable space.
// 开始合并到prev中
writeTo(prev, byteCount);
// 移除当前Segment
pop();
SegmentPool.recycle(this);
}

  • writeTo

将指定字节数的数据移到Sink中;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public void writeTo(Segment sink, int byteCount) {
if (!sink.owner) throw new IllegalArgumentException();
// sink从limit~SIZE的部分不足以放入当前Segment,则检查Sink的0~pos部分是否可以放入数据
if (sink.limit + byteCount > SIZE) {
// We can't fit byteCount bytes at the sink's current position. Shift sink first.
// 虽然Sink是其字节数组的拥有者,但是其字节数组一部分是与别人共享的,这部分共享的数据,
// 可能使用了0~pos的部分,所以这部分不能被修改
if (sink.shared) throw new IllegalArgumentException();
// 加上前面的pos部分也不足以将当前Segment数据移进Sink中
if (sink.limit + byteCount - sink.pos > SIZE) throw new IllegalArgumentException();
// 将sink的字节数组数据移到pos = 0的位置
System.arraycopy(sink.data, sink.pos, sink.data, 0, sink.limit - sink.pos);
sink.limit -= sink.pos;
sink.pos = 0;
}
// 将当前Segment的数据移到Sink中
System.arraycopy(data, pos, sink.data, sink.limit, byteCount);
sink.limit += byteCount;
pos += byteCount;
}

ByteString

ByteString内部可以保存byte类型的数据,作为一个工具类,它可以把byte转为String,这个String可
以是utf8的值,也可以是base64后的值,也可以是md5的值等等;