原始碼分析篇--Java集合操作(1)
一、集合框架
1、集合框架體系圖
2、集合的概念
Java集合是使程式能夠儲存和操縱元素不固定的一組資料。 所有Java集合類都位於java.uti包中。與Java陣列不同,Java集合中不能存放基本資料型別,只能存放物件的引用。但是在JDK5.0以後的版本當中,JAVA增加了“自動裝箱”和“自動拆箱”的機制,比如如果要存入一個INT型別的資料,JVM會把資料包裝成Integer然後再存入集合,看起來集合能夠存入基本資料型別,其實是不能的只是多了一個包裝資料的過程。我們來看個例子,驗證一下集合存放的是物件的引用(記憶體地址,什麼是記憶體地址呢?記憶體地址就是計算機中使用16進位制或其他進位制來對映一個字元,這個進位制數就是實際字元在記憶體中所對映的地址,記憶體地址是記憶體當中儲存資料的一個標識,並不是資料本身,通過記憶體地址可以找到記憶體當中儲存的資料。),而不是物件資料本身:
package com.yzh.maven.main;
import java.util.ArrayList;
import java.util.List;
import com.yzh.maven.entity.UserInfo;
public class CollectionTest{
private static Integer[] arr = null;
public static void main(String[] args) {
///////////////////////list元素的新增/////////////////////////
List<UserInfo> list = new ArrayList<UserInfo>();
UserInfo u = null;
for(int i = 0;i<5;i++){
u = new UserInfo();
u.setUserName("yzh"+i);
u.setPassword("123"+i);
list.add(u);
}
for(int i = 0;i<list.size();i++){
//System.out.println(System.identityHashCode(list.get(i)));
System.out.println(list.get(i));
}
List<Integer> list2 = new ArrayList<Integer>();
for(int i = 0;i<5;i++){
list2.add(i);
}
System.out.println(list2.get(0).getClass());
}
}
output:
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
class java.lang.Integer
注意:
① System.out.println(xx),括號裡面的“xx”如果不是String型別的話,就自動呼叫xx的toString()方法。
② Java中列印物件記憶體地址
Object的hashCode()預設是返回記憶體地址的,但是hashCode()可以重寫,所以hashCode()不能代表記憶體地址的不同。System.identityHashCode(Object)方法可以返回物件的記憶體地址,不管該物件的類是否重寫了hashCode()方法。
3、集合與陣列的區別
陣列是最常見的鏈式儲存結構,它是一段連續的記憶體空間,在記憶體中我們可以簡單表示為下圖樣式
注意:連結串列與陣列是兩種不同的資料結構,資料結構可以分為線性結構和非線性結構,線上性結構中,儲存方式又分為連續儲存(陣列)和離散儲存(連結串列),例如棧和佇列都是線性結構常見的應用。我們可以將陣列簡單地理解為一種線性表資料結構(線性表是動態的),因為陣列一旦定義了,其長度就不可以更改了,也就是不可以做增刪操作,但可以做修改和查詢操作。不存在什麼先進先出的含義。
用陣列存放一堆相同型別物件也是一個不錯的選擇,但是有一個很大的缺陷,那就是陣列大小隻能是固定的,不能從數組裡動態新增和刪除一個物件,要擴容的時候,就只能新建一個數組然後把原來的物件全部複製到新的數組裡,而且只能存放相同型別的物件,使用起來不夠靈活。下面我們來使用陣列實現擴容,如下所示。
package com.yzh.maven.main;
public class CollectionTest3 {
//初始化元素陣列
private static Object[] src = new Object[1];
private static int len = src.length;
//目標陣列
private static Object[] to = new Object[len];
//記錄新增元素的個數
private static int countTemp = 0;
//中間陣列
private static Object[] obj = new Object[len];
public static void main(String[] args) {
for(int i = 0;i<12;i++){
add("AA"+i);
}
for(Object o:to){
System.out.print(o+" ");
}
System.out.println("\n原素個數"+size());
}
public static void add(Object i){
int temp = src.length - 1;
//最後一個元素不為空,說明陣列元素已經滿了,就擴容
if(src[temp] != null){
//在擴充套件前需要儲存to陣列的資料
System.arraycopy(to, 0, obj, 0,len);
//擴容
len = len+1;
//動態擴充套件陣列,增加陣列容量
to = new Object[len];
//將obj陣列從0開始的元素拷貝to陣列到從0-第len-1個元素的位置,從obj中間陣列中獲取資料到to陣列
System.arraycopy(obj, 0, to, 0,len-1);
//新增元素
to[len-1]=i;
//克隆陣列是為了每次使長度增1
obj = to.clone();
//如果是第一個元素(陣列元素未滿),也就是最後一個元素為空
}else{
src[0] = i;
System.arraycopy(src, 0, to, 0, countTemp+1);
}
countTemp++;
return;
}
public static int size(){
return countTemp;
}
}
/**output:
AA0 AA1 AA2 AA3 AA4 AA5 AA6 AA7 AA8 AA9 AA10 AA11
12
*/
實際上,集合的底層是一個動態陣列,原理類似於上面的情形。可以判斷的是,在List集合的底層方法中,一定使用了arraycopy()方法來實現動態陣列擴容。陣列擴容的大概思路是,先判斷陣列的最後一個元素是否有值,如果有值,說明陣列溢滿,陣列需要擴容才能新增元素了;如果沒值,說明陣列沒滿,就可以繼續新增元素。在擴容時,需要定義一箇中間陣列來儲存未擴容的目標陣列的資料,否則會丟失記憶體中原有的資料,而且需要將中間陣列的長度也要進行擴容,例如obj = to.clone();只是為了擴充套件中間陣列容量而已,還可以將其改寫成obj = new Object[len];來達到擴充套件中間陣列容量。使用陣列進行容量擴充套件是一件很麻煩的事,但是,我們使用已經封裝好的List的實現類就不一樣了:
package com.yzh.maven.main;
import java.util.ArrayList;
import java.util.List;
public class CollectionTest5 {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
for(int i = 0;i<12;i++){
list.add("AA"+i);
}
for(Object o:list){
System.out.print(o+" ");
}
}
}
/**output:
AA0 AA1 AA2 AA3 AA4 AA5 AA6 AA7 AA8 AA9 AA10 AA11
*/
我們看到,實現的效果和上面的一樣,但是相比陣列的操作就簡單多了。