Queue وDeque والـ Stack
Queue وDeque والـ Stack
الـ Queue (الطابور) هو مجموعة مصمَّمة لحفظ العناصر قبل معالجتها. القاعدة الأساسية هي FIFO — الأول دخولًا هو الأول خروجًا: تُضاف العناصر من طرف (الذيل) وتُحذف من الطرف الآخر (الرأس). تخيّل طابور الدفع في محل البقالة.
الـ Deque (الطابور المزدوج، ينطق "ديك") يعمّم مفهوم الطابور: يمكنك إضافة العناصر وحذفها من كلا الطرفين. هذا التجريد الواحد يمكنه العمل كطابور أو كمكدّس أو كنافذة منزلقة.
واجهة Queue
تمتدّ java.util.Queue<E> من Collection وتضيف عائلتين من الوظائف — إحداهما ترمي استثناءات عند الفشل والأخرى تُعيد قيمة خاصة:
- الإضافة من الذيل:
add(e)ترميIllegalStateException؛offer(e)تُعيدfalse. - الحذف من الرأس:
remove()ترميNoSuchElementException؛poll()تُعيدnull. - الاطلاع على الرأس:
element()ترمي استثناءً؛peek()تُعيدnull.
في الممارسة العملية، استخدم ثلاثي offer / poll / peek — فهو آمن من null والخيار الأسلوبي المعتاد.
Queue لتشمل الطوابير ذات السعة المحدودة (كـ ArrayBlockingQueue) حيث قد تمنع القيود السعوية الإدراج. الصيغ التي ترمي استثناءات تشير إلى خطأ برمجي؛ أما صيغ القيمة الخاصة فهي لضبط التدفق حين تكون الطوابير الممتلئة أو الفارغة حالة طبيعية.
واجهة Deque
تمتدّ java.util.Deque<E> من Queue وتعكس كل وظيفة على الطرفين. الاتفاقية في التسمية واضحة: تنتهي الوظائف بـ First أو Last.
offerFirst(e)/offerLast(e)— الإضافة من الرأس / الذيل.pollFirst()/pollLast()— الحذف من الرأس / الذيل، تُعيدnullإذا كانت فارغة.peekFirst()/peekLast()— الاطلاع دون حذف.push(e)/pop()/peek()— أسماء بديلة للمكدّس تعمل على الرأس.
ArrayDeque — التنفيذ الأمثل
java.util.ArrayDeque<E> مصفوفة دائرية قابلة للتوسّع تُنفّذ Deque. وهي الخيار الصحيح الافتراضي لكلٍّ من الطوابير والمكدّسات في الكود أحادي الخيط.
استخدام ArrayDeque كطابور (FIFO)
صرِّح عن المتغير كـ Queue<String> لا ArrayDeque<String>. البرمجة وفق الواجهة تُبقي كودك مرنًا — يمكنك الاستبدال بـ LinkedList أو ArrayBlockingQueue المحدودة دون تغيير أي سطر آخر.
استخدام ArrayDeque كمكدّس (LIFO)
المكدّس يتبع قاعدة LIFO — الأخير دخولًا هو الأول خروجًا. حالات الاستخدام الكلاسيكية: تاريخ التراجع، تحليل التعابير، والاجتياز بالعمق أولًا.
صرِّح عن المتغير كـ Deque<Integer> حين تقصد دلالات المكدّس. الأسماء البديلة push / pop / peek على Deque تعمل حصرًا على الرأس، وهو بالضبط ما يحتاجه المكدّس.
لماذا تتجنّب java.util.Stack؟
يأتي Java مع كلاس يُسمّى java.util.Stack<E> موجود في JDK منذ الإصدار 1.0. يجب عدم استخدامه في الكود الجديد.
- يمتدّ
StackمنVectorالذي يزامن كل وظيفة. في الكود أحادي الخيط هذا الاستحواذ على القفل تكلفة صرفة دون أي فائدة. - لأن
Stackيمتدّ منVector، يرثget(int index)وadd(int index, E e)وغيرهما من وظائف الوصول بالفهرس التي تكسر تجريد المكدّس. يمكن للمستدعين إدراج عناصر أو حذفها من أي موضع — وهذا ليس مكدّسًا. - توثيق Javadoc الخاص بـ
Stackنفسه يقول: "مجموعة أكثر اكتمالًا واتساقًا من عمليات LIFO للمكدّس توفّرها واجهةDequeوتنفيذاتها، التي يجب استخدامها بدلًا من هذا الكلاس."
Deque<E> stack = new ArrayDeque<>();. إنه أسرع (لا مزامنة)، ويكشف فقط العمليات المناسبة للمكدّس عبر نوع الواجهة Deque، وهو التوصية في Javadoc الرسمي.
خصائص الأداء
تخزّن ArrayDeque العناصر في مصفوفة دائرية. جميع عمليات الحدود الأربع — الإضافة والحذف من الرأس أو الذيل — هي O(1) مستهلكة. تتضاعف المصفوفة حين تمتلئ وهو نسخ O(n) عرضي، لكن بالمتوسط على جميع عمليات الإدراج تكون التكلفة O(1). لا يوجد تكلفة تخصيص لكل عقدة كما في البنى المرتبطة.
تُنفّذ LinkedList أيضًا Deque، لكن كل عنصر كائن مستقل على الكومة مع حقلَي مؤشر. لأعباء عمل الطوابير والمكدّسات، تستهلك ArrayDeque ذاكرة أقل ولها موقع تخزين مؤقت أفضل مما يجعلها أسرع عمليًا. افضّل ArrayDeque ما لم تحتج تحديدًا إلى واجهة List على الكائن ذاته.
new ArrayDeque<>(1024) يخصّص المصفوفة الداخلية مسبقًا متجنّبًا نسخ تغيير الحجم خلال مرحلة الملء. السعة الافتراضية 16 عنصرًا.
مثال عملي: مدقّق الأقواس المتوازنة
التحقق من توازن الأقواس في سلسلة نصية مسألة كتابية كلاسيكية للمكدّس. كل قوس فاتح يُدفع؛ كل قوس غالق يجب أن يطابق قمة المكدّس.
الاختيار بين Queue وDeque
- تحتاج معالجة FIFO صارمة؟ صرِّح عن المتغير كـ
Queue<E>وخلفيّتهArrayDeque. - تحتاج مكدّسًا (LIFO)؟ صرِّح كـ
Deque<E>وخلفيّتهArrayDeque، واستخدمpush/pop/peek. - تحتاج التعامل مع الطرفين (مثل نافذة منزلقة)؟ استخدم
Deque<E>مع واجهة برمجةFirst/Lastالكاملة. - تحتاج أمانًا متعدد الخيوط؟ انظر إلى
java.util.concurrent:LinkedBlockingDequeوConcurrentLinkedQueueوغيرها.
الخلاصة
تُعرِّف Queue دلالات FIFO؛ تعمّمها Deque للطرفين وتعمل أيضًا كمكدّس. ArrayDeque هو التنفيذ الصحيح لكلا الدورين في الكود أحادي الخيط — إنه أسرع وأقل استهلاكًا من LinkedList لعمليات الحدود فقط. صرِّح دائمًا عن المتغيرات كـ Queue<E> أو Deque<E>، لا بالنوع الملموس. ولا تلجأ أبدًا إلى كلاس Stack القديم: Javadoc نفسه يوجّهك إلى Deque.