350 likes | 437 Views
ArrayList. คล้าย array แต่ไม่เหมือน. ใช้ index ในการเข้าถึงสมาชิกแต่ละตัวได้ จัดการขยายขนาดได้เองโดยอัตโนมัติ มีเมธอดสำหรับใส่ของในอาร์เรย์ตรงไหนก็ได้ เมธอดนั้นเป็นไปตาม List Interface แต่ก็มีเมธอดของมันเองด้วย clone ensureCapacity trimToSize ใส่ได้แต่ Object เท่านั้น.
E N D
คล้าย array แต่ไม่เหมือน • ใช้ index ในการเข้าถึงสมาชิกแต่ละตัวได้ • จัดการขยายขนาดได้เองโดยอัตโนมัติ • มีเมธอดสำหรับใส่ของในอาร์เรย์ตรงไหนก็ได้ • เมธอดนั้นเป็นไปตาม List Interface แต่ก็มีเมธอดของมันเองด้วย • clone • ensureCapacity • trimToSize • ใส่ได้แต่ Object เท่านั้น
ตัวอย่างเมธอด • public ArrayList(int initialCapacity) • Throw IllegalArgumantException ถ้า initialCapacity <0 • ArrayList<String>name = new ArrayList<String>(100); • ถ้าเราไม่บอกขนาด อาร์เรย์ลิสต์ที่สร้างจะจุได้ 10 สมาชิก
คอนสตรัคเตอร์อีกแบบ • public ArrayList(Collection<? Extends E> c) • เอาของจาก c มาใส่ในอาร์เรย์ลิสต์ของเราที่สร้างใหม่ • ลำดับของสมาชิกก็เอาตามลำดับสมาชิกของ c • อาร์เรย์ลิสต์ที่สร้างจะมีขนาด 110% ของ c • Worst time = O(n) • ชนิดของสมาชิกจาก c ต้องเป็นชนิดเดียวกับในอาร์เรย์ลิสต์ หรือไม่ก็ต้องเป็นสับคลาส • ถ้าเรามี womanList ซึ่งจุของประเภท Woman ซึ่งเป็นสับคลาสของ People • เราสามารถสร้าง อาร์เรย์ลิสต์ของ People ได้จาก womanList • ArrayList<People>a = new ArrayList<People>(womanList);
จริงๆแล้วเป็นการสร้างพอยต์เตอร์ไปยังของใน c เท่านั้น นี่ถือว่าเป็น shallow copy แบบหนึ่ง • อีกแบบคือใช้เมธอด clone() • ArrayList<People>a = (ArrayList<People>)womanList.clone(); • Shallow copy มี reference ต่างหากจากตัวต้นฉบับ แต่ถ้าภายในมีออบเจ็กต์ พอยต์เตอร์ของออบเจ็กต์นั้นจะชี้ไปที่สิ่งเดียวกับต้นฉบับ • จะไม่เหมือนกับการใช้ a = womanList; เพราะอันนี้จะให้ a ชี้ไปที่ womanList เลย
Shallow copy womanList w1 w3 w2 a
womanList MRS.X w1 w2 w3 a MR.A.J. Shallow copy (2) • จากหน้าที่แล้ว ถ้าเราเรียกใช้ • womanList.add(new People(“MRS.X”)); กับ • a.add(new People(“MR.A.J.”)) จะได้
new ArrayList<People>(womanList); กับ • (ArrayList<People>)womanList.clone(); • จริงๆจะต่างกันนิดนึง • การโคลนจะได้จำนวนช่องอาร์เรย์เท่าเดิม • แต่การใช้ copy constructor อาร์เรย์จะมีขนาด 110% เทียบกับของเดิม
อาร์เรย์ธรรมดาก็มีการก็อปปี้อาร์เรย์ธรรมดาก็มีการก็อปปี้ • System.arraycopy(array1, i, array2, i2, 3); • ก็อปปี้จาก array1 ที่ indexi • ไปที่ array2 โดยเริ่มที่ index i2 • ก็อปปี้ไป 3 ตัว
public boolean add(E element) • เอาของใส่ท้ายอาร์เรย์ลิสต์ • Worst time = O(n), Avg time = constant • รีเทิร์น true เมื่อของได้ถูกใส่จริงๆ (แบบนี้ก็ true ตลอด)
public E get(int index) • รีเทิร์นสมาชิก ณ ตำแหน่งที่บอก • Throw indexOutOfBoundsException ถ้า size()<=index, หรือ index<0
public E set(int index, E element) • แทนที่สมาชิกตัวที่ index ด้วย element • รีเทิร์นสมาชิกเก่าที่เก็บที่ index นี้ • Throw indexOutOfBoundsException ถ้า size()<=index หรือ index<0 • Worst time = constant
public void add(int index, E element) • แทรก element เข้าไป ณ ตำแหน่ง index • สมาชิกตัวอื่นต้องเลื่อนตำแหน่งไป • Worst time = O(n) • Throw indexOutOfBoundsException ถ้า size()< index หรือ index<0
public E remove(int index) • เอาสมาชิก ณ ตำแหน่ง index ออกไป • สมาชิกข้างขวาต้องเลื่อนมาแทนที่ • รีเทิร์นสมาชิกตัวที่เอาออก • Throw indexOutOfBoundsException ถ้า size()<=index หรือ index<0
public int indexOf(Object element) • หาค่าที่เหมือนกับ element ตัวแรกในอาร์เรย์ลิสต์ • รีเทิร์น ดัชนีของค่านั้นในอาร์เรย์ลิสต์ หรือ -1 ถ้าหาไม่เจอ • เทียบค่าว่าเหมือนหรือไม่ด้วยเมธอด equals() • Worst time = O(n)
ArrayList class heading • public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable • มีฟิลด์ • private transient E[ ] elementData; • Transient หมายถึงไม่เซฟตัวอาร์เรย์ตอนทำ serialization แต่เซฟสมาชิกนะ • private int size;
ตัวอย่างโค้ดภายในคอนสตรัคเตอร์ตัวอย่างโค้ดภายในคอนสตรัคเตอร์ public ArrayList (int initialCapacity) { elementData = new Object [initialCapacity]; } public ArrayList ( ) { this (10); }
public ArrayList(Collection<? extends E> c) { this((c.size()*110)/100); // โตขึ้น 10% Iterator i = c.iterator( ); while (i.hasNext( )) elementData[size++] = i.next(); }
ตัวอย่างโค้ดภายใน เมธอด add(E element) public boolean add(E element){ ensureCapacity(size+1); //ขยายอาร์เรย์ถ้าจำเป็น elementData[size++] = element; return true; }
public void ensureCapacity(int min) { modcount++; //ดูหน้าถัดไป int oldCapacity = elementData.length; if(min>oldCapacity){ //ความจุใหม่ใหญ่กว่าเก่า E oldData[] = elementData; int newCapacity = (oldCapacity*3)/2+1; //ให้ขยาย 50% if(newCapacity < minCapacity) //ถ้ายังขยายไม่พอ newCapacity = min; // ก็ให้ขนาดตามอินพุตไปเลย elementData = (E) new Object[newCapacity]; System.arrayCopy(oldData,0,elementData,0,size); } }
modCount • มีในแต่ละคลาส ArrayList, LinkedList, TreeMap, TreeSet, HashMap, HashSet • ของ ArrayList นั้น inherit มาจาก AbstractList • เปลี่ยนค่าเมื่อมีการเปลี่ยนแปลงคือมีการ insert หรือ remove จาก ArrayList นี้ • อิเทอเรเตอร์แต่ละตัวก็มี expectedModCount ต่างหาก • เปลี่ยนค่าเมื่ออิเทอเรเตอร์ตัวนั้นเปลี่ยนของในคอลเลคชั่น เช่น itr.remove() • modCount ก็จะเปลี่ยนด้วย • ถ้าสองฟิลด์นี้ไม่เท่ากันเมื่อไร แสดงว่ามีการเปลี่ยนคอลเลคชั่นด้วยเมธอดที่ไม่ใช่ของอิเทอเรเตอร์ระหว่างที่อิเทอเรเตอร์มีการใช้งานอยู่ เช่นเปลี่ยนโดยเทรดอื่น • ทุกๆการเปลี่ยนแปลงของอิเทอเรเตอร์เอง จะมีการเรียกใช้ if (modCount!=expectedModCount) throw new ConcurrentModificationException(); Fail fast
modCount & expectedModCount (ต่อ) ตัวอย่างการใช้อิเทอเรเตอร์ที่ไม่ดี public ModCountDriver( ) { ArrayList list = new ArrayList( ); list.add (“yes”); Iterator itr = list. iterator( ); list.add (“good”); itr.next( ); // exception thrown at this point } สร้างอิเทอเรเตอร์ modCount++, แต่ expectedModCount เท่าเดิม next นี่จึงใช้ไม่ได้
เวลาของ ensureCapacity • Worst time = การก็อปปี้อาร์เรย์ = O(n) • Avg time = • ต้องเรียก add ไป n/3 ครั้งก่อน ถึงจะมีการก็อปปี้อาร์เรย์ • ฉะนั้น ต้องเรียก add ไป (n/3)+1 ครั้ง จึงจะเกิดการก็อปปี้ n สมาชิก • จำนวนการก็อปปี้ ต่อการเรียก add หนึ่งครั้งจึงเฉลี่ยเป็น n/((n/3)+1) • ประมาณ 3 ครั้ง • Avg time = constant
โค้ดของเมธอดโคลน public Object clone() { try { ArrayList v = (ArrayList)super.clone(); // copies size v.elementData = new Object[size]; System.arraycopy(elementData, 0, v.elementData, 0, size); v.modCount = 0; return v; } catch (CloneNotSupportedException e) { // this shouldn't happen, since we are Cloneable throw new InternalError(); } }
ตัวอย่างการใช้งาน สมมติเราต้องการทำอะไรต่างๆดังนี้ • สร้าง ArrayList ซึ่งมี n สมาชิก • ลูป n ครั้ง ใส่สมาชิกซึ่งเป็น Double (i) โดย i เริ่มจาก 0 ลงไปท้ายลิสต์ • ใส่ new Double (7.8) ที่ index n/2 • เอาสมาชิกที่ตำแหน่ง 2n / 3 ทิ้งไป • เอา 2.5 คูณกับสมาชิกตัวกลางของลิสต์ • พริ้นต์ค่าภายในลิสต์ทั้งหมดออกมา
1 2 • public void processInput (String s) { • int n = Integer.parseInt (s); • ArrayList myList = new ArrayList (n); • for (int i = 0; i < n; i++) • myList.add (new Double (i)); • myList.add (n / 2, new Double (7.8)); • myList.remove (2 * n / 3); • double d = ((Double)myList.get (n / 2)).doubleValue( ) • * 2.5; • myList.set (n / 2, new Double (d)); • gui.println (myList) ; • } 3 4 5 6
การใช้งาน ArrayList – VeryLongIntฟิลด์ในคลาส VeryLongInt • protected ArrayList<Integer> digits; • แต่ละสมาชิกจะมีเลขหนึ่งหลักเท่านั้น
การใช้งาน ArrayList –VeryLongInt class • public VeryLongInt(String s) • สร้าง VeryLongInt ขึ้นมาจาก String • Throw NullPointerException ถ้า s == null • VeryLongInt a = new VeryLongInt(“5629?3”); • จะได้ค่า 56293 เก็บไว้ใน a • Worst time = O(string length) ไม่ใช่เลขจึงทิ้งได้
โค้ดของ VeryLongInt(String s) • ลูปไปตามคาแร็กเตอร์แต่ละตัว • ถ้าคาแร็กเตอร์เป็นตัวเลข ให้ลบด้วย ‘0’เพื่อให้ได้ค่าแท้จริงออกมา • เอาเลขที่ได้ใส่อาร์เรย์ลิสต์
public VeryLongInt(String s){ final char LOWEST_DIGIT_CHAR = ‘0’; digits = new ArrayList<Integer>(s.length()); char c; int digit; for(int i = 0; i<s.length(); i++){ c = s.charAt(i); if(Character.isDigit(c)){ digit = c - LOWEST_DIGIT_CHAR; digits.add(digit); // auto convert } } } Avg time & worst time = O(n) Avg time = constant, Worst time = constant เพราะไม่มีการเปลี่ยนขนาด
public String toString() • Return String ที่แทน VeryLongInt object นี้ • 56293 จะกลายเป็น [5,6,2,9,3] • Worst time = O(จำนวนหลักเลข) public String toString(){ return digits.toString();//เรียกใช้เมธอดของอาร์เรย์ลิสต์ }
public void add(VeryLongInt otherVeryLong) • บวกสอง VeryLongInt • Worst time = O(จำนวนหลักเลขของตัวที่มีจำนวนหลักมากที่สุด) • Throw NullPointerException ถ้า otherVeryLong == null
โค้ดของ add(VeryLongInt otherVeryLong) • บวกทีละหลัก เริ่มจากหลักหน่วย (เหมือนบวกเลขปกติ) เอาผลที่บวกแต่ละหลักนั้นใส่อาร์เรย์ลิสต์อีกตัวที่ชื่อว่า sumDigits • ถ้าเรามี 135 กับ 79 เราจะได้ผลใน sumDigits เป็น 412 เพราะว่าหลักหน่วยถูกนำไปใส่ sumDigits เสียก่อน ดังนั้นต้องมีการรีเวิร์สสมาชิกใน sumDigits • จะได้ 214 เป็นคำตอบที่แท้จริง
public void add(VeryLongInt otherVeryLong){ final int BASE = 10; int largerSize, partialSum, carry = 0; ArrayList<Integer>sumDigits= new ArrayList<Integer>(); if(digits.size()>otherVeryLong.digits.size()) largerSize= digits.size(); else largerSize = otherVeryLong.digits.size(); for(int i=0; i<largerSize; i++){ partialSum = least(i) + otherVeryLong.least(i) + carry; carry = partialSum / BASE ; sumDigits.add(partialSum % BASE); } if(carry==1) sumDigits.add(carry); //ไงๆหลักสูงสุดก็มี carry ได้ค่าเดียว Collections.reverse(sumDigits); digits = sumDigits; } หลักที่ i เริ่มจากหลักหน่วย O(n) Constant time
protected int least(int i){ if(i>=digits.size()) return 0; return digits.get(digits.size()-i-1); } Constant time