Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.

java IO

write

This write method can only write byte by byte;

Note that the stream to be closed is enclosed in try parentheses, eliminating the need for finally closure in the code, as in the following examples.

private static void ioWrite(a) {
    try (OutputStream outputStream = new FileOutputStream("./demo.txt")) {
      outputStream.write('a');
      outputStream.write('b');
    } catch (FileNotFoundException e) {
      e.printStackTrace();
    } catch(IOException e) { e.printStackTrace(); }}Copy the code

read

This read method can only be written byte by byte;

private static void ioRead(a) {
  try (InputStream inputStream = new FileInputStream("./demo.txt")) {
    System.out.println((char)inputStream.read());
    System.out.println((char)inputStream.read());
  } catch (FileNotFoundException e) {
    e.printStackTrace();
  } catch(IOException e) { e.printStackTrace(); }}Copy the code

Read with cache

private static void ioBufferedRead(a) {
    try (InputStream inputStream = new FileInputStream("./demo.txt");
         Reader reader = new InputStreamReader(inputStream);
         BufferedReader bufferedReader = new BufferedReader(reader)) {
      System.out.println(bufferedReader.readLine());
    } catch (FileNotFoundException e) {
      e.printStackTrace();
    } catch(IOException e) { e.printStackTrace(); }}Copy the code

Write with cache

Pay attention to

BufferedOutputStream. Flush () needs to write stream data, but this method will automatically be invoked in this flow, thus in the try to write the stream objects, could save this step. Also note that this method overwrites the contents of the original file rather than appends.

private static void ioBufferedWrite(a) {
    try (OutputStream outputStream = new FileOutputStream("./demo.txt");
         BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream)) {
      bufferedOutputStream.write('a');
      bufferedOutputStream.write('q');
    } catch (FileNotFoundException e) {
      e.printStackTrace();
    } catch(IOException e) { e.printStackTrace(); }}Copy the code

Read and write with cache

 private static void ioWriteRead(a) {
    try (
      InputStream inputStream = new BufferedInputStream(new FileInputStream("./demo.txt"));
      OutputStream outputStream = new BufferedOutputStream(new FileOutputStream("./demoNew.txt"))) {
      byte[] data = new byte[1024];
      int read;
      while((read = inputStream.read(data)) ! = -1) {
        outputStream.write(data, 0, read); }}catch (FileNotFoundException e) {
      e.printStackTrace();
    } catch(IOException e) { e.printStackTrace(); }}Copy the code

IO Implements communication on the network

private static void ioNetDemo(a) {
    try (Socket socket = new Socket("yanfriends.com".80);
         BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
         BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {
      writer.write("GET/HTTP / 1.1 \ n" +
          "Host: www.yanfriends.com\n\n");
      writer.flush();
      String message;
      while((message = reader.readLine()) ! =null) { System.out.println(message); }}catch(IOException e) { e.printStackTrace(); }}Copy the code

NIO

NIO (New IO) library is introduced in JDK1.4. The purpose of NIO is the same as IO, but the implementation is different. NIO mainly uses blocks, so NIO is much more efficient than IO. Two sets of NIOS are provided in the Java API, one for standard INPUT and output NIO and the other for network programming NIO.

Contrast the IO:

IO NIO
Facing the flow Facing the buffer
Blocking IO Non-blocking IO
There is no The selector

Flow and cache

Java IO is stream-oriented, which means that a batch of data is read from a stream at a time, the data is not cached anywhere, and there is no support for moving data back and forth between streams. If you need to move this data (and why, you can read it multiple times), you still need to cache that data first.

NIO takes a slightly different buffer-oriented approach, in which data is read into the buffer for later processing. The buffer can be easily moved forward and backward, which allows for greater flexibility in processing data. However, you need to check that the buffer contains all the data you need to process it completely, and you need to make sure that reading data to the buffer through a channel does not overwrite data that has not yet been processed.

Blocking non-blocking

An IO stream is blocking, blocking when a thread calls its read() or write() methods until it has finished reading or writing data, during which it can do nothing.

NIO provides a non-blocking mode so that when a thread requests a channel to read data, it only gets the data ready and does not block until all data is ready (as IO does) so that the thread can do something else during the data preparation phase. The same is true for non-blocking writes. When a thread writes data to a channel, it does not block and wait for the data to finish. Instead, it can do something else and wait until the data has been written. When the thread is making IO calls and not blocking, this spare time can be spent interacting with other channels. That is, a single thread can manage the inputs and outputs of multiple channels.

Selector

The Selector in Java NIO allows a single thread to monitor multiple channels. Multiple channels can be registered into a Selector, and channels can then be “selected” to either have data ready or to be written to. This selector mechanism makes it easier for a single thread to manage multiple channels simultaneously.

Instead of reading bytes directly from InputStream, the data in NIO must first be read into buffer and then processed from there.

Read the example

When the thread is making IO calls and not blocking, this spare time can be spent interacting with other channels. That is, a single thread can manage the inputs and outputs of multiple channels.

  private static void nioRead(a) {
    try {
      RandomAccessFile file = new RandomAccessFile("./demo.txt"."r");
      FileChannel channel = file.getChannel();
      ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
      channel.read(byteBuffer);
      byteBuffer.flip();
      System.out.println(Charset.defaultCharset().decode(byteBuffer));
      byteBuffer.clear();
    } catch (FileNotFoundException e) {
      e.printStackTrace();
    } catch(IOException e) { e.printStackTrace(); }}Copy the code

NIO communicates in a network

private static void nioNetDemo(a) {
    try {
      ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
      serverSocketChannel.bind(new InetSocketAddress(80));
      serverSocketChannel.configureBlocking(false);
      Selector selector = Selector.open();
      serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
      while (true) {
        selector.select();
        for (SelectionKey key : selector.selectedKeys()) {
          if (key.isAcceptable()) {
            SocketChannel socketChannel = serverSocketChannel.accept();
            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
            while(socketChannel.read(byteBuffer) ! = -1) {
              byteBuffer.flip();
              socketChannel.write(byteBuffer);
              byteBuffer.clear();
            }
          }
        }
      }
    } catch(IOException e) { e.printStackTrace(); }}Copy the code

Okio

The advantage of Okio

In Java I/O reading and writing, the existence of buffer must involve the copy process, and if there is a dual-stream operation, such as reading from an input stream and then writing to an output stream, in the presence of buffer, the data direction is:

  1. Read from an input stream to a buffer
  2. Copy from input stream buffer to b[]
  3. Copy b[] to the output stream buffer
  4. The output stream buffer reads data to the output stream

This operation has a redundant copy operation, and Okio was born. In addition, Okio has simplified a more developer-friendly API to compensate for the inconvenience of IO/NIO.

Segment

Okio uses segments as data stores. Segment encapsulates byte[] with attributes that record states. When exchanging, if possible, the Segment as a whole is used as the data transmission medium, so that there is no copy of the specific data, but the corresponding Segment reference is exchanged. Segments are managed by Buffer. In buffer.write (), moving references rather than real data is the key to reducing copy and thus exchanging data.

The data structure of the Segment is as follows:

final class Segment {
  // Default capacity
  static final int SIZE = 8192;
  // Minimum amount of data to share
  static final int SHARE_MINIMUM = 1024;
  // An array to store specific data
  final byte[] data;
  // Start position of valid data index
  int pos;
  // End position of valid data index
  int limit;
  // Indicates whether the Segment is in shared state
  boolean shared;
  // Indicates whether the current Segment is a data owner, mutually exclusive with shared
  // The Segment owner of the default constructor is true when sharing data
  // The owner of the Segment to be shared is marked as false on exit
  boolean owner;
  // Point to the next Segment
  Segment next;
  // Point to the previous Segment
  Segment prev;
}
Copy the code

Okio’s dependency link

private static void okioRead(a) {
    try (BufferedSource source = Okio.buffer(Okio.source(new File("./demo.txt")))) {
      System.out.println(source.readUtf8Line());
    } catch (FileNotFoundException e) {
      e.printStackTrace();
    } catch (IOException e) { // AIO Asynchronous I/Oe.printStackTrace(); }}Copy the code

Android + Okio instance

Here is a simple example of downloading a web image using OkHttp and Okio:

File file = new File(getCacheDir() + "/demoImg.jpg");
OkHttpClient client = new OkHttpClient();
final Request request = new Request.Builder()
                .url("https://avatar.csdnimg.cn/7/E/5/1_lucasxu01.jpg")
                .build();
            client.newCall(request)
                .enqueue(new Callback() {
                  @Override
                  public void onFailure(@NotNull Call call, @NotNull IOException e) {
                    v.post(new Runnable() {
                      @Override
                      public void run(a) {
                        Toast.makeText(MainActivity.this."Downloading error", Toast.LENGTH_SHORT).show(); }}); }@Override
                  public void onResponse(@NotNull Call call, @NotNull Response response) {
                    try (BufferedSink sink = Okio.buffer(Okio.sink(apk))) {
                      sink.write(response.body().bytes());
                    } catch (IOException e) {
                      e.printStackTrace();
                    }
                    v.post(new Runnable() {
                      @Override
                      public void run(a) {
                        Toast.makeText(MainActivity.this."Download successful", Toast.LENGTH_SHORT).show(); }}); }});Copy the code

summary

Okio’s core competence is to enhance the interaction between streams, so that when data is moved from one buffer to another, it can be achieved without copy:

  1. With Segment as the storage structure, real data is stored in the member variable data of type Byte [], and the data state is marked with other variables. If necessary, move Segment references instead of copying data
  2. The Segment is stored as a single linked list in the Segment thread pool for reuse. The Buffer stores data as a bidirectional linked list. The head points to the header and is the oldest data
  3. Segments can be segmented by SLIpt (), shared by data, and combined by compact(). Buffer is used for data scheduling, which basically follows the idea of “large block of data moving reference, small block of data copy”
  4. Source corresponds to the input flow, Sink corresponds to the output flow
  5. TimeOut To complete an I/O operation within the expected time. If a synchronous TimeOut occurs, check the I/O operation time. If a asynchronous TimeOut occurs, check the interval time of another thread

Okio does not intend to optimize the underlying IO and replace the native IO. Okio optimizes the caching strategy to reduce memory stress and performance consumption, and provides a more friendly API for some IO scenarios, but for many IO scenarios, you still need to remember.

reference

NIO – NIO basic details

Okio where is good

Okio 1.9 easy to get started