1. What is it?

It is a set of Java IO on the basis of the packaging IO framework, in form than the original Java IO more concise and easy to use. Import OkHttp dependency can be referenced, source address: github.com/square/okio.

2. How to use it?

2.1 Use kotlin extension function, the code is concise, two lines of code

Of course, if you remember to add read and write permission to the file.

@Test fun useExtension() { val appContext = InstrumentationRegistry.getInstrumentation().targetContext var file:File = File (appContext filesDir. Path. The toString () + "/ test. TXT") / / write File File. The sink (). The buffer () writeString (" write STH ", Val fileContent = file.source().buffer().readString(charset.forname (" utF-8 ")).close() println() }Copy the code

The result is as follows

2.2 Java invocation methods of earlier versions

The code is as follows:

@Test public void testIO() { Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); File file = new File(appContext.getFilesDir().getPath().toString()); Try {// write the file BufferedSink BufferedSink = okio.buffer (okio.sink (file)); bufferedSink.writeString("write sth", Charset.forName("utf-8")); bufferedSink.close(); BufferedSource BufferedSource = okio.buffer (okio.source (file)); bufferedSource.readString(Charset.forName("utf-8")); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } System.out.println("===end"); }Copy the code

The result of the run is the same and is no longer listed.

3. See how it works

*** Note: Version is the latest version of kotlin code **

3.1 Who does it use?

3.1.1 InputStream and OutputStream

Trace the sink() and source() methods, obviously extending the related functions of InputStream and OutputStream and subclassing them into instances of source and sink.

3.1.2 SegmentPool and Segment

If you trace Okio’s buffer method, you can see the RealBufferedSink and RealBufferedSource classes, which implement Sink and Source respectively. RealBufferedSink and RealBufferedSource both hold the attribute Buffer of type Buffer. This Buffer class is the key. It uses a SegmentPool internally. A SegmentPool is a tool that manages segments.

3.1.3 ByteString

It’s also worth mentioning ByteString, which is a convenient tool for transferring Byte and String classes

3.2 Who uses it?

3.2.1 OkHttp

Okio is used in OKHTTP to support related stream operations.

3.2.2 In many large-scale applications such as Alipay

3.3 Principle Analysis

3.3.1 class diagram

Sink is responsible for output related operations, and Source is responsible for input related operations.

As you can see, both reads and writes are handled uniformly through Buffer. The underlying use of OutputSream, InputStream, essentially Java IO related API encapsulation.

3.3.2 Storage segments

Segments are essentially cached fragments, maintained by a SegmentPool, and the data structure is represented as a two-way linked list. The following figure

Use the SegmentPool to fetch a Segment for caching the data to be written.

Using two-way linked list can be very efficient in data replication, transfer and other operation scenarios.

Take a file writing example to see when segments come into play.

var file:File = File(appContext.getFilesDir().getPath().toString());
file.sink().buffer().writeString("write sth", Charset.forName("utf-8")).close()
Copy the code

The File class does not have a sink method. Here, the sink method is an extension function that returns an instance of sink.

/** Returns a sink that writes to `file`. */
@JvmOverloads
@Throws(FileNotFoundException::class)
fun File.sink(append: Boolean = false): Sink = FileOutputStream(this, append).sink()
Copy the code

The sink method of the FileOutputStream class is also an extension function. If you follow up, you can see that this is an extension function of the OutputStream. The FileOutputStream inherits from the OutputStream.

/** Returns a sink that writes to `out`. */ fun OutputStream.sink(): Sink = OutputStreamSink(this, Timeout()) This in OutputStreamSink is FileOutputStream, which is initialized when file.sink is called. Looking at the implementation of the OutputStreamSink class, which internally overwrites the write method, it essentially ends up calling the OutputStream in Java IO to write data. private class OutputStreamSink( private val out: OutputStream, private val timeout: Timeout ) : Sink {override fun write(source: Buffer, byteCount: Long) {/* omit */ out.write(head.data, head.pos, toCopy) /* omit */}}Copy the code

Although you have seen the final implementation via OutputStream, the actual write method has not been called yet, and you continue with the buffer() method, which returns the RealBufferedSink instance. Note that this in the constructor is OutputStreamSink

fun Sink.buffer(): BufferedSink = RealBufferedSink(this)
Copy the code

Follow up with the writeString method of RealBufferedSink. This method has a simple logical step. The first step is to write data through the Buffer, and the second step is to submit the completed Segment.

override fun writeString(string: String, charset: Charset): BufferedSink { check(! closed) { "closed" } buffer.writeString(string, charset) return emitCompleteSegments() }Copy the code

We follow up to the Buffer class, and we end up calling the commonWrite method, which in turn calls the commonWritableSegment method internally. Finally, copy the data to the Segment.

internal inline fun Buffer.commonWritableSegment(minimumCapacity: Int): Segment { require(minimumCapacity >= 1 && minimumCapacity <= Segment.SIZE) { "unexpected capacity" } if (head == null) {  val result = SegmentPool.take() // Acquire a first segment. head = result result.prev = result result.next = result return result } var tail = head!! .prev if (tail!! .limit + minimumCapacity > Segment.SIZE || ! tail.owner) { tail = tail.push(SegmentPool.take()) // Append a new empty segment to fill up. } return tail }Copy the code

So far still didn’t really read/write calls, follow up emitCompleteSegments method, finally call commonEmitCompleteSegment method.

internal inline fun RealBufferedSink.commonEmitCompleteSegments(): BufferedSink { check(! closed) { "closed" } val byteCount = buffer.completeSegmentByteCount() if (byteCount > 0L) sink.write(buffer, byteCount) return this }Copy the code

Who is sink in the sink.write call? Is OutputStreamSink! Finally, the write method of OutputStream is called to complete the writing of the file, and the timeline is reclaimed. Finally, don’t forget to call the close method to close the IO stream!

override fun close() = out.close()
Copy the code

3.3.3 ByteString

This class is designed to facilitate String and byte transfers. The principle is simple: hold both the original string and the corresponding byte array, reducing the number of conversions between them when fetching data.

4. Application Scenario

4.1 Network Request

IO streams in the TCP request framework, for example, can use it

4.2 the cache

For special data that can be saved as a stream, OkIO is much simpler. The various classes in OkIO also provide good caching for data that needs to be copied and moved frequently.

Request, for example, in the design of network framework, some banned frequently call interface (such as: data from the same client home page list pull), can be achieved by OkIO cache request data recently, when the business party frequently request, the framework without an actual network connection requests, directly back to the data in the cache.

5. Matters needing attention

The essence of OkIO is Java IO encapsulation. When writing code, you need to pay attention to the close of the IO stream. After the stream operation is complete, the close method is called last.

As an international convention, the project address is listed at the end of the article. The code is in androitTest’s com.pengyeah.kkp.

Order me order me order me