14 Mayıs 2020 Perşembe

ByteBuffer Sınıfı - NIO Soyut Bir Sınıftır

Giriş
Şu satırları dahil ederiz.
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
Kalıtım
Buffer arayüzünden kalıtır. Kodu şöyle
public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer> {
  final byte[] hb; // Non-null only for heap buffers
  final int offset;
  boolean isReadOnly; //Valid only for heap buffers
  ...
}
Bu sınıfın kardeşleri CharBuffer, DoubleBuffer, FloatBuffer, IntBuffer, LongBuffer, ShortBuffer sınıflarıdır.

Kavramlar
3 tane önemli kavram var.
1. position
2. limit
3. capacity
Şeklen şöyle
- Yazılmış bir nesneyi okumaya hazır hale getirmek için flip() metodu kullanılır
- Yazılmış bir nesneyi tekrar yazmaya hazır hale getirmek için clear() metodu kullanılır.


Bellek Yaratma
Bu sınıf mevcut bir byte[] nesnesini wrap() metodu ile sarmalayabilir, ya da allocate metodu ile bir bellek alanı yaratabilir. Yaratılan belleğin boyu sabittir ve büyümez!

Packing Görevi
putX() metodları ile yazma işlemi yapılır. getX() metodları ile okuma işlemi yapılır.

Yani putShort() çağrısı ile iki tane short değer yazıp getInt() metodu ile int değer okunabilir. Kısaca packing two shorts diyelim.

allocate metodu
Heap'te yaşayan bir bellek yaratır.
Örnek
Şöyle yaparız.
ByteBuffer bb = ByteBuffer.allocate(4);
allocateDirect metodu - off-heap/native/direct olarak tabir edilen bellek yaratır
Açıklaması şöyle. Bir DirectByteBuffer  nesnesi yaratır.
The contents of direct buffers may reside outside of the normal garbage-collected heap
Açıklaması şöyle.
When such an object is created, it makes an internal call that allocates the amount of native memory equal to the buffer capacity. This memory is released when this class’ equivalent of “finalize” Java method is called — either automatically, when the DirectByteBuffer  instance is GCed, or manually, which is rare.
Bellek alanı Java Heap dışında olsa bile, nesnenin kendisi halen JVM içinde yaşadığı için GC başlayıncaya kadar nesne silinmez.
Direct ByteBuffer objects clean up their native buffers automatically but can only do so as part of Java heap GC — so they do not automatically respond to pressure on the native heap. GC occurs only when the Java heap becomes so full it can't service a heap-allocation request or if the Java application explicitly requests it (not recommended because it causes performance problems).
DirectByteBuffer nesnesin kullandığı belleğe, işletim sistemi erişebilir. Bazı kısıtlar şöyle
The ByteBuffer API allows the creation of direct, off-heap byte buffers. These buffers can be directly accessed from a Java program. However, there are some limitations:

- The buffer size can't be more than two gigabytes
- The garbage collector is responsible for memory deallocation

Furthermore, incorrect use of a ByteBuffer can cause a memory leak and OutOfMemory errors. This is because an unused memory reference can prevent the garbage collector from deallocating the memory.
array metodu
Eğer allocate metodu ile bellek alanı yaratılmışsa, bu alana erişmek için kullanılır. Şöyle yaparız. Bu metod okuma yazma pozisyonuna dikkat etmez. Tüm dizi bellek alanını döndürür.
byte[] result = bb.array();
arrayOffset metodu
Şöyle yaparız.
int arrayOffset = bb.arrayOffset();
asDoubleBuffer metodu
Şöyle yaparız.
ByteBuffer bb = ...;
DoubleBuffer db = bb.asDoubleBuffer();
clear metodu - Yazmaya Hazır Hale Getirir
Buffer nesnesinin pozisyon değerini 0 yapar. Yani daha önce kullanılmış bir nesneyi tekrar yazmaya hazır hale getirir. flip() metodunun tersi diye düşünülebilir.
Örnek
Eğer clear metodunu çağırmazsak nesne dolunca tekrar yazmak mümkün olmaz. Şu kod yazma işleminden sonra tekrar yazmaya hazır hale getirmediği için yanlış.
FileChannel fileChannel = FileChannel.open(Paths.get("input.txt"),
  StandardOpenOption.READ, StandardOpenOption.WRITE);
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
while (fileChannel.read(byteBuffer) != -1) {
  byteBuffer.flip();  //Okumaya hazır hale getir.
  while (byteBuffer.hasRemaining()) {
    char c = (char)byteBuffer.get();
    System.out.println(c);
  }
}
Şöyle yaparız.
FileChannel fileChannel = FileChannel.open(Paths.get("JPPFConfiguration.txt"),
  StandardOpenOption.READ, StandardOpenOption.WRITE);
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
int n;
while ((n = fileChannel.read(byteBuffer)) != -1) {
  
  byteBuffer.flip();  //Okumaya hazır hale getir.
  while (byteBuffer.hasRemaining()) {
    char c = (char) byteBuffer.get();
    System.out.print(c);
  }
  byteBuffer.clear(); //Yazmaya hazır hale getir
}
Örnek
Şöyle yaparız.
ByteBuffer bb = ...;
bb.clear();
Örnek
Şöyle yaparız.
ByteBuffer b = new ByteBuffer(1024);
for(int i=0; i<N; i++) {
  b.clear(); //Make ready for write
  b.put(header[i]);
  b.put(data[i]);
  b.flip(); //Make ready for read
  out.write(b);
}
compact metodu
Açıklaması şöyle
Most writable buffers support a compact() method:
Compacting shifts any remaining data in the buffer to the start of buffer, freeing up more space for elements. Any data that was in those positions will be overwritten. The buffer's position is set to the end of data so it's ready for writing more data.

Compaction is an especially useful operation when you're copying-reading from one channel and writing data to another using non-blocking I/O. You can read some data into a buffer, write the buffer out again, then compact the data so all the data that wasn't written is at the beginning of the buffer, and the position is at the end of the data remaining in the buffer, ready to receive more data.
Örnek
Şöyle yaparız.
buffer.compact();
flip metodu - Okumaya Hazır Hale Getirir
Bu metod Java 11 ile artık Buffer yerine ByteBuffer dönüyor. Yazma işlemi bittikten sonra okumaya hazır hale getirmek için kullanılır. Açıklaması şöyle. flit() işlemi sonucunda limit = position yapıldığı için, okuma işleminde buffer overrun engellenir.
When you are done putting bytes into the buffer, you should flip it.
Eğer flip() metodunu kullanmadan belleğin belli bir alanına erişmek istersek getInt(offset) gibi bir metodu kullanmak gerekir. Şöyle yaparız,
long l = ByteBuffer.allocate(8).putInt(x).putInt(y).getLong(0);
//
ByteBuffer buffer = ByteBuffer.allocate(8).putLong(l);
x = buffer.getInt(0);
y = buffer.getInt(4);
Örnek
Şöyle yaparız.
ByteBuffer bb = ByteBuffer.allocate(1024);
... //Put data
bb.flip();
get metodu - Current position
byte okur. Şöyle yaparız. Current position ilerletilir.
ByteBuffer bb =..
byte[] b = new byte[bb.remaining()]
bb.get(b);
getFloat metodu
Şöyle yaparız. Current position ilerletilir.
float v = ByteBuffer.wrap(...).order(ByteOrder.BIG_ENDIAN).getFloat();
getInt metodu
Bir byte[] nesnesini int değere çevirir
Örnek
Şöyle yaparız
byte[] digest = ...
int hash = ByteBuffer.wrap(digest).getInt();
getInt metodu - index
16. int'e erişmek için şöyle yaparız. Current position ilerletilmez.
System.out.println(bb.getInt(16*4));
System.out.println(bb.asIntBuffer().get(16));
getShort metodu
Şöyle yaparız. Current position ilerletilir.
ByteBuffer bb = ...;
short s  = bb.getShort();
hasArray metodu
Şöyle yaparız.
ByteBuffer bb = ...;
if (buffer.hasArray()) {
  byte[] array = buffer.array();
  int arrayOffset = buffer.arrayOffset();
  return Arrays.copyOfRange(array, arrayOffset + buffer.position(),
                              arrayOffset + buffer.limit());
}
hasRemaining metodu
Okumak veya yazmak için kullanılır.
Örnek
Sonuna kadar okumak için şöyle yaparız.
while(buffer.hasRemaining()) {
  file.write(buffer);
}
limit metodu
Açıklaması şöyle. Belirtilen konum aşılırsa BufferOverflowException fırlatır.
mark <= position <= limit <= capacity
Örnek
Şöyle yaparız.
ByteBuffer b= ByteBuffer.allocate(10); // capacity = 10, limit = 10
b.limit(1);    //set limit to 1
b.put((byte)1);
b.put((byte)1); //causes java.nio.BufferOverflowException
order metodu
Buffer little endian olarak okunabilir.
Örnek
Şöyle yaparız.
byte[] buf = new byte[512];
ByteBuffer bb = ByteBuffer.wrap(buf).order(ByteOrder.LITTLE_ENDIAN);
Örnek
Şöyle yaparız.
ByteBuffer buf = ByteBuffer.wrap(new byte[] { (byte)0xC2, (byte)0x95, 0x3C, 0x2C,
  (byte)0xC1, 0x6B, (byte)0xD6, 0x41 });
double d = buf.order(ByteOrder.LITTLE_ENDIAN).getDouble();
position metodu - getter
Şöyle yaparız.
Arrays.copyOf(buffer.array(), 0, buffer.position());
position metodu - setter
Şöyle yaparız.
ByteBuffer bb = ByteBuffer.allocateDirect(32);
bb.position(0);
put metodu - byte
Şöyle yaparız.
bb.put((byte)12);
put metodu - byte []
Şöyle yaparız. Current position ilerletilir.
bb.put("one\ntwo\nthree\nfour\n".getBytes());
put metodu- ​int index, byte[] src, int offset, int length
Java 16 ile geliyor. Açıklaması şöyle
ByteBuffer, CharBuffer, DoubleBuffer, FloatBuffer, IntBuffer, LongBuffer and ShortBuffer all have a new put() method.  These methods copy a defined length section of a second buffer, starting at a given offset in that buffer to a specified index in the buffer that the method is being called on.  That description seems complicated, but it is a simple replacement for the loop:
Yani şu döngüden kurtuluyoruz
for (int i = offset, j = index; i < offset + length; i++, j++)
  destinationBuffer.put(j, sourceBuffer.get(i));
putInt metodu
Şöyle yaparız. Current position ilerletilir.
int totalPacket = ...;
bb.putInt(totalPacket);
putShort metodu
Şöyle yaparız.
bb.putShort((short)0x03);
remaining metodu
Okumak veya yazmak için kullanılır. Geriye kalan alanı döner.
ByteBuffer bb =..

byte[] b = new byte[bb.remaining()]
bb.get(b, 0, b.length);
rewind metodu
position alanını 0 yapar. Yani yazma ve okuma işlemine baştan başlanabilir.

slice metodu
Bu metodun ismi bence view() olmalıydı. Açıklaması şöyle.
The new buffer's position will be zero, its capacity and its limit will be the number of bytes remaining in this buffer, and its mark will be undefined.
Açıklaması şöyle
Slicing a buffer is a slight variant of duplicating. Slicing also creates a new buffer that shares data with the old buffer. However, the slice's zero position is the current position of the original buffer, and its capacity only goes up to the source buffer's limit. That is, the slice is a subsequence of the original buffer that only contains the elements from the current position to the limit. Rewinding the slice only moves it back to the position of the original buffer when the slice was created. The slice can't see anything in the original buffer before that point.
Bu metod header + payload şeklindeki paketlerde payload kısmını ayırmak için kullanılabilir. Açıklaması şöyle
This is useful when you have a long buffer of data that is easily divided into multiple parts such as a protocol header followed by the data. You can read out the header then slice the buffer and pass the new buffer containing only the data to a separate method or class
Örnek
Şöyle yaparız.
byte[] test = "Hello World".getBytes("Latin1");
ByteBuffer b1 = ByteBuffer.wrap(test);
byte[] hello = new byte[6];
b1.get(hello);
ByteBuffer b2 = b1.slice(); // position = 0, string = "World"
byte[] tooLong = b2.array(); // Will NOT be "World", but will be "Hello World".
byte[] world = new byte[5];
b2.get(world); // world = "World"
Örnek
Elimizdeki ByteBuffer nesnesini dst nesnesine yazmak için şöyle yaparız.
@Override
public synchronized int read(ByteBuffer dst) {
  if (buf.remaining() == 0) return -1;

  int count = min(dst.remaining(), buf.remaining());
  if (count > 0) {
    ByteBuffer tmp = buf.slice();
    tmp.limit(count);
    dst.put(tmp);
    buf.position(buf.position() + count);
  }
  return count;
}
Örnek
src ByteBuffer nesnesini elimizdeki nesneye yazmak şöyle yaparız.
@Override
public synchronized int write(ByteBuffer src) {
  if (buf.isReadOnly()) throw new NonWritableChannelException();

  int count = min(src.remaining(), buf.remaining());
  if (count > 0) {
    ByteBuffer tmp = src.slice();
    tmp.limit(count);
    buf.put(tmp);
    src.position(src.position() + count);
  }
  return count;
}
wrap metodu
Örnek - byte []
Şöyle yaparız.
byte[] buf = new byte[512];
ByteBuffer bb = ByteBuffer.wrap(buf);
Örnek - String
Şöyle yaparız.
ByteBuffer buffer = ByteBuffer.wrap("...".getBytes());
Socket ile Kullanım
Yazmak için şöyle yaparız.
SocketChannel socketChannel = ...;

ByteBuffer buf = ...;

//write
while(buf.hasRemaining()) {
  socketChannel.write(buf);
}
Okumak için şöyle yaparız.
//read
ByteBuffer inBuf = ByteBuffer.allocate(78);
if socketChannel.read(inBuf) > 0) {...}

Hiç yorum yok:

Yorum Gönder