上一篇總結了一下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毫秒,由此可知,它是線性增長。
同樣,查詢也是如此。