openplanning

Hướng dẫn và ví dụ Java Buffer

  1. Buffer
  2. Buffer Methods
  3. capacity()
  4. position()
  5. position(int newPosition)
  6. limit()
  7. limit(int newLimit)
  8. mark()
  9. reset()
  10. clear()
  11. flip()
  12. rewind()
  13. remaining()
  14. hasRemaining()
  15. slice()
  16. duplicate()
  17. array()
  18. hasArray()
  19. arrayOffset()
  20. isReadOnly()
  21. isDirect()

1. Buffer

Java NIO Buffer đại diện cho một bộ chứa với sức chứa (capacity) cố định để lưu trữ các dữ liệu nguyên thuỷ. Nó thường được sử dụng cùng với các Java NIO Channel(s). Cụ thể, dữ liệu sẽ được đọc từ Channel vào Buffer hoặc ghi dữ liệu từ Buffer vào Channel.
public abstract class Buffer extends Object
Hệ thống phân cấp các lớp và interface có liên quan tới Java NIO Buffer:
  • ByteBuffer
  • ShortBuffer
  • IntBuffer
  • LongBuffer
  • FloatBuffer
  • DoubleBuffer
  • CharBuffer
  • MappedByteBuffer
  • ByteOrder
Mối quan hệ giữa ChannelBuffer cũng giống như mối quan hệ giữa cái bát và cái thìa. Cái thìa có thể sử dụng như một bộ chứa nhỏ để lấy đường ra từ bát và nó cũng được sử dụng như một bộ chứa nhỏ để cho đường từ bên ngoài vào trong bát. Như vậy, cái thìa hoạt động như một Buffer còn cái bát hoạt động như một Channel.
Capacity, limit, positionmark là 4 thuật ngữ quan trọng nhất của Java NIO Buffer, chúng sẽ được giải thích chi tiết dưới đây. Trong cả hai chế độ đọc và ghi con trỏ luôn tiến sang phải.

2. Buffer Methods

public final int capacity()
 
public final int position()   
public Buffer position(int newPosition)  

public final int limit()   
public Buffer limit(int newLimit)

public Buffer mark()
public Buffer reset()  
public Buffer clear()  
public Buffer flip()   
public Buffer rewind()  

public final int remaining()
public final boolean hasRemaining()

public abstract boolean isReadOnly();
public abstract boolean hasArray();
public abstract Object array();
public abstract int arrayOffset();
public abstract boolean isDirect();
public abstract Buffer slice();
public abstract Buffer duplicate();

3. capacity()

public final int capacity()
Phương thức capacity() trả về sức chứa (capacity) của Buffer này.
Mặc dù bạn chỉ có thể đọc hoặc ghi vào các phần tử từ chỉ số 0 đến chỉ số limit-1, nhưng nếu bạn sét limit = capacity bạn có thể truy cập (đọc, ghi) vào tất cả các phần tử của Buffer.

4. position()

public final int position()
Trả về vị trí hiện tại của con trỏ. Cả hai hoạt động đọc và ghi đều làm con trỏ tiến về cuối của Buffer, giá trị trả về luôn nhỏ hơn hoặc bằng limit.
  • 0 <= mark <= position <= limit <= capacity

5. position(int newPosition)

public Buffer position(int newPosition)
Sét vị trị mới cho con trỏ.
  • newPostion phải lớn hơn hoặc bằng 0 và nhỏ hơn hoặc bằng limit.
  • Nếu newPosition < mark thì mark sẽ bị loại bỏ (discard).

6. limit()

public final int limit()
Trả về limit (giới hạn) của Buffer này.
Buffer chỉ hỗ trợ đọc và ghi vào các phần tử tại chỉ số từ 0 đến limit-1, các phần tử tại chỉ số từ limit đến capacity-1 bị vô hiệu hoá. Tuy nhiên bạn có thể sét limit = capacity để có thể truy cập (đọc hoặc ghi) vào tất cả các phần tử của Buffer.
  • 0 <= mark <= position <= limit <= capacity
Trong ví dụ dưới đây. Một CharBuffer với capacity = 10, và limit = 7, bạn chỉ có thể đọc và ghi vào các phần tử tại chỉ số từ 0 đến 6. Nếu vi phạm một ngoại lệ sẽ được ném ra.
Buffer_limit_ex1.java
// Allocate a character type buffer.
CharBuffer buffer = CharBuffer.allocate(10); // capacity = 10

buffer.limit(7); // limit = 7

String text = "abcdefghij";
System.out.println("Input text: " + text);
System.out.println("Text length: " + text.length()); // 10

for (int i = 0; i < text.length(); i++) {
    char chr = text.charAt(i);
    
    // put character in buffer.
    buffer.put(chr);
    System.out.println(i + ". put: " + chr);
}
Output:
Input text: abcdefghij
Text length: 10
0. put: a
1. put: b
2. put: c
3. put: d
4. put: e
5. put: f
6. put: g
Exception in thread "main" java.nio.BufferOverflowException
    at java.base/java.nio.Buffer.nextPutIndex(Buffer.java:665)
    at java.base/java.nio.HeapCharBuffer.put(HeapCharBuffer.java:199)
    at org.o7planning.buffer.ex.Buffer_limit_ex1.main(Buffer_limit_ex1.java:21)

7. limit(int newLimit)

public Buffer limit(int newLimit)
Sét giá trị limit mới cho Buffer này. newLimit phải nhỏ hơn capacity, nếu không IllegalArgumentException sẽ được ném ra.
  • Nếu newLimit < position thì postion sẽ được sét thành newLimit.
  • Nếu newLimit < mark thì mark sẽ bị loại bỏ (discard).
Ví dụ:
Buffer_limit_newLimit_ex1.java
// Allocate a character type buffer.
CharBuffer buffer = CharBuffer.allocate(10); // capacity = 10

System.out.printf("Buffer capacity: %d%n%n", buffer.capacity()); // 10

buffer.limit(9); // limit = 9
System.out.printf("Buffer limit: %d, position: %d%n%n", buffer.limit(), buffer.position());

System.out.println("Set newPostion: 8");
buffer.position(8);

System.out.printf("Buffer limit: %d, position: %d%n%n", buffer.limit(), buffer.position());

System.out.println("Set newLimit: 7");
// Set limit = 7.
buffer.limit(7);

System.out.printf("Buffer limit: %d, position: %d%n", buffer.limit(), buffer.position());
Output:
Buffer capacity: 10

Buffer limit: 9, position: 0

Set newPostion: 8
Buffer limit: 9, position: 8

Set newLimit: 7
Buffer limit: 7, position: 7

8. mark()

public Buffer mark()
Phương thức mark() được sử dụng để đánh dấu vị trí hiện tại của con trỏ. Trong quá trình thao tác với Buffer vị trí của con trỏ có thể thay đổi, gọi phương thức reset() sẽ giúp con trỏ quay trở lại ví trí đã đánh dấu trước đó.
  • 0 <= mark <= position <= limit <= capacity
mark sẽ bị loại bỏ (discard) trong các trường hợp sau:
  • Gọi phương thức setPosition(newPosition) với newPosition < mark.
  • Gọi phương thức setLimit(newLimit) với newLimit < mark.
  • Gọi phương thức clear(), rewind() hoặc flip().

9. reset()

public Buffer reset()
Phương thức reset() được sử dụng để đưa con trỏ về vị trí đã được đánh dấu trước đó. (Xem phương thức mark()).
Phương thức này có thể ném ra InvalidMarkException nếu mark không được định nghĩa hoặc đã bị loại bỏ (discard).
mark sẽ bị loại bỏ (discard) trong các trường hợp sau:
  • Gọi phương thức setPosition(newPosition) với newPosition < mark.
  • Gọi phương thức setLimit(newLimit) với newLimit < mark.
  • Gọi phương thức clear(), rewind() hoặc flip().
Ví dụ:
Buffer_reset_ex1.java
CharBuffer buffer = CharBuffer.allocate(10); // capacity = 10

System.out.println("Set newPostion: 5");
buffer.position(5);

System.out.println("Mark current position!");
buffer.mark(); // marked position = 5

System.out.println("Call buffer.get() twice!");
char ch1 = buffer.get();
char ch2 = buffer.get();

System.out.printf("Position: %d%n%n", buffer.position()); // position = 7

System.out.println("Reset!");
buffer.reset();

System.out.printf("Position: %d%n%n", buffer.position()); // position = 5
Output:
Set newPostion: 5
Mark current position!
Call buffer.get() twice!
Position: 7

Reset!
Position: 5

10. clear()

public Buffer clear()
Phương thức clear() sét position = 0; limit = capacity, loại bỏ (discard) mark và trả về Buffer này. Việc gọi phương thức này không ảnh hưởng tới các dữ liệu trên Buffer.
Ví dụ:
Buffer_clear_ex1.java
CharBuffer buffer = CharBuffer.allocate(7); // capacity = 7

// Write data to buffer:
buffer.put('A');
buffer.put('B');

buffer.position(3); // Set position to 3.
buffer.limit(5); // Set limit to 5.

System.out.printf("buffer, capcity: %d, limit: %d, position: %d%n%n", //
        buffer.capacity(), buffer.limit(), buffer.position());

System.out.println("Clear...");
buffer.clear();

System.out.printf("buffer, capcity: %d, limit: %d, position: %d%n%n", //
        buffer.capacity(), buffer.limit(), buffer.position());

// Read data in buffer:
while (buffer.hasRemaining()) {
    char chr = buffer.get();
    System.out.println(chr + " --> " + (int) chr); // char and code.
}
Output:
buffer, capcity: 7, limit: 5, position: 3

Clear...
buffer, capcity: 7, limit: 7, position: 0

A --> 65
B --> 66
--> 0
--> 0
--> 0
--> 0
--> 0

11. flip()

public Buffer flip()
Phương thức flip() sẽ sét limit = position hiện tại, position = 0 và trả về Buffer này. Và loại bỏ (discard) mark. (Xem hình minh hoạ bên dưới).
Ví dụ dưới đây tương ứng với hình minh hoạ trên:
Buffer_flip_ex1.java
CharBuffer buffer = CharBuffer.allocate(10); // capacity = 10

System.out.printf("Position: %d, Limit: %d, Capacity: %d%n%n",
        buffer.position(), buffer.limit(), buffer.capacity());

System.out.println("Write 3 characters to buffer\n");
for(char ch : new char[] {'A','B','C'}) {
    buffer.put(ch);
}
System.out.printf("Position: %d, Limit: %d, Capacity: %d%n%n",
        buffer.position(), buffer.limit(), buffer.capacity());

System.out.println("Set limit = 7, position = 5\n");
buffer.limit(7);
buffer.position(5);

System.out.printf("Position: %d, Limit: %d, Capacity: %d%n%n",
        buffer.position(), buffer.limit(), buffer.capacity());   

System.out.println(" --- flip() --- \n");
buffer.flip();  

System.out.printf("Position: %d, Limit: %d, Capacity: %d",
        buffer.position(), buffer.limit(), buffer.capacity());
Output:
Position: 0, Limit: 10, Capacity: 10

Write 3 characters to buffer

Position: 3, Limit: 10, Capacity: 10

Set limit = 7, position = 5

Position: 5, Limit: 7, Capacity: 10

 --- flip() ---

Position: 0, Limit: 5, Capacity: 10
Phương thức flip() thường được sử dụng sau khi hoàn thành việc ghi dữ liệu vào Buffer. Sẵn sàng cho việc đọc ra các dữ liệu có ích trên Buffer. (Xem hình minh hoạ và ví dụ bên dưới).
Buffer_flip_ex2.java
CharBuffer buffer = CharBuffer.allocate(5); // capacity = 5  

// WRITE MODE:
System.out.println("Write 3 characters to buffer\n");
for(char ch : new char[] {'A','B','C'}) {
    buffer.put(ch);
}  
// (Now position = 3, limit = 5, capacity = 5).
System.out.println(" --- flip() --- \n");
buffer.flip();

// READ MODE:
// (Now position = 0, limit = 3, capacity = 5).
while(buffer.position() < buffer.limit()) {
   char ch = buffer.get();
   System.out.println(ch);
}
Output:
Write 3 characters to buffer

 --- flip() ---

A
B
C

12. rewind()

public Buffer rewind()
Phương thức rewind() được sử dụng để tua lại Buffer này, hay nói cách khác nó sét position = 0, và loại bỏ (discard) mark.

13. remaining()

public final int remaining()
Trả về số phần tử ở giữa positionlimit-1.

14. hasRemaining()

public final boolean hasRemaining()
Trả về true nếu có bất kỳ phần tử nằm giữa positionlimit-1. Ngược lại trả về false.

15. slice()

public abstract Buffer slice();
Trả về một Buffer mới là ảnh chụp một phần của Buffer này. Buffer mới bao gồm các phần tử nằm giữa positionlimit-1 của Buffer này. Vị trí đánh dấu (marked position) của Buffer mới không được xác định, position của Buffer mới là 0. (Xem hình minh hoạ bên dưới).
Hai Buffer này có liên hệ với nhau, các thay đổi dữ liệu trên Buffer này sẽ được nhìn thấy trên Buffer kia và ngược lại. Các giá trị mark, position, limit, capacity của hai Buffer này là độc lập với nhau.
Ví dụ:
Buffer_slice_ex1.java
CharBuffer buffer1 = CharBuffer.allocate(10); // capacity = 10  

// Write data to buffer1:
buffer1.put('A');
buffer1.put('B');
buffer1.put('C');

buffer1.position(1); // Set position to 1.
buffer1.limit(7); // Set limit to 7.

CharBuffer buffer2 = buffer1.slice();

System.out.printf("buffer2, capcity: %d, limit: %d, position: %d%n%n",
              buffer2.capacity(), buffer2.limit(), buffer2.position());

// Change data in buffer2:
buffer2.put('D');
buffer2.put('E');
buffer2.put('F');

//
buffer1.position(0);
buffer1.limit(4);
// Read data in buffer1:
while(buffer1.hasRemaining()) {
    System.out.println(buffer1.get());
}
Output:
buffer2, capcity: 6, limit: 6, position: 0

A
D
E
F

16. duplicate()

public abstract Buffer duplicate();
Trả về một Buffer mới là ảnh chụp của Buffer này.
Dữ liệu của Buffer mới sẽ là dữ liệu của Buffer này. Các thay đổi đối với dữ liệu của Buffer này sẽ hiển thị trong Buffer mới và ngược lại; các giá trị position, limitmark của hai Buffer sẽ độc lập. Khi vừa được tạo ra, hai Buffer này có cùng các giá trị position, limit, mark.
Ví dụ:
Buffer_duplicate_ex1.java
CharBuffer buffer1 = CharBuffer.allocate(10); // capacity = 10  

// Write data to buffer1:
buffer1.put('A');
buffer1.put('B');
buffer1.put('C');

buffer1.position(1); // Set position to 1.
buffer1.limit(7); // Set limit to 7.

CharBuffer buffer2 = buffer1.duplicate();

System.out.printf("buffer2, capcity: %d, limit: %d, position: %d%n%n",
              buffer2.capacity(), buffer2.limit(), buffer2.position());

// Change data in buffer2:
buffer2.put('D');
buffer2.put('E');
buffer2.put('F');

//
buffer1.position(0);
buffer1.limit(4);
// Read data in buffer1:
while(buffer1.hasRemaining()) {
    System.out.println(buffer1.get());
}
Output:
buffer2, capcity: 10, limit: 7, position: 1

A
D
E
F

17. array()

public abstract Object array(); // Optional Operation.
Trả về một mảng chứa các phần tử của Buffer này nếu thực sự Buffer này sử dụng mảng như một kỹ thuật để lưu trữ các phần tử. Đây là một hoạt động tuỳ chọn (optional operation), nó có thể không được hỗ trợ tại lớp con của Buffer. Nếu phương thức này không được hỗ trợ nó sẽ ném ra UnsupportedOperationException. Hãy kiểm tra xem liệu Buffer này có hỗ trợ mảng hay không bằng phương thức hasArray().
Hầu hết các lớp con của Buffer sẵn có trong JDK đều sử dụng một mảng nội bộ để lưu trữ các phần tử.
Class
Has Array?
ByteBuffer
true
MappedByteBuffer
true
ShortBuffer
true
IntBuffer
true
FloatBuffer
true
LongBuffer
true
DoubleBuffer
true
CharBuffer
true
Ví dụ:
Buffer_array_ex1.java
CharBuffer charBuffer = CharBuffer.allocate(5); // capacity = 5

// Write data to charBuffer:
charBuffer.put('A');
charBuffer.put('B');
charBuffer.put('C');

boolean hasArray = charBuffer.hasArray(); // true

if(hasArray)  {
    char[] charArray = charBuffer.array();
    System.out.println("charArray.length: " + charArray.length);  // 5
    for(char ch: charArray)  {
        System.out.println(ch + " --> " + (int)ch);    // char and code
    }
}
Output:
charArray.length: 5
A --> 65
B --> 66
C --> 67
--> 0
--> 0

18. hasArray()

public abstract boolean hasArray();
Trả về true nếu Buffer này sử dụng mảng như một kỹ thuật để lưu trữ các phần tử, ngược lại trả về false. Đây là một hoạt động tuỳ chọn (optional operation), nó có thể không được hỗ trợ tại lớp con của Buffer.
Hầu hết các lớp con của Buffer sẵn có trong JDK đều sử dụng một mảng nội bộ để lưu trữ các phần tử.
Class
Has Array?
ByteBuffer
true
MappedByteBuffer
true
ShortBuffer
true
IntBuffer
true
FloatBuffer
true
LongBuffer
true
DoubleBuffer
true
CharBuffer
true

19. arrayOffset()

public abstract int arrayOffset();

20. isReadOnly()

public abstract boolean isReadOnly();
Kiểm tra xem liệu Buffer này là chỉ đọc (read-only) hay không. Đây là một hoạt động tuỳ chọn (optional operation), nó có thể không được hỗ trợ tại lớp con của BufferUnsupportedOperationException sẽ được ném ra.
Hầu hết các lớp con của Buffer sẵn có trong JDK đều hỗ trợ cả hai chế độ đọc và ghi (theo mặc định):
Class
Read-Only
by default?
Support
Read-Only?
ByteBuffer
false
true
MappedByteBuffer
false
true
ShortBuffer
false
true
IntBuffer
false
true
FloatBuffer
false
true
LongBuffer
false
true
DoubleBuffer
false
true
CharBuffer
false
true
Ví dụ:
Buffer_isReadOnly_ex1.java
Buffer b1 = ByteBuffer.allocate(10);
Buffer b2 = MappedByteBuffer.allocate(10);
Buffer b3 = ShortBuffer.allocate(10);
Buffer b4 = IntBuffer.allocate(10);
Buffer b5 = FloatBuffer.allocate(10);
Buffer b6 = LongBuffer.allocate(10);
Buffer b7 = DoubleBuffer.allocate(10);
Buffer b8 = CharBuffer.allocate(10);

Buffer[] buffers = new Buffer[] { b1, b2, b3, b4, b5, b6, b7, b8 };

for (Buffer buffer : buffers) {
    System.out.println(buffer.getClass().getSimpleName() + " --> " + buffer.isReadOnly());
}
Output:
HeapByteBuffer --> false
HeapByteBuffer --> false
HeapShortBuffer --> false
HeapIntBuffer --> false
HeapFloatBuffer --> false
HeapLongBuffer --> false
HeapDoubleBuffer --> false
HeapCharBuffer --> false
Ví dụ: Tạo một read-only CharBuffer:
Buffer_isReadOnly_ex2.java
CharBuffer charBuffer = CharBuffer.allocate(10); // capacity = 10
// Write data to charBuffer.
charBuffer.put('A');
charBuffer.put('B');
charBuffer.put('C');

// Create a read-only CharBuffer.
CharBuffer readOnlyBuffer = charBuffer.asReadOnlyBuffer();

System.out.println("Write data to read-only buffer:");
readOnlyBuffer.put('D'); // ==> java.nio.ReadOnlyBufferException
Output:
Write data to read-only buffer:
Exception in thread "main" java.nio.ReadOnlyBufferException
    at java.base/java.nio.HeapCharBufferR.put(HeapCharBufferR.java:202)
    at org.o7planning.buffer.ex.Buffer_isReadOnly_ex2.main(Buffer_isReadOnly_ex2.java:18)
Ví dụ: Tạo các read-only Buffer khác: ByteBuffer, MappedByteBuffer, ShortBuffer, ...
Buffer_isReadOnly_ex3.java
package org.o7planning.buffer.ex;

import java.io.IOException;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.LongBuffer;
import java.nio.MappedByteBuffer;
import java.nio.ShortBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

public class Buffer_isReadOnly_ex3 {

    public static void main(String[] args) throws IOException {
        ByteBuffer b1 = ByteBuffer.allocate(10);
        
        //
        Path pathToWrite = Paths.get("/Volumes/Data/test/out-file.txt");
        FileChannel fileChannel = (FileChannel) Files.newByteChannel(pathToWrite, //
                                                StandardOpenOption.READ,
                                                StandardOpenOption.WRITE,
                                                StandardOpenOption.TRUNCATE_EXISTING);
        
        CharBuffer charBuffer = CharBuffer.wrap("This will be written to the file");
        MappedByteBuffer b2 = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, charBuffer.length());
        //
        ShortBuffer b3 = ShortBuffer.allocate(10);
        IntBuffer b4 = IntBuffer.allocate(10);
        FloatBuffer b5 = FloatBuffer.allocate(10);
        LongBuffer b6 = LongBuffer.allocate(10);
        DoubleBuffer b7 = DoubleBuffer.allocate(10);
        CharBuffer b8 = CharBuffer.allocate(10);
        
        Buffer[] buffers = new Buffer[] { b1, b2, b3, b4, b5, b6, b7, b8 };
        for (Buffer buffer : buffers) {
            System.out.println(buffer.getClass().getSimpleName() + " --> " + buffer.isReadOnly());
        }
        System.out.println(" --------- ");
        
        ByteBuffer b1r = b1.asReadOnlyBuffer();
        MappedByteBuffer b2r = (MappedByteBuffer) b2.asReadOnlyBuffer();
        ShortBuffer b3r = b3.asReadOnlyBuffer();
        IntBuffer b4r = b4.asReadOnlyBuffer();
        FloatBuffer b5r = b5.asReadOnlyBuffer();
        LongBuffer b6r = b6.asReadOnlyBuffer();
        DoubleBuffer b7r = b7.asReadOnlyBuffer();
        CharBuffer b8r = b8.asReadOnlyBuffer();
        
        Buffer[] readOnlyBuffers = new Buffer[] { b1r, b2r, b3r, b4r, b5r, b6r, b7r, b8r };
        
        for (Buffer buffer : readOnlyBuffers) {
            System.out.println(buffer.getClass().getSimpleName() + " --> " + buffer.isReadOnly());
        }
    }
}
Output:
HeapByteBuffer --> false
DirectByteBuffer --> false
HeapShortBuffer --> false
HeapIntBuffer --> false
HeapFloatBuffer --> false
HeapLongBuffer --> false
HeapDoubleBuffer --> false
HeapCharBuffer --> false
 ---------
HeapByteBufferR --> true
DirectByteBufferR --> true
HeapShortBufferR --> true
HeapIntBufferR --> true
HeapFloatBufferR --> true
HeapLongBufferR --> true
HeapDoubleBufferR --> true
HeapCharBufferR --> true

21. isDirect()

public abstract boolean isDirect();