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:
- Read from an input stream to a buffer
- Copy from input stream buffer to b[]
- Copy b[] to the output stream buffer
- 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:
- 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
- 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
- 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”
- Source corresponds to the input flow, Sink corresponds to the output flow
- 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