Collection:List集合

上一篇總結了一下Collection中的常用方法,它是所有集合的超類,那實現它的子類,肯定會有它特殊的方法,現在再瞭解一下它的其中一個集合List。

List也是一個接口,它又被好多子類實現,以他的常用的子類ArrayList來學習他的基本方法。

ArrayList底層是以數組來實現的。它的add方法可以看一看。下面是源碼:

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!   初始化一個數組
        elementData[size++] = e;<span style="white-space:pre">		</span>//將對象e添加到第size+1個位置
        return true;
    }
    private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {<span style="white-space:pre">		</span>//如果集合是空的
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);      //創建一個數組,長度爲10,默認ArrayList創建大小爲10
        }

        ensureExplicitCapacity(minCapacity);
    }
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);執行到這兒,就是說添加的數據長度超過了默認長度10,然後新的大小爲10+10/2=15.下一次是15+15/2取整
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);     //重新初始化一個數組
    }
在搜狐面試,問過的一個問題,現在還記憶猶新:ArrayList底層是怎麼實現的,默認長度是多少?如果超出了範圍,會如何?所以,在複習的時候,我特別認真的看了一下他的源碼,所以,大概總結一下:ArrayList底層是以數組形式實現的,當添加一個元素時,相應的在數組中插入一條記錄,數組的特點是查找速度快,但不方便刪除和插入。如果要刪除或插入一個元素,相應的後面都要向前移動一位,效率較低。每次初始化一個ArrayList時,默認創建一個大小爲10的數組,當添加數量超過10條之後,繼續初始化當前數組,將數組長度增加到[10/2](取整)即15,當長度再次超過時,將數組增加到15+[15/2]=22,以此類推。

上面是ArrayList的add方法。ArrayList在Collection的基礎上,又添加了新的方法,常用的方法有:

E get(int index);       得到一個元素

E set(int index, E element); 更改指定位置的元素

void add(int index, E element); 向指定位置添加指定元素

E remove(int index); 移除指定位置的元素
int indexOf(Object o); 查找是否有該元素,返回出現的位置,如果不存在則返回-1

int lastIndexOf(Object o); 最後出現的位置

ListIterator<E> listIterator(); List的迭代器
List<E> subList(int fromIndex, int toIndex); 截取部分集合,返回截取後的集合

大部分都很好理解,其中indexOf(Object o)()和contains()很相似,前者返回查找後的位置,後者返回Boolean型。其中ListIterator<E> listIterator()比較特殊,它是一個List的迭代器,父類是Iterator,主要是在父類的基礎上增加了一部分功能。下面先用一個具體的例子來看看他所加的功能。

public class ListTest {
	public static void main(String[] args) {
		List<Integer> list = new ArrayList<Integer>();
		list.add(1);
		list.add(2);
		list.add(3);
		
		ListIterator<Integer> listIterator = list.listIterator();
		//Iterator方法
		while(listIterator.hasNext()){
			Integer a = listIterator.next();
			System.out.println(a);
			if (a==2) {
				listIterator.add(4);<span style="white-space:pre">		</span>//可以往集合中添加數據,添加位置在當前位置之後
			}
		}
		System.out.println("----------------------------");
		//ListIterator新增方法
		while(listIterator.hasPrevious()){		//逆向遍歷,返回是否有前一個元素
			System.out.println(listIterator.previous());	//前一個元素
		}
	}
}
輸出結果:

1
2
3
----------------------------
3
4
2
1
其實ListIterator的逆向遍歷是沒有太大意義的,如果不做正向遍歷,逆向遍歷不會輸出任何數據。

最後的一個比較重要的方法是List<E> subList(int fromIndex, int toIndex)()。我個人覺得這個方法挺重要的,因爲我前不久在研究彩票預測,其中要對集合中的數據進行截取,這個方法正好派上用場。主要是截取指定區間中的數據。用一個簡單例子說明一下:

public class ListTest {
	public static void main(String[] args) {
		List<Integer> list = new ArrayList<Integer>();
		list.add(1);
		list.add(2);
		list.add(3);
		list.add(4);
		list.add(5);
		List<Integer> subList = list.subList(1, 4);
		for (Integer integer : subList) {
			System.out.println(integer);
		}
	}
}

輸出結果 2 3 4.是從第i個位置開始到第j個位置,包含i但不包含j。

上面值得補充一點的是,indexOf(Object o)這個方法,如果我比較的是一個自定義對象,會是什麼情況呢?下面做個測試,然後跟進源碼進行分析一下:

public class Student {
	private String name;
	private Integer age;
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public Integer getAge() {
		return age;
	}
	public void setAge(Integer age) {
		this.age = age;
	}
	public Student(String name, Integer age) {
		super();
		this.name = name;
		this.age = age;
	}
	@Override
	public String toString() {
		return "Student [name=" + name + ", age=" + age + "]";
	}
}

public class ListTest {
	public static void main(String[] args) {
		List<Student> list = new ArrayList<Student>();
		Student stu1 = new Student("張三", 22);
		Student stu2 = new Student("李四", 27);
		Student stu3 = new Student("王五", 30);
		list.add(stu1);
		list.add(stu2);
		list.add(stu3);
		Student stu4 = new Student("李四", 27);
		System.out.println(list.indexOf(stu4));
	}
}

上面這個結果最後輸出的是-1,意思就是沒有找到這個Student對象,我跟進一下源碼去看看他的底層是如何實現的。

  public int indexOf(Object o) {
        if (o == null) {
            for (int i = 0; i < size; i++)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = 0; i < size; i++)
                if (o.equals(elementData[i]))        /比較兩個對象,這兒比較的是數組,如果要比較自定義對象,先重寫equals方法
                    return i;
        }
        return -1;
    }

看到源碼,主要起作用的是equals,而調用的equals方法是Object的,所以可以在自定義類中重寫equals方法去實現這一功能。在Student中添加如下代碼:

public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Student other = (Student) obj;
		if (age != other.age)
			return false;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		return true;
	}

現在再次運行,就返回了具體的位置。1.


List集合中出了ArrayList之外,還有LinkedList、Vector等。他們都實現了List接口,裏面大部分方法都是相同的,主要了解一下不同之處,再做個對比。

LinkedList:

ArrayList底層是以數組的形式去實現的,它的好處是方便查找,但是在刪除和插入時,效率會很低。所以,爲了更高效的去插入和刪除時,就出現了LinkedList。LinkedList的底層是以鏈表的形式去實現的。鏈表,當前指針指向下一個節點,如果要插入一個數據,首先獲取當前節點,將指針指向插入的新節點,再將插入的數據指向當前節點的下一個節點。舉個例子:A-->B-->C-->D,現在要在B後面C的前面插入一個E,那麼首先是得到B的指針,將B的指針指向E,然後再將E的指針指向C即可。這樣插入,後面的元素不需要移動,因此效率是比較高的。LinkedList底層的插入就是以這種方式去實現的。

LinkedList特有的方法:

 public void addFirst(E e) 

public void addLast(E e)

因爲LinkedList底層是鏈表形式去實現的,因此這兩個方法就很容易理解了。

Vector:

Vector和ArrayList底層實現是一樣的,他們的基本方法也是一模一樣的,但是,ArrayList是線程不同步的,效率比較高,而Vector是線程同步的,所以效率比較低,一般現在都很少去使用Vector。

剛剛說到LinkedList適合插入和刪除,而ArrayList適合查詢,LinkedList插入和刪除是線性的,因爲它只是在指定位置加入或刪除一個數據元素,其他元素不做相應的移動,而就插入和刪除來說,ArrayList是二次增長的。因爲它每次插入或者刪除一個元素後,後面的元素都需要相應的向前面移動一個位置。同樣查詢也是如此。下面我用代碼來說明一下它們之間的差距。

插入或刪除:

public class ArrayListDemo {
	public static void main(String[] args) {
		List<Integer> list1 = new ArrayList<Integer>();
		//往list1中添加200000的值
		for (int i = 0; i < 200000; i++) {
			list1.add(i);
		}
		
		long startTime = System.currentTimeMillis();		//獲取開始時間
		Iterator<Integer> iterator = list1.iterator();
		while(iterator.hasNext()){
			//刪除偶數項
			Integer next = iterator.next();
			if (next%2==0) {
				iterator.remove();
			}
		}
		long endTime = System.currentTimeMillis();			//獲取結束時間
		System.out.println(endTime-startTime);				//運行時間
	}
}
運行結果:1909毫秒

上面是ArrayList的刪除時間,再看看LinkedList的刪除時間

public class LinkedListDemo {
	public static void main(String[] args) {
		List<Integer> list1 = new LinkedList<Integer>();
		//往list1中添加200000的值
		for (int i = 0; i < 200000; i++) {
			list1.add(i);
		}
		
		long startTime = System.currentTimeMillis();		//獲取開始時間
		Iterator<Integer> iterator = list1.iterator();
		while(iterator.hasNext()){
			//刪除偶數項
			Integer next = iterator.next();
			if (next%2==0) {
				iterator.remove();
			}
		}
		long endTime = System.currentTimeMillis();			//獲取結束時間
		System.out.println(endTime-startTime);				//運行時間
	}
}
運行結果:6毫秒。

由此可以看出效率之高。我把List值提高一倍,看看兩個集合的時間,是不是一個是指數增長,一個是線性增長

ArrayList:8025毫秒,由此可知,它不是線性增長,當他的值增加到2倍後,所需要的時間,增加4倍。(由於計算機原因,時間有誤差)

LinkedList:12毫秒,由此可知,它是線性增長。

同樣,查詢也是如此。







發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章