John Squirrels
مرحله
San Francisco

Java LinkedList

در گروه منتشر شد
سلام! تمام آخرین درس ها به ArrayList اختصاص داده شده است . این ساختار داده بسیار راحت و مفید است. می تواند وظایف زیادی را انجام دهد. اما جاوا دارای بسیاری از ساختارهای داده دیگر است. چرا؟ مهمتر از همه، زیرا دامنه وظایف بسیار زیاد است و کارآمدترین ساختارهای داده برای وظایف مختلف متفاوت است. امروز با یک ساختار جدید آشنا خواهیم شد: Java LinkedList ، یک لیست با پیوند دوگانه.
LinkedList - 1
بیایید ببینیم چگونه سازماندهی شده است، چرا به آن پیوند دوگانه می گویند، چه تفاوتی با ArrayList دارد . عناصر موجود در Java LinkedList در واقع پیوندهایی در یک زنجیره واحد هستند. علاوه بر داده ها، هر عنصر ارجاعاتی به عناصر قبلی و بعدی را ذخیره می کند. این مراجع به شما امکان می دهد از یک عنصر به عنصر دیگر بروید. به این صورت یکی را ایجاد می کنید:
public class Main {

   public static void main(java.lang.String[] args) {

       String str1 = new String("Hello World!");
       String str2 = new String("My name is Earl");
       String str3 = new String("I love Java");
       String str4 = new String("I live in Canada");

       LinkedList<String> earlBio = new LinkedList<>();
       earlBio.add(str1);
       earlBio.add(str2);
       earlBio.add(str3);
       earlBio.add(str4);

       System.out.println(earlBio);

   }
}
خروجی: [سلام جهان! نام من ارل است، من عاشق جاوا هستم، من در کانادا زندگی می کنم] لیست ما به این صورت است: LinkedList - 2 بیایید ببینیم چگونه یک عنصر جدید اضافه کنیم. این کار با استفاده از متد add() انجام می شود .
earlBio.add(str2);
در نقطه ای از کد، لیست ما از یک عنصر تشکیل شده است: String str1 . بیایید ببینیم بعد در تصویر چه اتفاقی می‌افتد: LinkedList - 3 در نتیجه، str2 و str1 از طریق پیوندهای بعدی و قبلی ذخیره شده در این گره‌های لیست به هم پیوند می‌خورند : لینکد لیست - 4 اکنون باید ایده اصلی یک لیست دارای پیوند دوگانه را درک کنید. این زنجیره از پیوندها دقیقاً همان چیزی است که عناصر LinkedList را به یک لیست واحد تبدیل می کند. بر خلاف ArrayList ، LinkedList یک آرایه یا چیزی شبیه به آرایه در داخل ندارد. هر (خوب، بیشتر) کار با ArrayList به کار با آرایه داخلی خلاصه می شود. هر کاری با Java LinkedList به تغییر پیوندها خلاصه می شود. این را می توان با افزودن یک عنصر به وسط لیست به وضوح مشاهده کرد:
public class Main {

   public static void main(java.lang.String[] args) {

       String str1 = new String("Hello World!");
       String str2 = new String("My name is Earl");
       String str3 = new String("I love Java");
       String str4 = new String("I live in Canada");

       LinkedList<String> earlBio = new LinkedList<>();
       earlBio.add(str1);
       earlBio.add(str3);
       earlBio.add(1, str2);

       System.out.println(earlBio);

   }
}
همانطور که می بینید، متد ()add overloaded به شما امکان می دهد یک شاخص خاص برای یک آیتم جدید مشخص کنید. در این مورد، ما می خواهیم رشته str2 را بین str1 و str3 اضافه کنیم . این همان چیزی است که در داخل رخ می دهد: LinkedList - 5 پس از تغییر پیوندهای داخلی، str2 با موفقیت به لیست اضافه شد: LinkedList - 6 اکنون هر 3 عنصر متصل شده اند. شما می توانید از طریق پیوند بعدی از اولین عنصر در زنجیره به آخرین و دوباره برگردید. بنابراین، ما با درج نسبتا راحت هستیم، اما در مورد حذف عناصر چطور؟ اصل کار دقیقاً همین است. ما فقط پیوندهای موجود در دو عنصر "راست و چپ" عنصر در حال حذف را به روز می کنیم:
public class Main {

   public static void main(java.lang.String[] args) {

       String str1 = new String("Hello World!");
       String str2 = new String("My name is Earl");
       String str3 = new String("I love Java");
       String str4 = new String("I live in Canada");

       LinkedList<String> earlBio = new LinkedList<>();
       earlBio.add(str1);
       earlBio.add(str3);
       earlBio.add(1, str2);

       earlBio.remove(1);
       System.out.println(earlBio);
   }
}
اگر آیتم را با نمایه 1 حذف کنیم (در وسط لیست قرار دارد) این اتفاق می افتد: LinkedList - 7 پس از به روز رسانی پیوندها، نتیجه دلخواه را می گیریم: LinkedList - 8 برخلاف عملیات حذف در ArrayList ، در اینجا نیازی به جابجایی عناصر آرایه یا انجام آن نیست. هر چیزی از نوع ما فقط پیوندهای str1 و str3 را به روز می کنیم . آنها اکنون به یکدیگر اشاره می کنند، و str2 از زنجیره پیوندها " خارج شده " و دیگر بخشی از لیست نیست.

مروری بر روش ها

LinkedList روش های مشترک زیادی با ArrayList دارد . به عنوان مثال، هر دو کلاس متدهایی مانند add() ، remove() ، indexOf() ، clear() ، contain() (نشان می دهد که آیا یک آیتم در لیست قرار دارد)، set() (جایگزین عنصر موجود می شود) و اندازه() . اگرچه بسیاری از آنها به طور داخلی متفاوت عمل می کنند (همانطور که با add() و remove() دریافتیم ، نتیجه نهایی یکسان است. با این حال، LinkedList روش های جداگانه ای برای کار با ابتدا و انتهای لیست دارد که ArrayList آن را ندارد:
  • addFirst() , addLast() : این روش ها برای اضافه کردن یک عنصر به ابتدا/پایان لیست
public class Car {

   String model;

   public Car(String model) {
       this.model = model;
   }

   public static void main(String[] args) {
       LinkedList<Car> cars = new LinkedList<>();
       Car ferrari = new Car("Ferrari 360 Spider");
       Car bugatti = new Car("Bugatti Veyron");
       Car lambo = new Car("Lamborghini Diablo");
       Car ford = new Car("Ford Mondeo");
       Car fiat = new Car("Fiat Ducato");

       cars.add(ferrari);
       cars.add(bugatti);
       cars.add(lambo);
       System.out.println(cars);

       cars.addFirst(ford);
       cars.addLast(fiat);
       System.out.println(cars);
   }

   @Override
   public String toString() {
       return "Car{" +
               "model='" + model + '\'' +
               '}';
   }
}
خروجی: [Car{model='Ferrari 360 Spider'}, Car{model='Bugatti Veyron'}, Car{model='Lamborghini Diablo'}] [Car{model='Ford Mondeo'}، Car{model=' فراری 360 Spider'}, Car{model='Bugatti Veyron'}, Car{model='Lamborghini Diablo'}, Car{model='Fiat Ducato'}] ما با "فورد" در بالای لیست قرار گرفتیم . و "فیات" در پایان.
  • peekFirst() , peekLast() : متدها اولین/آخرین عنصر را در لیست برمیگردانند. در صورت خالی بودن لیست، آنها را باطل می کنند.
public static void main(String[] args) {
   LinkedList<Car> cars = new LinkedList<>();
   Car ferrari = new Car("Ferrari 360 Spider");
   Car bugatti = new Car("Bugatti Veyron");
   Car lambo = new Car("Lamborghini Diablo");

   cars.add(ferrari);
   cars.add(bugatti);
   cars.add(lambo);
   System.out.println(cars.peekFirst());
   System.out.println(cars.peekLast());
}
خروجی: Car{model='Ferrari 360 Spider'} Car{model='Lamborghini Diablo'}
  • pollFirst() , pollLast() : این متدها اولین/آخرین عنصر را در لیست برمی گرداند و آن را از لیست حذف می کند. در صورت خالی بودن لیست، آنها را باطل می کنند
public static void main(String[] args) {
   LinkedList<Car> cars = new LinkedList<>();
   Car ferrari = new Car("Ferrari 360 Spider");
   Car bugatti = new Car("Bugatti Veyron");
   Car lambo = new Car("Lamborghini Diablo");

   cars.add(ferrari);
   cars.add(bugatti);
   cars.add(lambo);
   System.out.println(cars.pollFirst());
   System.out.println(cars.pollLast());

   System.out.println ("What's on the list?");
   System.out.println(cars);
}
خروجی: Car{model='Ferrari 360 Spider'} Car{model='Lamborghini Diablo'} چه چیزی در لیست باقی مانده است؟ [Car{model='Bugatti Veyron'}]
  • toArray() : این متد یک آرایه حاوی آیتم های لیست را برمی گرداند
public static void main(String[] args) {
   LinkedList<Car> cars = new LinkedList<>();
   Car ferrari = new Car("Ferrari 360 Spider");
   Car bugatti = new Car("Bugatti Veyron");
   Car lambo = new Car("Lamborghini Diablo");

   cars.add(ferrari);
   cars.add(bugatti);
   cars.add(lambo);
   Car[] carsArray = cars.toArray(new Car[3]);
   System.out.println(Arrays.toString(carsArray));
}
خروجی: [Car{model='Ferrari 360 Spider'}، Car{model='Bugatti Veyron'}، Car{model='Lamborghini Diablo'}] اکنون می دانیم که LinkedList چگونه کار می کند و سازماندهی آن با ArrayList چگونه است . مزایای استفاده از LinkedList چیست ؟ مهمتر از همه، ما هنگام کار در وسط لیست سود می بریم. عملیات درج و حذف در وسط LinkedList بسیار ساده تر از ArrayList است . ما به سادگی پیوندهای عناصر همسایه را به روز می کنیم و عنصر ناخواسته از زنجیره پیوندها خارج می شود. اما در یک ArrayList ، ما باید
  • بررسی کنید که آیا فضای کافی وجود دارد (هنگام قرار دادن)
  • اگر نه، یک آرایه جدید ایجاد می کنیم و داده ها را در آنجا کپی می کنیم (هنگام درج)
  • ما عنصر را حذف/ وارد می کنیم و تمام عناصر دیگر را به سمت راست/چپ (بسته به نوع عملیات) منتقل می کنیم. و پیچیدگی این فرآیند به شدت به اندازه لیست بستگی دارد. کپی/انتقال 10 عنصر یک چیز است و انجام همین کار با یک میلیون عنصر کاملاً چیز دیگری است.
به عبارت دیگر، اگر عملیات درج/حذف در وسط لیست در برنامه شما رایج ترین باشد، LinkedList باید سریعتر از ArrayList باشد .

در تئوری

public class Main {

   public static void main(String[] args) {
       List<Integer> list = new LinkedList<>();

       for (int i = 0; i < 5_000_000; i++) {
           list.add(new Integer(i));
       }

       long start = System.currentTimeMillis();

       for (int i = 0; i < 100; i++) {
           list.add(2_000_000, new Integer(Integer.MAX_VALUE));
       }
       System.out.println("Time taken by LinkedList (in milliseconds) = " + (System.currentTimeMillis()-start));
   }
}
خروجی: زمان صرف شده توسط LinkedList (بر حسب میلی ثانیه) = 1873
public class Main {

   public static void main(String[] args) {
       List<Integer> list = new ArrayList<>();

       for (int i = 0; i < 5_000_000; i++) {
           list.add(new Integer(i));
       }

       long start = System.currentTimeMillis();

       for (int i = 0; i < 100; i++) {
           list.add(2_000_000, new Integer(Integer.MAX_VALUE));
       }
       System.out.println("Time taken by ArrayList (in milliseconds) = " + (System.currentTimeMillis()-start));
   }
}
خروجی: زمان صرف شده توسط ArrayList (بر حسب میلی ثانیه) = 181 که غیرمنتظره بود! ما عملیاتی را انجام دادیم که در آن LinkedList باید بسیار کارآمدتر باشد: درج 100 مورد در وسط یک لیست. و لیست ما بزرگ است: 5,000,000 عنصر. ArrayList باید چند میلیون مورد را با هر درج تغییر می داد! چگونه برنده شد؟ ابتدا، زمان مورد نیاز ArrayList برای دسترسی به عناصر ثابت است (ثابت). وقتی می نویسی
list.add(2_000_000, new Integer(Integer.MAX_VALUE));
سپس ArrayList [2_000_000] یک آدرس حافظه خاص است (در نهایت، لیست دارای یک آرایه داخلی است). اما، LinkedList آرایه ای ندارد. عنصر شماره 2_000_000 را در امتداد زنجیره پیوندها جستجو می کند. برای LinkedList، این یک آدرس حافظه نیست، بلکه پیوندی است که هنوز باید به آن دسترسی پیدا کنید: fistElement.next.next.next.next.next.next.next.next.next.next.next.next.next.next. next.next.next.next.next.next.next.next.next.next.next.next.next.next.next.next.next.next……… در نتیجه، در طی هر درج (حذف) در وسط لیست ، ArrayList از قبل آدرس دقیق حافظه را برای دسترسی می داند، اما LinkedList هنوز باید "به آنجا برسد". دوم، ساختار خود ArrayList وجود دارد . یک تابع داخلی ویژه ( System.arrayCopy() ) آرایه داخلی را گسترش می دهد و همه عناصر را کپی و جابجا می کند. بسیار سریع است، زیرا برای این کار خاص بهینه شده است. اما زمانی که مجبور نیستید به یک شاخص خاص "رسیدن" کنید، LinkedList برنده است. فرض کنید در همان ابتدای لیست درج می کنیم. بیایید سعی کنیم یک میلیون عنصر را در آنجا وارد کنیم:
public class Main {

   public static void main(String[] args) {
       getTimeMsOfInsert(new ArrayList());
       getTimeMsOfInsert(new LinkedList());
   }

   public static long getTimeMsOfInsert(List list) {
       // Write your code here
       Date currentTime = new Date();
       insert1000000(list);
       Date newTime = new Date();
       long msDelay = newTime.getTime() - currentTime.getTime(); // Calculate the difference
       System.out.println("The result in milliseconds: " + msDelay);
       return msDelay;

   }

   public static void insert1000000(List list) {
       for (int i = 0; i < 1000000; i++) {
           list.add(0, new Object());
       }
   }

}
خروجی: نتیجه در میلی ثانیه: 43448 نتیجه بر حسب میلی ثانیه: 107 اکنون نتیجه کاملا متفاوتی دریافت می کنیم! ArrayList بیش از 43 ثانیه را صرف قرار دادن یک میلیون آیتم در جلوی لیست کرد، در حالی که LinkedList توانست این کار را در 0.1 ثانیه انجام دهد! LinkedList در اینجا سود می برد، زیرا لازم نیست هر بار از زنجیره پیوندها تا وسط لیست عبور کند. فوراً شاخص مورد نیاز را در ابتدای لیست پیدا می کند، بنابراین الگوریتم متفاوت در حال حاضر یک مزیت است. :) در واقع، بحث " ArrayList در مقابل LinkedList " بسیار گسترده است و ما در سطح فعلی به عمق آن نخواهیم پرداخت. نکته اصلی که باید به خاطر بسپارید این است:
  • همه مزیت های نظری هر مجموعه خاصی همیشه در واقعیت کار نمی کند (ما این را با مثالی که در وسط لیست بود دیدیم)
  • هنگام انتخاب یک مجموعه، موضع شدیدی را اتخاذ نکنید (" ArrayList همیشه سریعتر است. از آن استفاده کنید و نمی توانید اشتباه کنید. هیچ کس مدت زیادی است که از LinkedList استفاده نکرده است").
اگرچه حتی نویسنده LinkedList ، Joshua Bloch، می گوید که این مورد است. :) با این حال، این دیدگاه به دور از 100٪ درست است، و ما خود را متقاعد کرده ایم. در مثال قبلی ما، LinkedList 400 (!) برابر سریعتر بود. نکته دیگر این است که واقعاً موقعیت‌های کمی وجود دارد که LinkedList بهترین انتخاب باشد. اما آنها وجود دارند و در لحظه مناسب LinkedList می تواند به شما پاداش بزرگی بدهد. آنچه را که در ابتدای درس گفتیم فراموش نکنید: کارآمدترین ساختارهای داده برای کارهای مختلف متفاوت هستند. غیرممکن است که مطمئن باشید 100٪ کدام ساختار داده بهتر است تا زمانی که همه شرایط کار خود را بدانید. بعداً در مورد این مجموعه ها بیشتر خواهید دانست که انتخاب را آسان تر می کند. اما ساده ترین و موثرترین گزینه همیشه یکسان است: هر دو را روی داده های واقعی استفاده شده در برنامه خود امتحان کنید. سپس خودتان خواهید دید که هر دو نوع لیست چگونه عمل می کنند و قطعاً اشتباه نخواهید کرد. :) برای تقویت آموخته هایتان، پیشنهاد می کنیم یک درس ویدیویی از دوره جاوا ما تماشا کنید
نظرات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION