CodeGym /وبلاگ جاوا /Random-FA /رابط صف جاوا و پیاده سازی های آن
John Squirrels
مرحله
San Francisco

رابط صف جاوا و پیاده سازی های آن

در گروه منتشر شد
در اینجا قصد داریم در مورد رابط Java Queue بحث کنیم. خواهید فهمید که ساختار داده Queue چیست، چگونه در جاوا نشان داده می شود، چه روش هایی برای همه صف ها مهم هستند. همچنین، چه پیاده سازی هایی از Queue در زبان جاوا وجود دارد. پس از آن به مهم ترین پیاده سازی ها نگاه دقیق تری می کنیم و با مثال هایی آن ها را یاد می گیریم.

ساختار داده صف

صف یک ساختار داده انتزاعی خطی با ترتیب خاصی از انجام عملیات است - First In First Out (FIFO). این بدان معناست که شما می توانید یک عنصر (یا صف، قرار دادن در صف) را فقط در انتهای ساختار اضافه کنید، و یک عنصر را (dequeue یا حذف از صف) فقط از ابتدای آن بگیرید. ممکن است ساختار داده صف را خیلی راحت تصور کنید. به نظر می رسد یک صف یا صف مشتریان در زندگی واقعی است. مشتری که اول اومده، قراره اول بهش سرویس داده بشه. اگر چهار نفر در صف مک‌دونالدز یا جاهای دیگر دارید، اولین کسی که صف می‌کشد اولین کسی است که فروشگاه را دریافت می‌کند. اگر مشتری جدید بیاید، او پنجمین صف خرید همبرگر خواهد بود. رابط صف جاوا و پیاده سازی های آن - 1بنابراین در حین کار با صف، المان های جدیدی به انتها اضافه می شود و اگر بخواهید عنصری دریافت کنید از ابتدا گرفته می شود. این اصل اصلی کار ساختار داده صف کلاسیک است.

صف در جاوا

صف در جاوا یک رابط است. طبق مستندات اوراکل، رابط صف دارای 2 سوپرواسط، 4 واسط مختلف است که از صف به ارث می‌برند، و فهرستی بسیار چشمگیر از کلاس‌ها.

سوپرواسط ها:

مجموعه<E>، تکرارپذیر<E>

همه زیرواسط های شناخته شده:

BlockingDeque<E>, BlockingQueue<E>, Deque<E>, TransferQueue<E>

همه کلاس های اجرایی شناخته شده:

AbstractQueue، ArrayBlockingQueue، ArrayDeque، ConcurrentLinkedDeque، ConcurrentLinkedQueue، DelayQueue، LinkedBlockingDeque، LinkedBlockingQueue، LinkedList، LinkedTransferQueue، PriorityBlockingQueue، PrioritychronQueue،

چه مفهومی داره؟ اول از همه، Java Queue بخشی از مجموعه Framework است و رابط مجموعه را پیاده سازی می کند. بنابراین از تمام روش های رابط مجموعه مانند درج، حذف و غیره پشتیبانی می کند. Queue رابط Iterable را پیاده‌سازی می‌کند که به یک شی اجازه می‌دهد تا هدف عبارت «for-each loop» باشد.

روش های صف جاوا

صف تعدادی روش را اعلام می کند. به عنوان متدهای اینترفیس، آنها باید در تمام کلاس هایی که Queue را پیاده سازی می کنند، نمایش داده شوند. مهم ترین روش های صف، جاوا:
  • پیشنهاد () Boolean - در صورت امکان یک عنصر جدید را در صف قرار می دهد
  • افزودن بولی (E e) - در صورت امکان یک عنصر جدید را در صف قرار می دهد. در صورت موفقیت، مقدار true را برمی‌گرداند و در صورت عدم وجود فضای خالی، یک IllegalStateException می‌اندازد.
  • Object poll() – یک عنصر را از سر آن بازیابی و حذف می کند. اگر صف خالی باشد، null برمی‌گرداند.
  • Object remove() – یک عنصر را از سر صف بازیابی و حذف می کند.
  • Object peek() – بازیابی می کند، اما عنصری را از سر صف حذف نمی کند. اگر صف خالی باشد، null برمی‌گرداند.
  • عنصر شی () - بازیابی می کند، اما عنصری را از سر صف حذف نمی کند.

رابط های فرعی صف جاوا

رابط صف توسط 4 واسط فرعی به ارث می رسد - BlockingDeque<E>، BlockingQueue<E>، Deque<E>، TransferQueue<E> . می توانید آنها را به 3 گروه تقسیم کنید: Deques، Blocking Queues و Transfer Queues که BlockingDeque متعلق به دو گروه اول است. بیایید نگاهی اجمالی به این گروه ها بیندازیم.

Deques

Deque به معنای D ouble- Ended Q ueue است و از افزودن یا حذف از هر یک از دم داده ها به عنوان صف (first-in-first-out/FIFO) یا از head به عنوان یک ساختار داده محبوب دیگر به نام پشته (Last-in-) پشتیبانی می کند . اولین خروج/LIFO). کلاس هایی که Deque Interface را پیاده سازی می کنند: ArrayDeque، ConcurrentLinkedDeque، LinkedBlockingDeque، LinkedList.

مسدود کردن صف ها

صف مسدود کننده صفی است که یک رشته را در دو حالت مسدود می کند:
  • thread در حال تلاش برای گرفتن عناصر از یک صف خالی است
  • thread سعی دارد عناصر را در صف کامل قرار دهد
هنگامی که یک رشته سعی می کند موارد را از یک صف خالی دریافت کند، منتظر می ماند تا رشته دیگری آیتم ها را در صف قرار دهد. به طور مشابه، هنگامی که یک نخ سعی می کند عناصر را در یک صف کامل قرار دهد، منتظر می ماند تا رشته دیگری عناصر را از صف خارج کند تا فضای خالی برای عناصر بدست آید. مطمئناً، مفهوم "صف کامل" به این معنی است که صف دارای اندازه محدودی است که معمولاً در سازنده مشخص می شود. صف های استاندارد Blocking عبارتند از LinkedBlockingQueue، SynchronousQueue و ArrayBlockingQueue. کلاس های پیاده سازی رابط BlockingQueue : ArrayBlockingQueue، DelayQueue، LinkedBlockingDeque، LinkedBlockingQueue، LinkedTransferQueue، PriorityBlockingQueue، SynchronousQueue. BlockingDeque یک رابط فرعی برای BlockingQueue است. BlockingDeque مانند BlockingQueue یک صف مسدود کننده است، اما دو طرفه. بنابراین ویژگی های رابط Deque را به ارث می برد. این برای اجرای چند رشته ای است، اجازه نمی دهد عناصر صفر و ظرفیت محدود شود. پیاده‌سازی رابط BlockingDeque عملکرد گرفتن عناصر را در صورت خالی بودن صف و افزودن یک عنصر به صف در صورت پر بودن مسدود می‌کند.

صف های انتقال

رابط TransferQueue رابط BlockingQueue را گسترش می دهد. با این حال، برخلاف اجرای صف‌های رابط BlockingQueue، که در آن رشته‌ها می‌توانند در صورت خالی بودن صف (در حال خواندن)، یا اگر صف پر (نوشتن)، مسدود شوند، صف‌های رابط TransferQueue جریان نوشتن را مسدود می‌کنند تا زمانی که جریان دیگری عنصر را بازیابی کند. برای این کار از روش انتقال استفاده کنید. به عبارت دیگر، اجرای BlockingQueue تضمین می کند که عنصر ایجاد شده توسط Producer باید در صف باشد، در حالی که اجرای TransferQueue تضمین می کند که عنصر Producer توسط مصرف کننده "دریافت" شود. تنها یک پیاده سازی رسمی جاوا از رابط TransferQueue وجود دارد - LinkedTransferQueue.

پیاده سازی صف جاوا

کلاس های زیادی وجود دارند که رابط صف را پیاده سازی می کنند:
  • AbstractQueue طبق اسناد Queue Java 8، این کلاس انتزاعی پیاده سازی های اولیه برخی از عملیات Queue را ارائه می دهد. به عناصر تهی اجازه نمی دهد. 3 روش دیگر اضافه کردن، حذف کردن و عنصر بر اساس پیشنهاد کلاسیک صف ، نظرسنجی و نگاه کردن به ترتیب وجود دارد. با این حال آنها به جای نشان دادن شکست از طریق بازده های نادرست یا تهی، استثناهایی را ایجاد می کنند.
  • ArrayBlockingQueue - یک صف مسدود کننده FIFO با اندازه ثابت که توسط یک آرایه پشتیبانی می شود
  • ArrayDeque - اجرای آرایه قابل تغییر اندازه رابط Deque
  • ConcurrentLinkedDeque - یک deque همزمان نامحدود بر اساس گره های مرتبط.
  • ConcurrentLinkedQueue - یک صف بدون محدودیت رشته ای بر اساس گره های مرتبط.
  • DelayQueue - یک صف زمان بندی مبتنی بر زمان که توسط یک پشته پشتیبانی می شود
  • LinkedBlockingDeque - اجرای همزمان رابط Deque.
  • LinkedBlockingQueue - یک صف مسدودکننده FIFO که به صورت اختیاری محدود می شود که توسط گره های مرتبط پشتیبانی می شود
  • LinkedList - پیاده سازی لیست با پیوند دوگانه از رابط های List و Deque. تمام عملیات لیست اختیاری را پیاده سازی می کند و به همه عناصر (از جمله null) اجازه می دهد.
  • LinkedTransferQueue - یک TransferQueue نامحدود بر اساس گره های مرتبط
  • PriorityBlockingQueue - یک صف اولویت مسدود کننده نامحدود که توسط یک پشته پشتیبانی می شود
  • PriorityQueue - یک صف اولویت بر اساس ساختار داده پشته
  • SynchronousQueue - یک صف مسدود کننده که در آن هر عملیات درج باید منتظر عملیات حذف مربوطه توسط رشته دیگری باشد و بالعکس.
محبوب ترین پیاده سازی ها LinkedList، ArrayBlockingQueue و PriorityQueue هستند. بیایید به آنها نگاه کنیم و چند مثال برای درک بهتر انجام دهیم.

LinkedList

Class LinkedList در جاوا رابط های List و Deque را پیاده سازی می کند. بنابراین، ترکیبی از List و Deque، یک صف دو طرفه است که از افزودن و حذف عناصر از هر دو طرف پشتیبانی می کند. در جاوا LinkedList لیست دارای پیوند دوگانه است: هر عنصر List Node را فراخوانی می کند و شامل یک شی و ارجاع به دو شیء همسایه - قبلی و بعدی است. رابط صف جاوا و پیاده سازی های آن - 2ممکن است بگویید LinkedList از نظر استفاده از حافظه چندان موثر نیست. این درست است، اما این ساختار داده می تواند در مورد عملکرد عملیات درج و حذف مفید باشد. با این حال، تنها در صورتی اتفاق می‌افتد که از تکرارکننده‌ها برای آنها استفاده کنید (در این مورد در زمان ثابت رخ می‌دهد). عملیات دسترسی بر اساس شاخص با جستجو از ابتدای انتهای (هر کدام نزدیکتر) به عنصر مورد نظر انجام می شود. با این حال، در مورد هزینه های اضافی برای ذخیره مراجع بین عناصر فراموش نکنید. بنابراین، LinkedList محبوب ترین اجرای صف در جاوا است. این یک پیاده سازی از List و Deque نیز هست و به ما اجازه می دهد یک صف دو طرفه متشکل از هر شیء از جمله null ایجاد کنیم. LinkedList مجموعه ای از عناصر است.
اطلاعات بیشتر درباره LinkedList: ساختار داده جاوا LinkedList

سازنده های لینکدلیست

LinkedList() بدون پارامتر برای ساخت یک لیست خالی استفاده می شود. LinkedList(Collection<? extensions E> c) برای ایجاد لیستی است که حاوی عناصر مجموعه مشخص شده است، به منظور بازگرداندن آنها توسط تکرار کننده مجموعه.

روش های اصلی LinkedList:

  • add(E element) عنصر مشخص شده را به انتهای این لیست اضافه می کند.
  • add(int index, E element) عنصر را در نمایه موقعیت مشخص شده درج می کند.
  • get(int index) عنصر را در موقعیت مشخص شده در این لیست برمی گرداند.
  • remove(int index) عنصری را که در شاخص موقعیت قرار دارد حذف می کند.
  • remove(Object o) اولین رخداد عنصر ?o را در صورت وجود از این لیست حذف می کند.
  • remove() اولین عنصر لیست را بازیابی و حذف می کند.
  • addFirst()، addLast() یک عنصر را به ابتدا/پایان یک لیست اضافه می کنند
  • clear() تمام عناصر را از لیست حذف می کند
  • contain(Object o) اگر لیست حاوی عنصر o باشد مقدار true را برمی گرداند.
  • indexOf(Object o) اندیس اولین رخداد عنصر o یا -1 را اگر در لیست نباشد برمی گرداند.
  • set (int index، عنصر E) عنصر را در موقعیت شاخص با عنصر جایگزین می کند
  • size() تعداد عناصر موجود در لیست را برمی گرداند.
  • ()toArray آرایه ای را برمی گرداند که شامل تمام عناصر لیست از اول تا آخرین عنصر است.
  • pop() که عنصری را از پشته بیرون می آورد (که توسط لیست نشان داده می شود)
  • push(E e) که یک عنصر را روی پشته هل می دهد (که توسط این لیست ارائه می شود)
مثال صف جاوا - LinkedList (قرار دادن و حذف عناصر به روش های مختلف)

import java.util.*;
 
public class LinkedListTest {
 
       public static void main(String args[]){
 
           LinkedList<Integer> myLinkedList= new LinkedList<Integer>();
           myLinkedList.add(1);
           myLinkedList.add(2);
           myLinkedList.add(4);
           System.out.println("three added elements: " + myLinkedList);
           //put one element into the head, not to the tail:
           myLinkedList.push(5);
           System.out.println("The new element last in will be the first: " + myLinkedList);
           //add new element at the specified position:
           myLinkedList.add(4,3);
           //put one element into the head, not to the tail (same as push):
           myLinkedList.addFirst(6);
           System.out.println(myLinkedList);
           //now remove element no 2 (it is 1):
           myLinkedList.remove(2);
           System.out.println(myLinkedList);
           //now remove the head of the list
           myLinkedList.pop();
           System.out.println(myLinkedList);
           //remove with the other method
           myLinkedList.remove();
           System.out.println(myLinkedList);
           //and with one more
           myLinkedList.poll();
           System.out.println(myLinkedList);
       }
       }

صف اولویت

PriorityQueue دقیقاً صف به معنای کلی FIFO نیست. عناصر صف اولویت بر اساس ترتیب طبیعی آنها یا توسط مقایسه کننده ای که در زمان ساخت صف ارائه می شود، بسته به سازنده ای که استفاده می شود، مرتب می شوند. با این حال، نظمی نیست که بتواند در ساختار خطی مانند فهرست (از بزرگ‌ترین به کوچک‌ترین یا بالعکس) باشد. یک صف اولویت بر اساس یک پشته حداقل اولویت. Heap یک ساختار داده مبتنی بر درخت باینری است . اولویت هر یک از والدین بیشتر از اولویت های فرزندانش است. اگر هر یک از والدین بیش از دو فرزند نداشته باشند، درخت باینری کامل نامیده می شود و پر شدن سطوح از بالا به پایین (از همان سطح - از چپ به راست) انجام می شود. هر بار که یک عنصر جدید به آن اضافه یا حذف می شود، هیپ باینری خود را دوباره سازمان می دهد. در مورد min-heap، کوچکترین عنصر بدون توجه به ترتیب درج به ریشه می رود. صف اولویت بر اساس این min-heap، بنابراین اگر یک PriorityQueue از اعداد صحیح داشته باشیم، اولین عنصر آن کوچکترین این اعداد خواهد بود. اگر ریشه را حذف کنید، کوچکترین بعدی تبدیل به روت می شود.

روشهای صف اولویت اصلی:

  • add(object) boolean عنصر مشخص شده را در صف اولویت قرار می دهد. در صورت موفقیت واقعی برمی گردد. اگر صف پر باشد، متد یک استثنا ایجاد می کند.
  • پیشنهاد (object) boolean عنصر مشخص شده را در این صف اولویت قرار می دهد. اگر صف پر باشد، متد false را برمی گرداند.
  • حذف(شیء) بولی یک نمونه از عنصر مشخص شده را در صورت وجود از این صف حذف می کند.
  • Object poll() سر این صف را بازیابی و حذف می کند. اگر صف خالی باشد، null برمی‌گرداند.
  • void clear() تمام عناصر را از صف اولویت حذف می کند.
  • عنصر شی () سر این صف را بدون حذف آن بازیابی می کند. اگر صف خالی باشد NoSuchElementException را پرتاب می کند.
  • Object peek () سر صف را بدون حذف آن بازیابی می کند. اگر صف خالی باشد، null برمی‌گرداند.
  • اگر صف حاوی عنصر o باشد، boolean contain(Object o) true برمی گرداند.
  • int size() تعداد عناصر موجود در این صف را برمی گرداند.

مثال PriorityQueue


import java.util.LinkedList;
import java.util.PriorityQueue;
import java.util.Queue;
 
public class PriorityQueueExample {
   public static void main(String[] args) {
 
       Queue<Integer> queueL = new LinkedList<>();
       for (int i = 5; i > 0; i--) {
           queueL.add(i);
       }
       System.out.println("Print our LinkedList Queue (FIFO): " + queueL);
       Queue<Integer> priorityQueue = new PriorityQueue<>();
 
       for (int i = 5; i > 0; i--) {
       priorityQueue.offer(i);
       }
 
       System.out.println("PriorityQueue printing (by iterating, no elements removing): " + priorityQueue);
       System.out.println("Print PriorityQueue using poll() (by retrieval): " );
       while (!priorityQueue.isEmpty()) {
           System.out.println(priorityQueue.poll());
       }
}
}
Print our LinkedList Queue (FIFO): [5, 4, 3, 2, 1]
PriorityQueue printing (by iterating, no elements removing): [1, 2, 4, 5, 3]
Print our  PriorityQueue using poll() (by retrieval): 
1
2
3
4
5
درک این نکته مهم است که صف های اولویت بر پایه پشته های باینری هستند، بنابراین آنها عناصر را به ترتیب مرتب شده خطی نگه نمی دارند. هر راهی از ریشه تا برگ سفارش داده می شود، اما راه های مختلف از ریشه چنین نیست. این بدان معناست که می توانید عنصر حداقلی صف را خیلی سریع دریافت کنید. اگر هر بار هد را حذف کنید، یک ساختار مرتب شده چاپ خواهید کرد.

ArrayBlockingQueue

ساختار داده داخلی ArrayBlockingQueue بر اساس یک آرایه دایره ای برای ذخیره عناصر است. این یک صف معمولی (FIFO) است در صورتی که عناصر جدیدی در انتهای صف درج شوند و عملیات استخراج یک عنصر را از سر صف بازگرداند. پس از ایجاد ظرفیت صف نمی‌توان تغییر داد. تلاش برای درج (قرار دادن) یک عنصر در یک صف کامل منجر به مسدود کردن جریان می شود. تلاش برای گرفتن یک عنصر از یک صف خالی نیز موضوع را مسدود می کند. همانطور که قبلا گفتیم این آرایه دایره ای است. این بدان معناست که اولین و آخرین عناصر آرایه به طور منطقی در مجاورت قرار می گیرند. هر بار که عنصر را در صف قرار می دهید یا آن را از صف حذف می کنید، صف شاخص های عناصر سر و دم را ارتقا می دهد. اگر برخی از ایندکس ها آخرین عنصر آرایه را پیش ببرند، از 0 مجدداً راه اندازی می شود. بنابراین، در صورت حذف هد (مثل آرایه معمول)، صف لازم نیست همه عناصر را جابجا کند. با این حال، در صورت حذف یک عنصر از وسط (با استفاده از Iterator.remove)، عناصر جابجا می شوند. ArrayBlockingQueue از یک سیاست انصاف اضافی با پارامتر منصفانه در سازنده پشتیبانی می کند تا کار جریان های انتظار تولیدکنندگان (درج عناصر) و مصرف کنندگان (عناصر استخراج) را سفارش دهد. به طور پیش فرض، سفارش تضمین نمی شود. اما اگر صف با "fair == true" ایجاد شود، پیاده سازی کلاس ArrayBlockingQueue دسترسی رشته را به ترتیب FIFO فراهم می کند. ارزش ویژه معمولاً پهنای باند را کاهش می دهد، اما نوسانات را نیز کاهش می دهد و از تمام شدن منابع جلوگیری می کند.

سازنده های کلاس ArrayBlockingQueue

  • ArrayBlockingQueue (ظرفیت int) یک صف با ظرفیت ثابت و با یک خط مشی دسترسی پیش فرض ایجاد می کند.
  • ArrayBlockingQueue (ظرفیت int، بولی منصفانه) یک صف با ظرفیت ثابت و یک خط مشی دسترسی مشخص ایجاد می کند.
  • ArrayBlockingQueue (ظرفیت int، boolean fair، Collection <? extends E> c) یک صف با ظرفیت ثابت مشخص شده توسط خط مشی دسترسی ایجاد می کند و شامل عناصر در صف می شود.
در اینجا ما مثال BlockingQueueExample را داریم. ما یک صف از ArrayBlockingQueue با ظرفیت یک عنصر و یک پرچم منصفانه ایجاد می کنیم. دو تاپیک شروع می شود. اولین آنها، رشته تولید کننده، پیام ها را از آرایه پیام ها با استفاده از روش put صف می کشد. نخ دوم، Consumer، با استفاده از روش take عناصر را از صف می خواند و آنها را در کنسول نمایش می دهد. ترتیب عناصر یک ترتیب طبیعی برای صف است.

import java.util.concurrent.*;
 
public class ArrayBlockingQueueExample {
 
   private BlockingQueue<Integer> blockingQueue;
   private final Integer[]  myArray = {1,2,3,4,5};
  
       public ArrayBlockingQueueExample ()
       { blockingQueue = new ArrayBlockingQueue<Integer>(1, true);
           (new Thread(new Producer())).start();
           (new Thread(new Consumer())).start();
       }
 
       class Producer implements Runnable
       {
           public void run() {
               try {
                   int counter = 0;
                   for (int i=0; i < myArray.length; i++) {
                       blockingQueue.put(myArray[i]);
                       if (counter++ < 2)
                           Thread.sleep(3000);
                   } blockingQueue.put(-1);
               }
               catch (InterruptedException e) {
                   System.err.println(e.getMessage());
               }
           }
       }
 
       class Consumer implements Runnable
       {
           public void run() {
               try {
                   Integer message = 0;
                   while (!((message = blockingQueue.take()).equals(-1)))
                       System.out.println(message);
               } catch (InterruptedException e) {
                   System.err.println(e.getMessage());
               }
           }
       }
 
       public static void main(String[] args) {
           new ArrayBlockingQueueExample();
       }
   }
خروجی صف به ترتیب طبیعی است. دو عنصر اول با تاخیر ظاهر می شوند. برای تقویت آموخته هایتان، پیشنهاد می کنیم یک درس ویدیویی از دوره جاوا ما تماشا کنید
  • Queue برای درج عناصر در انتهای صف استفاده می شود و از ابتدای صف حذف می شود. از مفهوم FIFO پیروی می کند.
  • Java Queue بخشی از مجموعه Framework است و رابط مجموعه را پیاده سازی می کند. بنابراین از تمام روش های رابط مجموعه مانند درج، حذف و غیره پشتیبانی می کند.
  • متداول‌ترین پیاده‌سازی‌های Queue عبارتند از LinkedList، ArrayBlockingQueue و PriorityQueue.
  • عناصر صف اولویت بر اساس ترتیب طبیعی آنها یا توسط مقایسه کننده ای که در زمان ساخت صف ارائه می شود، بسته به سازنده ای که استفاده می شود، مرتب می شوند.
  • اگر عملیات تهی در BlockingQueues انجام شود، NullPointerException پرتاب می شود.
  • نظرات
    TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
    GO TO FULL VERSION