每天被大廠面試折磨的我,看了這Java集合框架體系之後,我悟了

每天被大廠面試折磨的我,看了這Java集合框架體系之後,我悟了

一、集合類

集合的由來:

面嚮物件語言對事物都是以物件的形式來體現,為了方便對多個物件的操作,就需要將物件進行儲存,集合就是 儲存物件 最常用的一種方式。

集合特點:

用於儲存物件的容器。(容器本身就是一個物件,存在於堆記憶體中,裡面存的是物件的地址)

集合的長度是可變的。

集合中不可以儲存基本資料型別值。 (只能存物件)

小問題:

想用集合存基本資料型別怎麼辦?

裝箱、拆箱。

例:

al。add(5);// 相當於al。add(new Integer(5));

集合和陣列的區別:

陣列雖然也可以儲存物件,但長度是固定的,集合長度是可變的。

陣列中可以儲存基本資料型別,集合只能儲存物件。

集合框架的構成及分類:(虛線為介面)

每天被大廠面試折磨的我,看了這Java集合框架體系之後,我悟了

下面分別整理集合框架中的幾個頂層介面。

二、 Collection介面

Collection子介面以及常用實現類

Collection介面

|–List介面:有序(存入和取出的順序一致),元素都有索引(角標),元素可以重複。

|–Vector:內部是陣列資料結構,是同步的。增刪,查詢都很慢!100%延長(幾乎不用了)

|–ArrayList:內部是陣列資料結構,是不同步的。

替代了Vector,查詢的速度快,增刪速度慢。50%延長。 (查詢時是從容器的第一個元素往後找,由於陣列的記憶體空間是連續的,所以查詢快;增刪的話所有元素記憶體地址都要改變,所以增刪慢。)

|–LinkedList:內部是 ***連結串列 * 資料結構,是不同步的。增刪元素的速度很快。

(同理,連結串列的記憶體空間是不連續的,所以查詢慢;增刪時只需改變單個指標的指向,所以快;)

|–Set介面:無序,元素不能重複。Set介面中的方法和Collection一致。

|–HashSet: 內部資料結構是雜湊表 ,是不同步的。

|–LinkedHashSet:內部資料結構是雜湊表和連結串列,是有順序的HashSet。

|–TreeSet:內部資料結構是有序的二叉樹,它的作用是提供有序的Set集合,是不同步的。

List介面:

有一個最大的共性特點就是都可以操作角標,所以LinkedList也是有索引的。list集合可以完成對元素的增刪改查。

Set和List的區別:

Set 介面例項儲存的是無序的,不重複的資料。List 介面例項儲存的是有序的,可以重複的元素 <最本質區別> 。

Set檢索效率低下,刪除和插入效率高,插入和刪除不會引起元素位置改變 。

List和陣列類似,可以動態增長,根據實際儲存的資料的長度自動增長List的長度。查詢元素效率高,插入刪除效率低,因為會引起其他元素位置改變 。

ArryList和Vector可變長度陣列的原理

當預設長度的陣列不夠儲存時,會建立一個新陣列。將原來陣列的內容複製到新的陣列當中,並將新增加的元素追加到複製完的陣列尾,如果仍然不夠重複上述動作。其中,ArryList的增加是以原來50%長度進行增加,而Vector是按照100%延長。

ArryList是執行緒不安全的,Vector是安全的

由於是否有鎖的判斷將影響效率,故Arrylist效率遠遠高於Vector。而且只要是常用的容器就不是同步的,因為同步效率比較低。

ArryList存取物件的一個小例子

Person p1 = new Person(“lisi1”,21); ArrayList al = new ArrayList(); al。add(p1); al。add(new Person(“lisi2”,22)); al。add(new Person(“lisi3”,23)); al。add(new Person(“lisi4”,24)); Iterator it = al。iterator(); while(it。hasNext()){// System。out。println(((Person) it。next())。getName()+“::”+((Person) it。next())。getAge());//錯誤方式:不能這樣取,next()一次指標會移動一次,會輸出“lisi1::22 lisi3::24” // 正確方式:拿到一個Person物件,然後取屬性。 Person p = (Person) it。next(); System。out。println(p。getName()+“——”+p。getAge()); }

HashSet之覆蓋hashCode方法和equals方法來保證元素唯一性

如何保證HashSet的元素唯一性呢?

是透過物件的hashCode和equals方法來完成物件唯一性的:

如果物件的hashCode值不同,那麼不用判斷equals方法,就直接儲存到雜湊表中。

如果物件的hashCode值相同,那麼要再次判斷物件的equals方法是否為true:

如果為true,視為相同元素,不存;如果為false,那麼視為不同元素,就進行儲存。

記住:如果物件要儲存到HashSet集合中,該物件必須覆蓋hashCode方法和equals方法。

一般情況下,如果定義的類會產生很多物件,比如人,學生,書,通常都需要覆蓋equals,hashCode方法,以建立物件判斷是否相同的依據。

例:往HashSet集合中儲存Person物件。如果姓名和年齡相同,視為同一個人,視為相同元素。

import java。util。HashSet;import java。util。Iterator; class Person { private String name; private int age; public Person(String name, int age) { this。name = name; this。age = age; } @Override public int hashCode() { // System。out。println(this+“……。hashCode”); return name。hashCode() + age * 27; // 乘以一個任意數,防止加了年齡以後HashCode仍相同 } @Override public boolean equals(Object obj) { // 健壯性判斷 if (this == obj) return true; if (!(obj instanceof Person)) throw new ClassCastException(“型別錯誤”); // System。out。println(this+“。。。。equals。。。。。”+obj); Person p = (Person) obj; return this。name。equals(p。name) && this。age == p。age; } public String getName() { return name; } public void setName(String name) { this。name = name; } public int getAge() { return age; } public void setAge(int age) { this。age = age; } public String toString() { return name + “:” + age; }} public class HashSetTest { public static void main(String[] args) { HashSet hs = new HashSet(); /* * HashSet集合資料結構是雜湊表,所以儲存元素的時候, * 使用的元素的hashCode方法來確定位置,如果位置相同,在透過元素的equals來確定是否相同。 * */ hs。add(new Person(“lisi4”, 24)); hs。add(new Person(“lisi7”, 27)); hs。add(new Person(“lisi1”, 21)); hs。add(new Person(“lisi9”, 29)); hs。add(new Person(“lisi7”, 27)); Iterator it = hs。iterator(); while (it。hasNext()) { Person p = (Person) it。next(); System。out。println(p); } }}

執行結果:

lisi1:21lisi9:29lisi4:24lisi7:27

TreeSet之判斷元素唯一性的兩種方式(如何排序)

TreeSet預設判斷元素唯一性的方式:

根據Conpare介面的比較方法conpareTo的返回結果是否是0,是0,就是相同元素,不存。

下面,我們給出兩種自定義判斷元素唯一性的方式:

方式一:

讓元素自身具備比較功能,即根據元素中的屬性來比較。採用這種方式需要元素實現Comparable介面,覆蓋compareTo方法。

例:

往TreeSet集合中儲存Person物件。如果姓名和年齡相同,視為同一個人,視為相同元素。

執行結果:

wangermazi:21zhangsan:22zhaosi:25lisi:27

可以看到,複寫compareTo方法後,元素根據age這個屬性進行了排序。

方式二:(開發用這個,掌握比較器的用法)

讓集合自身具備比較功能。自己寫一個比較器,先定義一個類實現Comparator介面,覆蓋compare方法。然後將該類物件作為引數傳遞給TreeSet集合的建構函式。

不再需要元素實現Conparable介面。

step1-新建比較器類ComparedByName.java,覆蓋compare方法:

import java。util。Comparator; public class ComparedByName implements Comparator { @Override public int compare(Object o1, Object o2) { // TODO Auto-generated method stub Person p1 = (Person) o1; Person p2 = (Person) o2; int temp = p1。name。compareTo(p2。name); return temp == 0 ? p1。age - p2。age : temp; }}

step2-將比較器類類物件作為引數傳遞給TreeSet集合的建構函式:

import java。util。Iterator;import java。util。TreeSet; class Person implements Comparable { public String name; public int age; public Person() { super(); } public Person(String name, int age) { super(); this。name = name; this。age = age; } public String toString() { return name + “:” + age; } @Override public int compareTo(Object o) { Person p = (Person) o; /* 敲黑板劃重點,程式碼簡潔方式 */ int temp = this。age - p。age; return temp == 0 ? this。name。compareTo(p。name) : temp; // 上面這兩句相當於底下這一段的簡潔形式 // if (this。age > p。age) // return 1; // if (this。age < p。age) // return -1; // else { // return this。name。compareTo(p。name); // } } public static void main(String[] args) { TreeSet ts = new TreeSet(new ComparedByName()); ts。add(new Person(“zhangsan”, 22)); ts。add(new Person(“lisi”, 27)); ts。add(new Person(“wangermazi”, 21)); ts。add(new Person(“zhaosi”, 25)); Iterator it = ts。iterator(); while (it。hasNext()) { Person person = (Person) it。next(); System。out。println(person。toString()); } }}

執行結果:

lisi:27wangermazi:21zhangsan:22zhaosi:25

這次我們的比較器是根據元素屬性name進行排序的,複寫的compareTo方法是根據age進行排序的。

可以看到,當兩種方法同時存在時,是按照比較器的方法來排序的。

思考:

如何透過這種方式實現先進先出和先進後出?

讓比較器直接返回1或-1即可。

三、Iterator介面

對 Collection 進行迭代的迭代器,即對所有的Collection容器進行元素取出的公共介面。

該迭代器物件依賴於具體容器,因為每一個容器的資料結構都不同,所以該迭代器物件是在具體容器中進行內部實現的。(內部類,可以看具體容器的原始碼)

對於使用容器者而言,具體的實現方法不重要,只要透過具體容器獲取到該實現的迭代器的物件即可,也就是

iterator()

方法,而不用new。

(Iterator ite=list。iterator();)

每天被大廠面試折磨的我,看了這Java集合框架體系之後,我悟了

小知識點:使用迭代器過程中while和for的區別

第一種Iterator ite=list。iterator(); while(ite。hasNext())//判斷下一個元素之後有值 { System。out。println(ite。next()); }第二種Iterator ite=list。iterator();for(Iterator it = coll。iterator(); it。hasNext(); ){ System。out。println(it。next()); }

第一種方法while迴圈結束後迭代器物件還在記憶體中存在,還能繼續使用迭代器物件。

第二種方法for迴圈結束後迭代器物件就消失了,清理了記憶體,開發中第二種常用。

Iterator的一個子介面

|–ListIterator介面(列表迭代器)

應用場景:

顧名思義,只能用於List的迭代器。

在使用迭代器迭代的過程中需要使用集合中的方法操作元素,出現ConcurrentModificationException異常時,具體看下面的例子。

出現異常情況程式碼:

Iterator it = list。iterator(); while(it。hasNext()){ Object obj = it。next();//java。util。ConcurrentModificationException //在使用迭代器的過程中使用集合中的方法add()操作元素,出現異常。 //可以使用Iterator介面的子介面ListIterator來完成在迭代中對元素進行更多的操作。 if(obj。equals(“abc2”)){ list。add(“abc9”); } else System。out。println(“next:”+obj); } System。out。println(list);

解決辦法程式碼:

public static void main(String[] args) { List list = new ArrayList(); list。add(“abc1”); list。add(“abc2”); list。add(“abc3”); System。out。println(“list:”+list); ListIterator it = list。listIterator();//獲取列表迭代器物件 //它可以實現在迭代過程中完成對元素的增刪改查。 //注意:只有list集合具備該迭代功能。 while(it。hasNext()){ Object obj = it。next(); if(obj。equals(“abc2”)){ it。add(“abc9”); //ListIterator提供了add方法 } }

四、Map介面

Map介面與Set類似,可以對照著來學,比如比較器在TreeMap中也適用。

Map:

一次新增一對元素,Collection 一次新增一個元素。

Map也稱為雙列集合,Collection集合也稱為單列集合。

其實map集合中儲存的就是鍵值對,map集合中必須保證鍵的唯一性。

常用方法:

1,新增

value put(key,value):返回前一個和key關聯的值,如果沒有返回null。

2,刪除

void clear():清空map集合。

value remove(key):根據指定的key翻出這個鍵值對。

3,判斷

boolean containsKey(key):是否包含該key

boolean containsValue(value):是否包含該value

boolean isEmpty();是否為空

4,獲取

value get(key):透過鍵獲取值,如果沒有該鍵返回null。當然,可以透過是否返回null,來判斷是否包含指定鍵。

int size(): 獲取鍵值對的個數。

Map常用的子類:(HashMap與Hashtable的區別,面試常問)

|–Hashtable :內部結構是雜湊表,是同步的。不允許null作為鍵,null作為值。

|–Properties:用來儲存鍵值對型的配置檔案的資訊,可以和IO技術相結合。

|–HashMap : 內部結構是雜湊表,不是同步的。允許null作為鍵,null作為值。

|–TreeMap : 內部結構是二叉樹,不是同步的。可以對Map集合中的鍵進行排序。

Map的迭代方法

Map本身沒有迭代器。

方法一:

利用Map介面的values()方法,返回此對映中包含的值的 Collection (值不唯一),

然後透過Collecion的迭代器進行迭代。(只需要Value,不需要Key的時候)

public class MapDemo { public static void main(String[] args) { Map map = new HashMap(); method_2(map); } public static void method_2(Map map){ map。put(8,“zhaoliu”); map。put(2,“zhaoliu”); map。put(7,“xiaoqiang”); map。put(6,“wangcai”); Collection values = map。values(); Iterator it2 = values。iterator(); while(it2。hasNext()){ System。out。println(it2。next()); } }}

方法二:

透過keySet方法獲取map中所有的鍵所在的Set集合(Key和Set的都具有唯一性),

再透過Set的迭代器獲取到每一個鍵,再對每一個鍵透過Map集合的get方法獲取其對應的值即可。

Set keySet = map。keySet();Iterator it = keySet。iterator(); while(it。hasNext()){ Integer key = it。next(); String value = map。get(key); System。out。println(key+“:”+value); }

方法三:

利用Map的內部介面Map。Entry使用iterator。

透過Map的entrySet()方法,將鍵和值的對映關係作為物件儲存到Set集合中。

這個對映關係的型別就是Map。Entry型別(結婚證)。

再透過Map。Entry物件的getKey和getValue獲取其中的鍵和值。

Set> entrySet = map。entrySet(); Iterator> it = entrySet。iterator(); while(it。hasNext()){ Map。Entry me = it。next(); Integer key = me。getKey(); String value = me。getValue(); System。out。println(key+“:”+value); }

方法四:

透過Map。entrySet()方法遍歷key和value(推薦,尤其是容量大時)

for (Map。Entry entry : map。entrySet()) { System。out。println(“key= ” + entry。getKey() + “ and value= ” + entry。getValue()); }

map中比較器的用法(百度面試題)

百度考到過HashMap中怎麼按value來排序。

和Set中比較器的用法類似,這裡我們用內部類的形式來實現比較器。簡單的例子涵蓋了很多知識點。

1 public class HashMapTest { 2 // 將內部內修改為靜態,直接可以在main函式中建立內部類例項 3 private static class ValueComparator implements Comparator> { 4 @Override 5 public int compare(Map。Entry entryA, Map。Entry entryB) { 6 // 複寫的方法是compare,String類的方法是compareTo,不要記混。 7 return entryA。getValue()。compareTo(entryB。getValue()); 8 } 9 }10 11 public static void main(String[] args) {12 Map map = new HashMap<>();13 map。put(‘c’, “3”);14 map。put(‘a’, “5”);15 map。put(‘b’, “1”);16 map。put(‘d’, “2”);17 System。out。println(“Before Sort:”);18 for (Map。Entry mapping : map。entrySet()) {19 System。out。println(mapping。getKey() + “:” + mapping。getValue());20 }21 22 List> list = new ArrayList<>(map。entrySet());23 // 或者list。addAll(map。entrySet());24 ValueComparator vc = new ValueComparator();25 Collections。sort(list, vc);26 27 System。out。println(“After Sort:”);28 for (Map。Entry mapping : list) {29 System。out。println(mapping。getKey() + “:” + mapping。getValue());30 }31 }32 }

五、集合框架工具類Collections和Arrays

Collections是集合框架的工具類,裡面的方法都是靜態的。

例1:

根據字串長度的正序和倒序排序。

用到比較器的地方都可以用Collections。reverseOrder()。

每天被大廠面試折磨的我,看了這Java集合框架體系之後,我悟了

比較器ComparatorByLength。java:

import java。util。Comparator; public class ComparatorByLength implements Comparator { @Override public int compare(String o1, String o2) { int temp = o1。length() - o2。length(); return temp==0?o1。compareTo(o2): temp; }}

Demo:

public static void demo_3() { // reverse實現原理 /* * TreeSet ts = new TreeSet(new Comparator() { @Override public int compare(String o1, String o2) { int temp = o2。compareTo(o1); return temp; } }); */ TreeSet treeset = new TreeSet(new ComparatorByLength()); treeset。add(“abc”); treeset。add(“hahaha”); treeset。add(“zzz”); treeset。add(“aa”); treeset。add(“cba”); System。out。println(treeset); TreeSet ts = new TreeSet(Collections。reverseOrder(new ComparatorByLength()));//都是靜態方法,直接類名呼叫 ts。add(“abc”); ts。add(“hahaha”); ts。add(“zzz”); ts。add(“aa”); ts。add(“cba”); System。out。println(“after reverse:\t” + ts); }public static void main(String[] args) { demo_3();}

執行結果

例2:用工具類Collections。sort()進行排序:

public static void demo_2() { List list = new ArrayList(); list。add(“abcde”); list。add(“cba”); list。add(“aa”); list。add(“zzz”); list。add(“cba”); list。add(“nbaa”); System。out。println(list); Collections。sort(list); System。out。println(“after sort:\n” + list); Collections。sort(list, Collections。reverseOrder()); System。out。println(“after reverse sort:\n” + list); int index = Collections。binarySearch(list, “cba”); System。out。println(“index=” + index); // 獲取最大值。 String max = Collections。max(list, new ComparatorByLength()); System。out。println(“maxLength=” + max); } public static void main(String[] args) { demo_2(); }

執行結果

[abcde, cba, aa, zzz, cba, nbaa]after sort:[aa, abcde, cba, cba, nbaa, zzz]after reverse sort:[zzz, nbaa, cba, cba, abcde, aa]index=2maxLength=abcde

例3:

給非同步的集合加鎖,方法太多就不一一列舉了,自己檢視API。(掌握,面試會問到)

每天被大廠面試折磨的我,看了這Java集合框架體系之後,我悟了

返回指定 set 支援的同步(執行緒安全的)set。 |

簡單說一下給集合加鎖的思想。

List list = new ArrayList();// 非同步的list。 list=MyCollections。synList(list);// 返回一個同步的list。 class MyCollections{ /** * 返回一個加鎖的List * */ public static List synList(List list){ return new MyList(list); } // 內部類 private class MyList implements List{ private List list; private static final Object lock = new Object(); MyList(List list){ this。list = list; } public boolean add(Object obj){ synchronized(lock) { return list。add(obj); } } public boolean remove(Object obj){ synchronized(lock) { return list。remove(obj); } } }}

例4:

將集合轉成陣列,Arrays。asList()方法 (掌握)

應用場景:陣列方法有限,需要使用集合中的方法運算元組元素時。

注意1:

陣列的長度是固定的,所以對於集合的增刪方法(add()和remove())是不能使用的。

Demo:

public static void demo_1() { String[] arr = { “abc”, “haha”, “xixi” }; List list = Arrays。asList(arr); boolean b1 = list。contains(“xixi”); System。out。println(“list contains:” + b1); // list。add(“hiahia”);//引發UnsupportedOperationException System。out。println(list);}

執行結果

list contains:true[abc, haha, xixi]

注意2:

如果陣列中的元素是物件(包裝器型別),那麼轉成集合時,直接將陣列中的元素作為集合中的元素進行集合儲存。(比如上面那個Demo)

如果陣列中的元素是基本資料型別,那麼會將該 陣列 作為集合中的元素進行儲存。(比如下面這個Demo)

Demo:

public static void demo_2() { /* * 如果陣列中的元素是物件,那麼轉成集合時,直接將陣列中的元素作為集合中的元素進行集合儲存。 * * 如果陣列中的元素是基本型別數值,那麼會將該陣列作為集合中的元素進行儲存。 * */ int[] arr = { 31, 11, 51, 61 }; List list = Arrays。asList(arr); System。out。println(list); System。out。println(“陣列的長度為:” + list。size());}

執行結果

[[I@659e0bfd]陣列的長度為:1複製程式碼

由結果可以看出,當陣列中的元素時int型別時,集合中存的元素是整個陣列,集合的長度為1而不是4。

** 例5:將陣列轉成集合,List。toArray()方法**

每天被大廠面試折磨的我,看了這Java集合框架體系之後,我悟了

應用場景:對集合中的元素操作的方法進行限定,不允許對其進行增刪時。

注意:

toArray方法需要傳入一個指定型別的陣列,陣列的長度如何定義呢?

如果定義的陣列長度小於集合的size,那麼該方法會建立一個同類型並和集合相同size的陣列。

如果定義的陣列長度大於集合的size,那麼該方法就會使用指定的陣列,儲存集合中的元素,其他位置預設為null。

所以,一般將陣列的長度定義為集合的size。

Demo:

public class ToArray { public static void main(String[] args) { List list = new ArrayList(); list。add(“abc1”); list。add(“abc2”); list。add(“abc3”); String[] arr = list。toArray(new String[list。size()]); System。out。println(Arrays。toString(arr)); }}

例6:foreach語句

應用場景:遍歷陣列或Collection單列集合。

對陣列的遍歷如果僅僅是獲取陣列中的元素用foreach可以簡化程式碼,如果要對陣列的角標進行操作建議使用傳統for迴圈。

格式:

for(型別 變數 :Collection集合**|陣列) {

}

Demo:

public class ForEachDemo { public static void main(String[] args) { // 遍歷陣列 int[] arr = { 3, 1, 5, 7, 4 }; for (int i : arr) { System。out。println(i); } //遍歷List List list = new ArrayList(); list。add(“abc1”); list。add(“abc2”); list。add(“abc3”); for (String s : list) { System。out。println(s); } // 遍歷map // 可以使用高階for遍歷map集合嗎?不能直接用,但是將map轉成單列的set,就可以用了。 Map map = new HashMap(); map。put(3, “zhagsan”); map。put(1, “wangyi”); map。put(7, “wagnwu”); map。put(4, “zhagsansan”); for (Integer key : map。keySet()) { String value = map。get(key); System。out。println(key + “::” + value); } for (Map。Entry me : map。entrySet()) { Integer key = me。getKey(); String value = me。getValue(); System。out。println(key + “:” + value); } // 老式的迭代器寫法 Iterator it = list。iterator(); while (it。hasNext()) { System。out。println(it。next()); } }}

最後,感謝大家的觀看,謝謝

如果文章有問題可以關注私信我交流更改,或在評論區互相交流。