Java Buffers: A Deep Dive

إيه هي الـ Buffers ؟

الـ Buffers عبارة عن object بيخزن كمية محددة من البيانات عشان تتبعت لجزء في نظام التشغيل مسؤول عن عمليات الإدخال والإخراج (I/O Service) أو يستقبل منها البيانات.
الـ Buffer بيبقى مكانه بين الأبلكيشن والـ channel الي بتكتب الـ buffered data للـ service، أو بتقرأ البيانات وتحطها في الـ buffer.
في أربع خصائص أساسية للـ buffers، وهم:

  • الـ Capacity: هي سعة العناصر الي ممكن تتخزن في الـ buffer، ودي بتتحدد لما الـ buffer بتعمل أول مرة، ومينفعش تتغير بعد كده.
  • الـ Limit: وهو عبارة عن index (بيبدأ من 0) بيحدد عدد data elements المتاحة في ال buffer، يعني أول element مش المفروض إنه يتقرأ أو يتكتب.
  • الـ Position: الـ index (بيبدأ من 0) للـ element الجاي عليه الدور في القراءة أو المكان الي هيتكتب فيه ال element الي جاي.
  • الـ Mark: هو index بيبدأ من الصفر، وهو عبارة عن الـ position في الـ buffer هيرجعله لما ننادي على reset() Method، الـ reset() مبتكونش متعرفة في الأول.

العلاقة بين الأربع خصائص دول ببعض كالآتي:

0markpositionlimitcapacity0 \leq mark \leq position \leq limit \leq capacity

الصورة الجاية دي بتوضح buffer لسه متكريت بيتعامل مع bytes والـ capacity بتاعته 7:

createdbuffer.svg

الصورة دي بتوضح buffer يقدر يشيل 7 عناصر كحد أقصى، الـ mark في الأول مش متعرف، والـ postion بيبدأ من ال 0، والـ limit بيبقى متظبط في الأول على الـ capacity والي بتحدد أقصى عدد من ال bytes الي ممكن تتخزن. تقدر توصل للأماكن من 0 لـ 6 بس في ال buffer ده، رقم 7 بره حدود ال buffer ده.

الـ Buffer والـ Classes المشتقة منه

الـ buffers بيتم تنفيذها عن طريق classes بـ inherit من ال abstract class الي اسمه java.nio.Buffer ودي الـ methods بتاعت الـ Buffer Class.

Pasted_image_20251221204333.png


Object array():
دي بترجع الـ array الي شايل الـ buffer ده، الـ method معمولة عشان تسمح للـ buffers الي معتمدة على الـ arrays انها تتبعت native code بشكل أسرع. الـ Concrete subclasses بتعمل override للـ method دي وبترجع covariant return types.
الـ method دي بترمي java.nio.ReadOnlyBufferException لو الـ buffer ده backed بـ array بس هي read-only، وبترمي java.lang.UnsupportedOperationException لو الـ buffer مش backed بـ accessible array.

int arrayOffset():
بترجع الـ offset بتاع أول element في الـ buffer جوه الـ array الي شايله لما الـ buffer يكون معتمد على array، الـ position الي رقمه pp في ال buffer بيقابل في الـ array الـ index الي رقمه ناتج

p+arrayOffset() p + arrayOffset()

أو

array index=buffer.position()+buffer.arrayOffset()\text{array index} = buffer.position() + buffer.arrayOffset()

مثال:


byte[] arr = {10, 20, 30, 40, 50, 60};

ByteBuffer buf = ByteBuffer.wrap(arr, 2, 3); 

// arrayOffset = 2
// capacity = 3
// position = 0

أول خطوة: قبل القراءة
Array indices:   0   1   2   3   4   5

Array values:   10  20  30  40  50  60
                         ^
                         | arrayOffset = 2 (start of buffer)
                         
Buffer indices:  0   1   2
Buffer values:  [30][40][50]
Position (p):   ^

Calculation:

array index = position + arrayOffset = 0 + 2 = 2
arr[2] = 30
تاني خطوة: بعد قراءة element واحد:

byte x = buf.get(); // position++

Array indices:   0   1   2   3   4   5
Array values:   10  20  30  40  50  60
                             ^
                             | arrayOffset = 2
Buffer indices:  0   1   2
Buffer values:  [30][40][50]
Position (p):       ^

Calculation:

array index = position + arrayOffset = 1 + 2 = 3
arr[3] = 40
الخطوة التالتة: بعد قراءة عنصرين:
byte y = buf.get(); // position++
Array indices:   0   1   2   3   4   5
Array values:   10  20  30  40  50  60
                                 ^
                                 | arrayOffset = 2
Buffer indices:  0   1   2
Buffer values:  [30][40][50]
Position (p):           ^

Calculation:

array index = position + arrayOffset = 2 + 2 = 4
arr[4] = 50

عشان تتأكد إن الـ buffer ليه array متاع ولا لأ أثناء استخدام ال method دي نادي على hasArray().
الـ method دي بترمي Exceptions مشابهة لـ array() الي اتكلمنا عنها فوق في حالات الـ read-only وعدم وجود backed array.

int capacity():
بترجع سعة الـ buffer ده.
Buffer clear():
بتمسح الـ buffer، الـ Position بيرجع 0 والـ limit بيبقى هو الـ capacity، والـ mark بتتلغي.
الـ method دي مش بتمسح الـ data فعليًا من ال buffer.
Buffer flip():
بتقلب الـ Buffer. الـ limit بيتظبط على ال position الحالي، بعدين الـ position بيرجع لـ 0، لو فيه mark متعرفة بتتلغي.
boolean hasArray():
بترجع true لو الـ buffer ده backed بـ array ومش للقراءة بس، غير كده بترجع false.
لما الـ method دي بترجع true تقدر تنادي array() و arrayOfset() وانت متطمن.

boolean hasRemaining():
بترجع true لو لسه فيه على الأقل عنصر واحد في ال buffer، يعني بين الـ position الحالي وال limit، غير كده بترجع false.
boolean isDirect():
بترجع true لو الـ buffer ده من نوع direct byte buffer، غير كده بترجع false.
boolean isReadOnly():
بترجع true لو ال buffer ده للقراءة بس، غير كده بترجع false.
int limit():
بترجع الـ limit بتاع ال buffer.
Buffer limit(int newLimit):
بتظبط الـ limit بتاع ال buffer على newLimit، لو الـ mark متعرفة وأكبر من newLimit بتتلغي.
الـ method دي بترمي java.lang.IllegalArgumentException لو الـ newLimit كان بالسالب أو أكبر من الـ capacity.
Buffer mark():
بتظبط الـ mark بتاعت ال buffer عند ال position الحالي، وبترجع الـ buffer.
int position():
بترجع الـ position الحالي بتاع الـ buffer.
Buffer position (int newPosition):
بتظبط الـ position بتاع الـ buffer على newPosition، لو الـ mark متعرفة وأكبر من newPosition بتتلغي.
الـ method دي بترمي java.lang.IllegalArgumentException لو الـ newPosition بالسالب أو أكبر من الـ limit الحالي.

int remaining():
بترجع عدد الـ elements الي فاضلة بين الـ position الحالي والـ limit.
Buffer reset():
بترجع الـ position بتاع الـ buffer للمكان الي كان متعلم عليه بالـ mark قبل كده.
الـ method دي مبتغيرش او بتلغي قيمة الـ mark. بترمي java.nio.InvalidMarkException لو الـ mark مكانتش اتظبطت أصلًا.
Buffer rewind():
بترجع الـ buffer للبداية، و الـ Position بتتظبط على 0 والـ mark بتتلغي.


هتلاحظ إم في methods كتيرة بترجع reference لنفس ال buffer عشان تسهل عليك إنك تعمل chain للـ methods ورا بعض، زي كده

              buf.mark().position(2).reset();

هتلاحظ كمان إن كل الـ buffers ينفع تقرأ منها، بس مينفعش يتكتب فيها وإلا هيترميلك ReadOnlyBufferException، نادي على isReadOnly() لو عايز تتأكد ينفع تكتب في الـ buffer ده ولا لأ.

مهم: الـ Buffers مش thread-safe، لازم تهندل الـ synchronization بنفسك لو عايز توصل لـ buffer من أكتر من thread.

في كذا abstract class بيعملوا extend لـ Buffer، واحد لكل primitive type ماعدا الـ boolean، زي

ByteBuffer, CharBuffer,  DoubleBuffer,  FloatBuffer, IntBuffer, LongBuffer, ShortBuffer.

الـ I/O operations الي بيعملها الـ OS بتكون عبارة عن bytes، الـ primitive types دي بتسمحلك تعمل حاجة اسمها view buffers عشان تقدر تعمل الـ I/O دي من وجهة نظر ال characters والـ integers والـ doubles وهكذا، لكن في الحقيقة كلها عبارة عن byte stream.

الـ Buffer Creation

الـ ByteBuffer وباقي الـ Types الـ primitive بتوفر class methods عشان تعرف تعمل create للنوع الي عايزه من الـ buffer، فـ مثلا الـ ByteBuffer:

ByteBuffer allocate (int capacity):
بيخليك تحجز byte buffer جديد، بالـ capacity الي بتبعتهاله، الـ position بيبقى 0، والـ limit بيبقى قد الـ capacity والـ mark مش متعرفة، وكل element بيبدأ بقيمة 0، كمان بيبقى ليه backing array والـ offset بتاعه 0.
الـ method بترمي IllegalArgumentException لو الـ capacity بالسالب.

ByteBuffer allocateDirect (int capacity):
بتحجز direct byte buffer جديد، هنتكلم عنه في الأخر، لكن بتحجزه بالـ capacity الي بتبعهاله، الـ position بيبقى 0، والـ limit بيبقى قد الـ capacity والـ mark مش متعرفة، وكل element بيبدأ بقيمة 0، لكن مبيكونش متحدد إذا كان ليه backing array ولا لأ، وبيرمي نفس الـ Exception بتاع الـ allocate.

قبل الـ JDK 7، الـ direct buffers اللي كانت تتحجز بالـ method دي كانت page-aligned في الذاكرة، وفي JDK 7 اتغير الـ implementation وبقت direct buffers مش بالضرورة page-aligned. ده المفروض يقلل استهلاك الـ memory للأبلكيشنز اللي بتنشئ buffers صغيرة.

Pasted_image_20251221234527.png
REF: https://docs.oracle.com/javase/7/docs/technotes/guides/io/enhancements.html#7

دي رسمة توضح الـ page-aligned قبل وبعد.

Pasted_image_20251222000216.png

ByteBuffer wrap (byte[] array)::
بتـ wrap bytes array جوه الـ buffer. الـ buffer الجديد ده بيبقى معتمد على الـ array دي، يعني أي تعديل في الـ buffer هيغير في ال array والعكس. الـ buffer capacity والـ limit بيكونوا نفس طول الـ array.length والـ position بيبقى 0، والـ mark مش متعرفة، والـ array offset بيبقى 0.

ByteBuffer wrap (byte[] array, int offset, int length)::
بـ wrap bytes array جوه الـ buffer، بس بتحدد جزء منها. الـ capacity بتبقى طول الـ array كله، بس الـ position بيبدأ من الـ offset، والـ limit بيبقى offset+lenghtoffset + lenght.
الـ method دي بترمي java.lang.IndexOutOfBoundsException لو الـ offset أو الـ length قيمهم مش مظبوطة بالنسبة لطول الـ array.

الطرق إلي اتكلمنا عنهم دول عبارة عن طريقتين بتخليك تـ create الـ byte Buffer وهي يا إنك تديه الـ backed array أو يا تخليه يعملها بنفسه.

        ByteBuffer buffer = ByteBuffer.allocate(10); // بيعمل المصفوفة بنفسه

1_ByteBufferAllocate.svg

        byte[] bytes = new byte[200]; 
        ByteBuffer buffer2 = ByteBuffer.wrap(bytes); // بيتبصاله المصفوفة الي فوق

2_ByteBufferWrap.svg

                buffer = ByteBuffer.wrap(bytes, 10, 50);

3_byteBufferWrapOffset.svg

الـ Buffers الي بتتعمل عن طريق allocate() أو wrap() بتعتبر Nondirect byte buffers (قولنا هنتكلم عنها في الأخر)، بس الي محتاج تعرفه إن بيكون ليها backing arrays، وتقدر توصل للـ arrays دي عن طريق الـ array() method طول ما hasArray() بترجع true.

متنساش تنادي arrayOffset() لو الـ hasArray() رجعت true عشان تعرف مكان أول element في الـ array.

زي ما الـ buffers بتقدر تـ manage elements متخزنة في arrays خارجية عن طريق wrap() هي كمان تقدر تـ manage data متخزنة في buffers تانية.
لما بتعمل buffer بيـ manage بيانات buffer تاني ده، الـ buffer التاني ده بيتسمى view buffer، وأي تغيير بيحصل في أي واحد فيهم بيسمع في التاني.
الـ view buffers بتتكريت عن طريق إننا نادي على dublicate() method من الـ subclass بتاع الـ buffer، والـ view buffer الي بيطلع بيبقى مكافئ للـ buffer الأصلي، الإتنين بيتشاركوا نفس الـ elements وليهم نفس الـ capacity لكن كل واحد فيهم بيبقى ليه position و limit و mark خاص بيه، ولو الـ buffer الأصلي كان read-only أو direct فـ الـ view buffer هيبقى read-only و direct.

لاحظ إن: الإتنين بيكونوا objects مختلفة، لكنهم بيتشاركوا في نفس الـ backing array.

الـ Buffers ممكن يتعملها create كمان عن طريق إنك تنادي واحدة من methods الـ asXBuffer()
في الـ ByteBuffer class. زي الموجودين في الصورة الي تحت دي:

Pasted_image_20251222020411.png

دول هيرجعوا view buffer بيتعامل مع الـ byte buffer كإنه buffer من ال type ده.
ممكن تستخدم الـ asReadOnlyBuffer() عشان تخلي الـ view buffer للقراءة بس، وأي محاولة للكتابة فيه هترمي ReadOnlyBufferException.

القراءة والكتابة من ال buffers

الـ ByteBuffer وباقي الـ Classes بتوفر الـ overloading لـ methods زي get() و put() عشان تكتب في الـ buffer أو تقرأ منه، الـ methods دي ممكن تبقى حاجة من إتنين

  1. Absolute:
    وده لما تطلب منك index زي
    ByteBuffer put(int index, byte b):
    دي بتخزن الـ byte الي اسمه b في المكان المُحدد بالـ index الي مبعوت معاها.
    byte get(int index):
    ودي بتجيب الـ byte الي في الـ index الي مبعوت ده.

الـ methods دي بترمي IndexOutOfBoundsException لو الـ index سالب أو أكبر من أو بيساوي الـ limit بتاع الـ buffer.

  1. Relative:
    ودي لما مبتطلبش منك index فـ بتحط أو بتجيب القيم من الـ current position، زي
    ByteBuffer put(byte b):
    دي عشان تخزن ال byte b في ال buffer عند الـ current position وبعدين تزود الـ position.
    الميثود دي بترمي java.nio.BufferOverflowException لو الـ position الحالي أكبر من أو بيساوي الـ limit.
    byte get():
    دي عشان تجيب ال byte الي في ال current position وتزود الـ position.
    الـ method دي بترمي BufferUnderflowException لو الـ position الحالي أكبر من أو بيساوي ال limit.

والـ put() سواءًا كانت absolute او relative بترمي ReadOnlyBufferException لو الـ buffer كان read-only.

في أنواع تانية overrides تانية للـ get() والـ put() متكلمناش عليها، زي إنك لو عايز تحط bulk من الdata وإلخ، هتلاقيهم موجودين في الـ documentation هنا:
https://docs.oracle.com/javase/8/docs/api/java/nio/ByteBuffer.html

مثال:

public static void main(String[] args) {   
    ByteBuffer buffer = ByteBuffer.allocate(10);  
    System.out.println("Capacity = " + buffer.capacity());  
    System.out.println("Limit = " + buffer.limit());  
    System.out.println("Position = " + buffer.position());  
    System.out.println("Remaining = " + buffer.remaining());  
    buffer.put((byte) 10).put((byte) 20).put((byte) 30);  
    System.out.println("Capacity = " + buffer.capacity());  
    System.out.println("Limit = " + buffer.limit());  
    System.out.println("Position = " + buffer.position());  
    System.out.println("Remaining = " + buffer.remaining());  
    for (int i = 0; i < buffer.position(); i++)  
        System.out.println(buffer.get(i));  
}

الـ Result هتبقى كده:

Capacity = 10
Limit = 10
Position = 0
Remaining = 10
Capacity = 10
Limit = 10
Position = 3
Remaining = 7
10
20
30

دي الـ state لحظة إنشاء الـ ### allocate(10) :
readwrite1.svg

بعد إضافة البيانات put(10), put(20), put(30)

readwrite2.svg

بعد قراءة get(i)

readwrite3.svg

الـ Flipping Buffers

بعد ما بنملى الـ buffer لازم نجهزه عشان الـ channels تسحب منه البيانات، لو بعت الـ buffer زي ماهو، الـ channel هتحاول توصل لبيانات مش متعرفة بعد الـ position الحالي، فعشان تحل المشكلة دي ممكن ترجع الـ position لـ 0، بس وقتها هيظهر مشكلة تانية إن ازاي الـ channel هتعرف ان الداتا الي دخلتها خلصت؟ الحل هو انك تستخدم خاصية الـ limit لإننا عارفين انه بيحدد نهاية الجزء النشط في ال buffer، فبنخلي الـ limit هو الـ position الحالي بعدين بنرجع الـ position لـ 0.
ممكن نعمل ده عن طريق:

                buffer.limit(buffer.position()).position(0);

في طريقة أسهل عشان تعمل ده وهي انك تعمل flip():

                buffer.flip();

في الحالتين الـ buffer هيكون جاهز إنك تسحب منه البيانات.
ده شكل الـ buffer بعد ما تعملها flip()

flipping.svg

لو ناديت على buffer.remaining() دلوقتي هترجعلك 3، القيم دي بتعبر عن ال bytes المتاحة لسه في ال buffer للسحب (10,20,30)

مثال لكتابة حروف في Character Buffer والقراءة منه:

public static void main(String[] args) {  
  
  
String[] poem = {  
  
        "El sa7 El Da7 Embo",  
        "Edy El wad La Abo",  
        "Ya 3eny El Wad By3yat",  
        "4el El wad Mn El 2rd",  
        "El Wad 3t4an Es2o"  
};  
  
CharBuffer buffer = CharBuffer.allocate(50);  
for(int i = 0; i < poem.length;i++){  
    for(int j =0; j < poem[i].length();j++)  
        buffer.put(poem[i].charAt(j));  
    buffer.flip();  
    while (buffer.hasRemaining())  
        System.out.print(buffer.get());  
    buffer.clear();  
    System.out.println();  
	}  
}
El sa7 El Da7 Embo
Edy El wad La Abo
Ya 3eny El Wad By3yat
4el El wad Mn El 2rd
El Wad 3t4an Es2o

الـ rewind() زي الـ flip() بس بتطنش الـ limit، كمان لو ناديت flip() مرتين ورا بعض مش هترجعك للحالة الأصلية، ولكن الـ buffer هيبقى حجمه صفر.

الـ Marking Buffers

لما بتنادي الـ mark() method في position معين في ال buffer ده كإنك عملت bookmark للمكان ده، انتَ بتعلم المكان ده عشان تقدر ترجعله لما تعمل reset().
شوف المثال ده:

 buffer = ByteBuffer.allocate(7);
 buffer.put((byte) 10).put((byte)20).put((byte) 30).put((byte) 40);
 buffer.limit(4);

marking1.svg

الـ position والـ limit دلوقتي متظبطين على الـ 4 زي ما واضح في الكود والصورة.
تعالى نفترض إننا عملنا

buffer.position(1).mark().position(3);

marking2.svg

زي ماهو واضح في الصورة الـ mark اتظبطت عند 1، لو بعت ال buffer ده لـ channel، الـ byte رقم 40 هو الي هيتبعت لإن ال position الحالي مشاور على ال index رقم 3 والـ position هيتحرك لـ 4.
لو بعدها عملت bufer.reset() وبعت الـ buffer للـ channel، الـ position هيرجع للـ mark الي هو 1 والـ bytes رقم 20 و 30 و40 هيتبعتوا للـ channel بالترتيب ده.

شوية Subclasses للـ Buffer

الـ Compact Method

تخيل إن عندك buffer ومليته داتا، وعملت flip() عشان تقرأ منه، قرأت شوية داتا بس لسه في Remaining bytes متقرأوش، وفجأة انتَ قررت إنك محتاج تكتب داتا جديدة في ال buffer ده من غير ما تمسح الـ remaining bytes دي، في الحالة دي انت لو عملت clear() فـ انتَ هتمسح كل حاجة، ولو كملت قراءة مش هتعرف تكتب، وهنا بيجي دور compact():
أول حاجة compact بتاخد الـ remaining وتعملهم shifting لأول الـ buffer خالص، بعدين بتجهز الـ buffer للكتابة تاني، ولكن بتبدأ تكتب من بعد الداتا الي اتعملها shift.

تخيل عندنا buffer الـ capacity بتاعته 6، وكان فيه حروف كلمة AHMED الي هي عبارة عن 5 حروف، إحنا قرأنا أول حرفين الي هم AH ولسه باقيلنا الـ MED
فـ الـ state دلوقتي كالآتي:

Index:    0   1   2   3   4   5 
Data:   | A | H | M | E | D |   |
          ^       ^           ^
          |       |           |
     (Old Data) Position    Limit
     (Read)     (Start of
                 remaining)
                 
Capacity: 6
Position: 2 (واقف عند حرف الـ M)
Limit: 5 (أخر داتا مكتوبة)

دلوقتي إحنا عايزين نشيل الـ A والـ H من الـ buffer ونجيب M,E,D للأول ونجهز الـ buffer إننا نكتب داتا جديدة بعدهم.
لما بنعمل compact() الـ JVM هيحسب عدد الـ bytes المتبقية: Remaining=LimitPositionRemaining = Limit - Position
في حالتنا 52=35-2=3 وإلي هم M,E,D.
هيبدأ ينسخهم لـ index 0:
position>index(0)position -> index(0)
position+1>index(1)position+1 -> index(1)
position+2>index(2)position+2 -> index(2)

دلوقتي الـ buffer بقى جاهز للكتابة، بعد ما الداتا القديمة انتقلت في الأول، والـ position بقى 3 عشان يبدأ يكتب من بعد ما الداتا القديمة خلصت، والـ Limit رجع 6 لأحر الـ Capacity عشان مسموحلك تملى الـ buffer للأخر.

Index:    0   1   2   3   4   5 
Data:   | H | A | M | ? | ? |   |
                      ^           ^
                      |           |
                   Position     Limit
                  (Ready to    (Capacity)
                   Write)
الـ Comparison

أوقات بتحتاج تقارن بين إتنين buffers عشان تشوفهم متساويين ولا لأ، أو عشان الترتيب. كل subclasses الـ buffer ماعدا الـ MappedByteBuffer بتعمل override للـ compareTo() و equals() methods عشان تعمل المقارنات، بينما MappedByteBuffer بتورث الـ methods دي من الـ ByteBuffer class.

                System.out.println(bytBuf1.equals(bytBuf2));
                System.out.println(bytBuf1.compareTo(bytBuf2));

شروط الـ equals() عشان نقول إن Buffer A بيساوي Buffer B هم 3 لازم يتحققوا كلهم:

  1. نفس الـ Type: مينفعش تقارن ByteBuffer بـ CharBuffer لازم الإتنين يكونوا Instances من نفس الـ class.
  2. نفس عدد الـ Remaining Elements: يعني الداتا الي فاضلة في الإتنين (الـ Active Window) يكونوا قد بعض، فـ المعادلة دي تتحقق (limitApositionA)==(limitBpositionB)(limitA - positionA) == (limitB - positionB)
  3. نفس تسلسل البيانات (Identical Sequence): المحتوى الي جوه الـ active window متطابق byte by byte بغض النظر عن الـ Position الي بدأوا منه. يعني لو الـ Buffer الأول بادئ من index 0 والتاني بادئ من index 50 بس فيهم المحتوى نفسه، يبقوا equal.

مثال:

Buffer A:
	Index:    0   1   2   3   4   5   6   7   8   9
	Data:   | . | . | . | . | X | Y | Z | . | . | . |
	                          ^           ^
	                          |           |
	                       Position     Limit
	                       
Capacity: 10 
Position: 4
Limit: 7
Remaining: 3 bytes (اللي هم X, Y, Z)
Buffer B:
	Index:    0   1   2   3   4
	Data:   | X | Y | Z | . | . |
	          ^           ^
	          |           |
	       Position     Limit
	       

Capacity: 5
Position: 0
Limit: 3
Remaining: 3 bytes (برضه X, Y, Z)

الإتنين متساويين لإن:

  1. الإتنين ByteBuffer
  2. عدد الـ Remaining هو 3 في الإتنين
  3. الـ element الأول في ال active window في الـ buffer الأول الي هو 4 قيمته X وفي الـ Buffer التاني الي الـ element الأول في الـ active window الي هو 0 قيمته X والإتنين بيساووا بعض، وباقي العناصر نفس النظام، برغم إختلاف الـ Position في كل واحد.

بالنسبة للـ compareTo() فـ دي بنستخدمها عشان الـ sorting، وبتشتغل بطريقة Lexicographical (أبجدي).
الي بتعمله إنها بتمشي Byte by Byte جوه الـ range بتاع الـ Remaining Elements
بتقارن أول Byte من الـ Buffer الأول وأول Byte من ال Buffer التاني عن طريق Byte.compare(b1, b2)

  • لو مختلفين، بترجع النتيجة فورًا
  • لو متساويين، بتتنقل للـ Byte الي بعده
  • في حالة خاصة وهي إن لو واحد خلص قبل التاني لإن عدد العناصر بتاعته أقل، فـ هما كده كانوا متطابقين لحد اللحظة دي، الـ Buffer الأقصر بيعتبر هو الأصغر وبيجي الأول في الترتيب.

العمليات في compateTo() بتعتمد كليًا على الـ position والـ limit الحاليين، ف لو غيرت الـ position ورجعت تقارن النتيجة هتختلف تمامًا.

الـ Direct Byte Buffers

الـ Byte Buffers هي الوحيدة بين كل أنواع الـ Buffers الي تقدر تشتغل كـ Sources أو Targets للـ I/O Operations الي بتتم خلال الـ Channels، السبب ورا ده هو طبيعة عمل الـ Operating Systems نفسها لإن الـ OS لما بيعمل I/O بيتعامل مع مناطق في الـ memory عبارة عن تسلسل متصل من الـ Bytes، حجم الواحدة 8-bit، ومش بيتعامل مع Integers ولا Objects معقدة.
نظريًا، الـ OS عنده القدرة إنه يوصل للـ Address Space الخاصة بالـ JVM Process وينقل الداتا منها على طول، لكن المشكلة هنا في طريقة الـ Memory Management في الـ JVM. الـ Byte Array في جافا مش شرط تكون متخزنة بشكل Contiguous في الـ physical memory، والأهم من كده إن الـ Garbage Collector ممكن في أي لحظة يحرك الـ Array دي من مكانها عشان يعمل Compaction للـ memory، القيود دي بتخلي الـ OS ميقدرش يعتمد على عنوان ثابت للداتا عشان ينقلها.

عشان كده الحل كان في الـ Direct Byte Buffer، وده نوع خاص من الـ Buffers بيتميز بإنه بيتعامل على طول مع الـ Channels والـ Native Code، فكرته انه بيحجز Memory space خارج الـ Java Heap بنسميها الـ Off-Heap، المساحة دي الـ Channels بتقدر تعمل عليها Raw Access باستخدام الـ Native Code و تـ instruct الـ OS ينقل الداتا منها وليها على طول من غير ما الـ Garbage Collector يتدخل او يحركها، وعشان كده بتعتبر أكفأ وسيلة إنك تنفذ I/O في بيئة الـ JVM.

direct_buffer.svg

لو قررنا نستخدم الـ  Non-direct Byte Buffer الي هو
العادي جوه الـ Heap ونبعته لـ Channel، العملية هتتم بس هيكون في Performance Overhead عشان:

الـ Channel مش هتعرف تتعامل مع الـ Heap Buffer بشكل مباشر فهتضطر تعمل Create لـ **Direct Byte Buffer** مؤقت وهتعمل copying لمحتوى الـ **Non-direct Buffer** وتحطه في الـ Buffer ده، بعدين تعمل الـ I/O Operation سواء كانت Read أو Write على الـ Buffer المؤقت ده، ولو كانت عملية read هترجع تنسخ الداتا تاني للـ Buffer الأصلي، وفي الأخر الـ buffer المؤقت ده بيترمي عشان الـ **Garbage Collector** يمسحه.

non_direct_buffer.svg

برغم إن الـ Direct Byte Buffer حل مثالي للسرعة، لكن الـ Allocation Cost بتاعته عالية لإن عشان تحجز memory بره الـ Heap وتكلم الـ OS يجهزها، ده بياخد وقت و resources أكتر بكتير من حجز Buffer عادي جوه الـ Heap.
عشان كده بيوصى إنك تستخدم الـ  Direct Buffers مع الـ Long-Lived Buffers (الـ Buffers الي هتفضل معاك فترة) عشان تتفادى مشكلة frequent creation.