集合和數組中存放的都是對象的引用。html
看到array,就要想到角標。java
看到link,就要想到first,last。能夠保證存入的有序性。算法
看到hash,就要想到hashCode,equals.apache
看到tree,能夠按順序進行排列,就要想到兩個接口。Comparable(集合中元素實現這個接口,元素自身具有可比性),Comparator(比較器,傳入容器構造方法中,容器具有可比性)。基於紅黑樹實現。編程
數組的長度是固定的,能夠經過擴容擴大數組的長度,好比:api
package loader; import java.util.Arrays; public class Client { public static void main(String[] args) { Object[] objects = new Object[2]; System.out.println(objects.length); objects[0] = 1; objects[1] = 2; System.out.println(Arrays.toString(objects)); // 若是不擴容直接存入第三個值報數組越界(擴容方法一) objects = Arrays.copyOf(objects, 3); objects[2] = 3; System.out.println(Arrays.toString(objects)); // System.arraycopy(原數組名,起始下標,新數組名,起始下標,複製長度);(擴容方法二) Object[] objects2 = new Object[4]; System.arraycopy(objects, 0, objects2, 0, 3); objects2[3] = 4; System.out.println(Arrays.toString(objects2)); } }
結果:數組
2
[1, 2]
[1, 2, 3]
[1, 2, 3, 4]安全
解釋: (數組擴容的兩種方法在org.apache.commons.lang.ArrayUtils普遍使用)網絡
(1) 擴容方法一System.arraycopy解釋數據結構
System.arraycopy(objects, 0, objects2, 0, 3); 是一個native方法,是將objects數組從下標0開始複製到objects2數組的下標0開始,複製3個數據(至關於所有複製objects)
/* * @param src the source array. * @param srcPos starting position in the source array. * @param dest the destination array. * @param destPos starting position in the destination data. * @param length the number of array elements to be copied. * @exception IndexOutOfBoundsException if copying would cause * access of data outside array bounds. * @exception ArrayStoreException if an element in the <code>src</code> * array could not be stored into the <code>dest</code> array * because of a type mismatch. * @exception NullPointerException if either <code>src</code> or * <code>dest</code> is <code>null</code>. */ public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);
(2) Arrays.copyOf (objects, 3)是至關於複製原來objects數組到一個新數組中,新數組的長度爲3。查看源碼內部也是調用System.arrayCopy, 以下:
public static <T> T[] copyOf(T[] original, int newLength) { return (T[]) copyOf(original, newLength, original.getClass()); } public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) { @SuppressWarnings("unchecked") T[] copy = ((Object)newType == (Object)Object[].class) ? (T[]) new Object[newLength] : (T[]) Array.newInstance(newType.getComponentType(), newLength); System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); return copy; }
補充:class.getComponentType 獲取的是數組的原始數據類型,而後經過反射的Array工具類能夠建立1個數組對象
class.getComponentType 源碼以下:
/** * Returns the {@code Class} representing the component type of an * array. If this class does not represent an array class this method * returns null. * * @return the {@code Class} representing the component type of this * class if this class is an array * @see java.lang.reflect.Array * @since JDK1.1 */ public native Class<?> getComponentType();
Array工具類的全部方法以下: (能夠改變或獲取數據對應下標的值)===結合apache的Arrayutils能夠知足對數組的基本操做
例如:
package loader; import java.lang.reflect.Array; public class Constants { public static void main(String[] args) { String[] strings = { "1", "2" }; System.out.println(strings.getClass().getComponentType()); String[] newInstance = (String[]) Array.newInstance(strings.getClass().getComponentType(), 3); System.out.println(newInstance); System.out.println(newInstance.length); } }
結果:
class java.lang.String
[Ljava.lang.String;@15db9742
3
非數組對象調用getComponentType的返回值是null,直接獲取到數組的class不能調用newInstance建立對象,沒有構造方法(init是調用初始化方法)。
public static void main(String[] args) throws InstantiationException, IllegalAccessException { // 非數組對象獲取不到原始數據類型 System.out.println(Object.class.getComponentType()); String[] strings = { "1", "2" }; String[] newInstance = strings.getClass().newInstance(); }
結果:報錯以下
Exception in thread "main" java.lang.InstantiationException: [Ljava.lang.String;
at java.lang.Class.newInstance(Class.java:427)
at loader.Constants.main(Constants.java:10)
Caused by: java.lang.NoSuchMethodException: [Ljava.lang.String;.<init>()
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.newInstance(Class.java:412)
... 1 more
null
基於上面的理解建立的擴容工具類:
package loader; import java.lang.reflect.Array; import java.util.Arrays; public class Client { public static void main(String[] args) throws InstantiationException, IllegalAccessException { Object[] objects = new Object[2]; objects[0] = 1; objects[1] = 2; objects = expandCapacity(objects, 4); System.out.println(Arrays.toString(objects)); objects = expandCapacity(objects, 1); System.out.println(Arrays.toString(objects)); } private static <U> U[] expandCapacity(U[] objects, int newLength) { Class<?> componentType = objects.getClass().getComponentType(); U[] newInstance = (U[]) Array.newInstance(componentType, newLength); System.arraycopy(objects, 0, newInstance, 0, Math.min(objects.length, newLength)); return newInstance; } }
結果:
[1, 2, null, null]
[1]
存儲對象的容器,面嚮對象語言對事物的體現都是以對象的形式,因此爲了方便對多個對象的操做,存儲對象,集合是存儲對象最經常使用的一種方式。
集合的出現就是爲了持有對象。集合中能夠存儲任意類型的對象, 並且長度可變。在程序中有可能沒法預先知道須要多少個對象, 那麼用數組來裝對象的話, 長度很差定義, 而集合解決了這樣的問題。
數組和集合類都是容器
數組長度是固定的,集合長度是可變的。數組中能夠存儲基本數據類型,集合只能存儲對象。數組中存儲數據類型是單一的,集合中能夠存儲任意類型的對象。
集合類的特色
用於存儲對象,長度是可變的,能夠存儲不一樣類型的對象。
存儲類型單一的數據容器,操做複雜(數組一旦聲明好不可變)CRUD
集合作什麼
1:將對象添加到集合
2:從集合中刪除對象
3: 從集合中查找一個對象
4:從集合中修改一個對象就是增刪改查
注意:集合和數組中存放的都是對象的引用而非對象自己
Java工程師對不一樣的容器進行了定義,雖然容器不一樣,可是仍是有一些共性能夠抽取最後抽取了一個頂層接口,那麼就造成了一個集合框架。如何學習呢?固然是從頂層學起,頂層裏邊具備最共性,最基本的行爲。具體的使用,就要選擇具體的容器了。爲何? 由於不斷向上抽取的東西有多是不能建立對象的.抽象的可能性很大,而且子類對象的方法更多一些. 因此是看頂層,建立底層。那麼集合的頂層是什麼呢 叫作Collection
集合框架體系
---|Collection: 單列集合 ---|List: 有存儲順序, 可重複 ---|ArrayList: 數組實現, 查找快, 增刪慢 因爲是數組實現, 在增和刪的時候會牽扯到數組增容, 以及拷貝元素. 因此慢。數組是能夠直接按索引查找, 因此查找時較快。初始大小是10,增加爲原來的1.5倍 ---|LinkedList: 雙向鏈表實現, 增刪快, 查找慢 因爲鏈表實現, 增長時只要讓前一個元素記住本身就能夠, 刪除時讓前一個元素記住後一個元素, 後一個元素記住前一個元素. 這樣的增刪效率較高但查詢時須要一個一個的遍歷, 因此效率較低。 ---|Vector: 和ArrayList原理相同, 但線程安全, 效率略低 和ArrayList實現方式相同, 但考慮了線程安全問題, 因此效率略低。初始大小是10,增加爲原來的2倍。 ---|Set: 無存儲順序, 不可重複。內部都是維護了一個map用於存儲數據,咱們add進去的內容都是做爲map的key,value是一個定值Object。 ---|HashSet--內部維護一個map,咱們存放的信息放在key,value就是一個固定的Object。HashMap的key不能夠重複(閱讀源碼的put方法,key重複會替換value值),因此其set不能夠重複(容許存null,且放在第一個位置)。當爲HashSet的時候map實際類型爲HashMap,當爲LinkedHashSet的時候map爲LinkedHashMap。 ---|TreeSet--內部經過TreeMap實現,因爲TreeMap的key能夠排序,所以Set能夠作到有序。TreeMap不容許key爲null,因此不能存null,null會直接報空指針異常。 ---|LinkedHashSet--繼承hashSet,且增長了本身的方法。將父類HashSet中的map初始化爲LinkedHashMap保存存入的順序,其保證順序的機制見LinkedHashMap的有序機制。容許值爲null。 ---| Map: 鍵值對 ---|HashMap 線程非安全,初始化16個數組大小。成倍增加。容許一個key爲null,多個value爲null(key爲null的時候存在數組的第一個位置table[0])。(HashMap的key不能夠重複,閱讀源碼的put方法,key重複會替換value值) ---|TreeMap------能夠按照key進行排序,前提是key元素實現Comparable接口或者給Treemap傳入一個實現Comparator的比較器。紅黑樹實現。不容許key爲null,容許value爲null(由於key要進行compare比較)。 ---|HashTable---線程安全,初始化11個數組大小,增加時2*old+1。key和value都不能是null。 ---|LinkedHashMap 保存了記錄的插入順序,繼承hashmap,實現Map接口。容許一個key爲null,多個value爲null。-----原理是其內部類 Entry維護了一個header和before記錄順序,沒有重寫put方法,重寫了addEntry()方法,由於HashMap的put方法中調用addEntry方法()。 |
爲何出現這麼多集合容器,由於每個容器對數據的存儲方式不一樣,這種存儲方式稱之爲數據結構(data structure)
注意
集合和數組中存放的都是對象的引用。
看到array,就要想到角標。
看到link,就要想到first,last。
看到hash,就要想到hashCode,equals.
看到tree,就要想到兩個接口。Comparable,Comparator。
Collection |
咱們須要保存若干個對象的時候使用集合。 |
List
|
若是咱們須要保留存儲順序, 而且保留重複元素, 使用List. 若是查詢較多, 那麼使用ArrayList--基於數組實現 若是存取較多, 那麼使用LinkedList--基於雙向鏈表實現 若是須要線程安全, 那麼使用Vector--基於數組實現,只是線程安全 |
Set
|
若是咱們不須要保留存儲順序, 而且須要去掉重複元素, 使用Set. 若是咱們須要將元素排序, 那麼使用TreeSet--內部基於SortedMap實現 若是咱們不須要排序, 使用HashSet, HashSet比--內部基於HashMap實現 TreeSet效率高. 若是咱們須要保留存儲順序, 又要過濾重複元素, 那麼使用LinkedHashSet--內部基於HashMap實現 |
1. Collection接口有兩個子接口:
List(鏈表|線性表)
Set(集)
特色:
Collection中描述的是集合共有的功能(CRUD)
List可存放重複元素,元素存取是有序的
Set不能夠存放重複元素,元素存取是無序的
java.util.Collection ---| Collection 描述全部接口的共性 ----| List接口 能夠有重複元素的集合 ----| Set 接口 不能夠有重複元素的集合 |
2. 學習集合對象
學習Collection中的共性方法,多個容器在不斷向上抽取就出現了該體系。發現Collection接口中具備全部容器都具有的共性方法。查閱API時,就能夠直接看該接口中的方法。並建立其子類對象對集合進行基本應用。當要使用集合對象中特有的方法,在查看子類具體內容。
查看api 文檔Collection在在java.util 中(注意是大寫Collection)
注意在現階段遇到的 E T 之類的類型,須要暫時理解爲object 由於涉及到了泛型.
3:建立集合對象,使用Collection中的List的具體實現類ArrayList
1:Collection coll=new Arraylist();
增長:
1:add() 將指定對象存儲到容器中
add 方法的參數類型是Object 便於接收任意對象
2:addAll() 將指定集合中的元素添加到調用該方法和集合中
刪除:
3:remove() 將指定的對象從集合中刪除
4:removeAll() 將指定集合中的元素刪除
修改
5:clear() 清空集合中的全部元素
判斷
6:isEmpty() 判斷集合是否爲空
7:contains() 判斷集合何中是否包含指定對象
8:containsAll() 判斷集合中是否包含指定集合
使用equals()判斷兩個對象是否相等
獲取: 9:int size() 返回集合容器的大小
轉成數組10: toArray() 集合轉換數組
public static void main(String[] args) { Collection list = new ArrayList(); // 增長:add() 將指定對象存儲到容器中 list.add("計算機網絡"); list.add("現代操做系統"); list.add("java編程思想"); System.out.println(list); // [計算機網絡, 現代操做系統, java編程思想] // 增長2 將list容器元素添加到list2容器中 Collection list2 = new ArrayList(); list2.add("java核心技術"); list2.addAll(list); list2.add("java語言程序設計"); System.out.println(list2); // [java核心技術, 計算機網絡, 現代操做系統, java編程思想, java語言程序設計] }
// 刪除1 remove boolean remove = list2.remove("java核心技術"); System.out.println(remove); // true System.out.println(list2); // // 刪除2 removeAll() 將list中的元素刪除 boolean removeAll = list2.removeAll(list); System.out.println(removeAll);// true System.out.println(list2);// [java語言程序設計]
public static void main(String[] args) { Collection list = new ArrayList(); // 增長:add() 將指定對象存儲到容器中 list.add("計算機網絡"); list.add("現代操做系統"); list.add("java編程思想"); list.add("java核心技術"); list.add("java語言程序設計"); System.out.println(list); // 修改 clear() 清空集合中的全部元素 list.clear(); System.out.println(list); //[] }
public static void main(String[] args) { Collection list = new ArrayList(); // 增長:add() 將指定對象存儲到容器中 list.add("計算機網絡"); list.add("現代操做系統"); list.add("java編程思想"); list.add("java核心技術"); list.add("java語言程序設計"); System.out.println(list); boolean empty = list.isEmpty(); System.out.println(empty);// false boolean contains = list.contains("java編程思想"); System.out.println(contains);// true Collection list2 = new ArrayList(); list2.add("水許傳"); boolean containsAll = list.containsAll(list2); System.out.println(containsAll);// false }
public static void main(String[] args) { Collection list = new ArrayList(); // 增長:add() 將指定對象存儲到容器中 list.add("計算機網絡"); list.add("現代操做系統"); list.add("java編程思想"); list.add("java核心技術"); list.add("java語言程序設計"); System.out.println(list); // 獲取 集合容器的大小 int size = list.size(); System.out.println(size); }
---| Iterable 接口
Iterator iterator()
----| Collection 接口
------| List 接口 元素能夠重複,容許在指定位置插入元素,並經過索引來訪問元素
1:增長 void add(int index, E element) 指定位置添加元素 boolean addAll(int index, Collection c) 指定位置添加集合 2:刪除 E remove(int index) 刪除指定位置元素 3:修改 E set(int index, E element) 返回的是須要替換的集合中的元素 4:查找: E get(int index) 注意: IndexOutOfBoundsException int indexOf(Object o) // 找不到返回-1 lastIndexOf(Object o) 5:求子集合 List<E> subList(int fromIndex, int toIndex) // 不包含toIndex
public static void main(String[] args) { List list = new ArrayList(); // 增長:add() 將指定對象存儲到容器中 list.add("計算機網絡"); list.add("現代操做系統"); list.add("java編程思想"); list.add("java核心技術"); list.add("java語言程序設計"); System.out.println(list); // add,在0角標位置添加一本書 list.add(0, "舒克和貝塔"); System.out.println(list); // 在list2集合的1角標位置添加list集合元素 List list2 = new ArrayList(); list2.add("史記"); list2.add("資治通鑑"); list2.add("全球通史"); boolean addAll = list2.addAll(1, list); System.out.println(addAll); //true System.out.println(list2); }
public static void main(String[] args) { List list = new ArrayList(); // 增長:add() 將指定對象存儲到容器中 list.add("計算機網絡"); list.add("現代操做系統"); list.add("java編程思想"); list.add("java核心技術"); list.add("java語言程序設計"); System.out.println(list); // 刪除0角標元素 Object remove = list.remove(0); System.out.println(remove); }
public static void main(String[] args) { List list = new ArrayList(); // 增長:add() 將指定對象存儲到容器中 list.add("計算機網絡"); list.add("現代操做系統"); list.add("java編程思想"); list.add("java核心技術"); list.add("java語言程序設計"); System.out.println(list); // 修改2角標位置的書,返回的原來2角標位置的書 Object set = list.set(2, "邊城"); System.out.println(set); //java編程思想 System.out.println(list); }
public static void main(String[] args) { List list = new ArrayList(); // 增長:add() 將指定對象存儲到容器中 list.add("計算機網絡"); list.add("現代操做系統"); list.add("java編程思想"); list.add("java核心技術"); list.add("java語言程序設計"); list.add("java編程思想"); System.out.println(list); // 查找: E get(int index) 注意角標越界 Object set = list.get(list.size() - 1); System.out.println(set); // java語言程序設計 System.out.println(list); // list.get(list.size()); //IndexOutOfBoundsException // indexOf(Object o) 返回第一次出現的指定元素的角標 int indexOf = list.indexOf("java編程思想"); System.out.println(indexOf); // 2 // 沒有找到,返回-1 int indexOf2 = list.indexOf("三國志"); System.out.println(indexOf2); // -1 // lastIndexOf 返回最後出現的指定元素的角標 int lastIndexOf = list.lastIndexOf("java編程思想"); System.out.println(lastIndexOf); // 5 }
---------------------S 重寫equals和hashcode的Person方法-------------------------------
1:若是不重寫,調用Object類的equals方法,判斷內存地址,爲false
1:若是是Person類對象,而且姓名和年齡相同就返回true
2:若是不重寫,調用父類hashCode方法
1:若是equals方法相同,那麼hashCode也要相同,須要重寫hashCode方法
3:重寫toString方法
1:不重寫,直接調用Object類的toString方法,打印該對象的內存地址
package CollectionTest; public class Person { private String name; private int 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; } @Override public int hashCode() { // TODO Auto-generated method stub return this.name.hashCode()+age; } @Override public boolean equals(Object obj) { if (!(obj instanceof Person)) { return false; } Person p = (Person) obj; return this.name.equals(p.name) && this.age == p.age; } protected Person(String name, int age) { super(); this.name = name; this.age = age; } @Override public String toString() { return "Person [name=" + name + ", age=" + age + "]"; } }
---------------------E 重寫equals和hashcode的Person方法-------------------------------
--| Iterable
----| Collection
------| List
---------| ArrayList 底層採用數組實現,默認10。每次增加50%, 查詢快,增刪慢。
---------| LinkedList 雙向鏈表實現。查詢慢,增刪快
ArrayList:實現原理:
數組實現, 查找快, 增刪慢
數組爲何是查詢快?由於數組的內存空間地址是連續的.
ArrayList底層維護了一個Object[] 用於存儲對象,默認數組的長度是10。能夠經過 new ArrayList(20)顯式的指定用於存儲對象的數組的長度。
當默認的或者指定的容量不夠存儲對象的時候,容量自動增加爲原來的容量的1.5倍。
因爲ArrayList是數組實現, 在增和刪的時候會牽扯到數組增容, 以及拷貝元素. 因此慢。數組是能夠直接按索引查找, 因此查找時較快
能夠考慮,假設向數組的0角標未知添加元素,那麼原來的角標位置的元素須要總體日後移,而且數組可能還要增容,一旦增容,就須要要將老數組的內容拷貝到新數組中.因此數組的增刪的效率是很低的.
public static void main(String[] args) { ArrayList arr = new ArrayList(); Person p1 = new Person("jack", 20); Person p2 = new Person("rose", 18); Person p3 = new Person("rose", 18); arr.add(p1); arr.add(p2); arr.add(p3); System.out.println(arr); // [Person [name=jack, age=20], Person [name=rose, age=18], Person [name=rose, age=18]] ArrayList arr2 = new ArrayList(); for (int i = 0; i < arr.size(); i++) { Object obj = arr.get(i); Person p = (Person) obj; if (!(arr2.contains(p))) { arr2.add(p); } } System.out.println(arr2); // [Person [name=jack, age=20], Person [name=rose, age=18]] }
--| Iterable ----| Collection ------| List ---------| ArrayList 底層採用數組實現,默認10。每次增加50% 查詢快,增刪慢。 ---------| LinkedList 底層採用雙向鏈表實現,增刪快,查詢慢。
LinkedList:鏈表實現, 增刪快, 查找慢
因爲LinkedList:在內存中的地址不連續,須要讓上一個元素記住下一個元素.因此每一個元素中保存的有下一個元素的位置.雖然也有角標,可是查找的時候,須要從頭往下找,顯然是沒有數組查找快的.可是,鏈表在插入新元素的時候,只須要讓前一個元素記住新元素,讓新元素記住下一個元素就能夠了.因此插入很快.
因爲鏈表實現, 增長時只要讓前一個元素記住本身就能夠, 刪除時讓前一個元素記住後一個元素, 後一個元素記住前一個元素. 這樣的增刪效率較高。
但查詢時須要一個一個的遍歷, 因此效率較低。
特有方法
1:方法介紹 addFirst(E e) addLast(E e) getFirst() getLast() removeFirst() removeLast() 若是集合中沒有元素,獲取或者刪除元 素拋:NoSuchElementException 2:數據結構 1:棧 (1.6) 先進後出 push() pop() 2:隊列(雙端隊列1.5) 先進先出 offer() poll() 3:返回逆序的迭代器對象 descendingIterator() 返回逆序的迭代器對象
基本方法:
package CollectionTest; import java.util.LinkedList; @SuppressWarnings("all") public class LinkedListTest { public static void main(String[] args) { LinkedList list = new LinkedList(); list.add("西遊記"); list.add("三國演義"); list.add("石頭記"); list.add("水滸傳"); list.add("全球通史"); // 往頭頂添加 list.addFirst("史記"); // 往最後添加 list.addLast("吶喊"); // list.addFirst(null); // list.addLast(null); System.out.println(list); // 獲取指定位置處的元素。 String str = (String) list.get(0); // 返回此列表的第一個元素。 String str2 = (String) list.getFirst(); System.out.println(str.equals(str2)); // 獲取指定位置處的元素。 String str3 = (String) list.get(list.size() - 1); // 返回此列表的最後一個元素。 String str4 = (String) list.getLast(); System.out.println(str3.equals(str4)); // 獲取但不移除此列表的頭(第一個元素)。 Object element = list.element(); System.out.println(element); int size = list.size(); System.out.println(size); } }
結果:
[史記, 西遊記, 三國演義, 石頭記, 水滸傳, 全球通史, 吶喊]
true
true
史記
7
@Test public void test1(){ LinkedList list = new LinkedList(); list.add("西遊記"); list.add("三國演義"); list.add("石頭記"); list.add("水滸傳"); list.add("全球通史"); Iterator it = list.iterator(); while (it.hasNext()) { String next = (String) it.next(); System.out.println(next); } }
/** * 逆襲迭代集合 */ @Test public void test2(){ LinkedList list = new LinkedList(); list.add("aa"); list.add("bb"); list.add("cc"); Iterator dit = list.descendingIterator(); while (dit.hasNext()) { System.out.println(dit.next()); } }
/** * 模擬做爲棧結構(後進先出) */ @Test public void test3(){ LinkedList list = new LinkedList(); // 壓棧,先進後出 list.push("西遊記"); list.push("三國演義"); list.push("石頭記"); list.push("水滸傳"); System.out.println(list); // 彈棧(獲取到元素而且刪除元素) String str1 = (String) list.pop(); System.out.println(str1); String str2 = (String) list.pop(); System.out.println(str2); String str3 = (String) list.pop(); System.out.println(str3); String str4 = (String) list.pop(); System.out.println(str4); System.out.println(list.size());// 0 System.out.println(list); //[] }
[水滸傳, 石頭記, 三國演義, 西遊記]
水滸傳
石頭記
三國演義
西遊記
0
[]
/** * 做爲隊列的使用(先進先出) */ @Test public void test4() { LinkedList list = new LinkedList(); // 隊列,先進先出 list.offer("西遊記"); list.offer("三國演義"); list.offer("石頭記"); list.offer("水滸傳"); System.out.println(list); // 出隊列 System.out.println(list.poll()); System.out.println(list.poll()); System.out.println(list.poll()); System.out.println(list.poll()); System.out.println(list.size()); System.out.println(list.peek()); // 獲取隊列的頭元素,可是不刪除 System.out.println(list.peekFirst()); // 獲取隊列的頭元素,可是不刪除 System.out.println(list.peekLast()); // 獲取隊列的最後一個元素可是不刪除 }
[西遊記, 三國演義, 石頭記, 水滸傳]
西遊記
三國演義
石頭記
水滸傳
0
null
null
null
ArrayList 和 LinkedList的存儲查找的優缺點:
一、ArrayList 是採用動態數組來存儲元素的,它容許直接用下標號來直接查找對應的元素。可是,可是插入元素要涉及數組元素移動及內存的操做。總結:查找速度快,插入操做慢。
二、LinkedList 是採用雙向鏈表實現存儲,按序號索引數據須要進行前向或後向遍歷,可是插入數據時只須要記錄本項的先後項便可,因此插入速度較快
問題:有一批數據要存儲,要求存儲這批數據不能出現重複數據,ArrayList、LinkedList都無法知足需求。解決辦法:使用 set集合。
Vector: 描述的是一個線程安全的ArrayList。
ArrayList: 單線程效率高
Vector : 多線程安全的,因此效率低
特有的方法:
void addElement(E obj) 在集合末尾添加元素 E elementAt( int index) 返回指定角標的元素 Enumeration elements() 返回集合中的全部元素,封裝到Enumeration對象中 Enumeration 接口: boolean hasMoreElements() 測試此枚舉是否包含更多的元素。 E nextElement() 若是此枚舉對象至少還有一個可提供的元素,則返回此枚舉的下一個元素。
public static void main(String[] args) { Vector v = new Vector(); v.addElement("aaa"); v.addElement("bbb"); v.addElement("ccc"); System.out.println( v ); System.out.println( v.elementAt(2) ); // ccc // 遍歷Vector遍歷 Enumeration ens = v.elements(); while ( ens.hasMoreElements() ) { System.out.println( ens.nextElement() ); } Iterator iterator = v.iterator(); while(iterator.hasNext()){ System.out.println(iterator.next()); } }
爲了方便的處理集合中的元素,Java中出現了一個對象,該對象提供了一些方法專門處理集合中的元素.例如刪除和獲取集合中的元素.該對象就叫作迭代器(Iterator).
對 Collection 進行迭代的類,稱其爲迭代器。仍是面向對象的思想,專業對象作專業的事情,迭代器就是專門取出集合元素的對象。可是該對象比較特殊,不能直接建立對象(經過new),該對象是之內部類的形式存在於每一個集合類的內部。
如何獲取迭代器?Collection接口中定義了獲取集合類迭代器的方法(iterator()),因此全部的Collection體系集合均可以獲取自身的迭代器。
正是因爲每個容器都有取出元素的功能。這些功能定義都同樣,只不過實現的具體方式不一樣(由於每個容器的數據結構不同)因此對共性的取出功能進行了抽取,從而出現了Iterator接口。而每個容器都在其內部對該接口進行了內部類的實現。也就是將取出方式的細節進行封裝。
Jdk1.5以後添加的新接口, Collection的父接口. 實現了Iterable的類就是可迭代的.而且支持加強for循環。該接口只有一個方法即獲取迭代器的方法iterator()能夠獲取每一個容器自身的迭代器Iterator。(Collection)集合容器都須要獲取迭代器(Iterator)因而在5.0後又進行了抽取將獲取容器迭代器的iterator()方法放入到了Iterable接口中。Collection接口進程了Iterable,因此Collection體系都具有獲取自身迭代器的方法,只不過每一個子類集合都進行了重寫(由於數據結構不一樣)
Iterator iterator() 返回該集合的迭代器對象
該類主要用於遍歷集合對象,該類描述了遍歷集合的常見方法 1:java.lang. Itreable ---| Itreable 接口 實現該接口可使用加強for循環 ---| Collection 描述全部集合共性的接口 ---| List接口 能夠有重複元素的集合 ---| Set接口 不能夠有重複元素的集合
public interface Iterable<T>
Itreable 該接口僅有一個方法,用於返回集合迭代器對象。
1: Iterator<T> iterator() 返回集合的迭代器對象
Iterator接口定義的方法
Itreator 該接口是集合的迭代器接口類,定義了常見的迭代方法 1:boolean hasNext() 判斷集合中是否有元素,若是有元素能夠迭代,就返回true。 2: E next() 返回迭代的下一個元素,注意: 若是沒有下一個元素時,調用next元素會拋出NoSuchElementException 3: void remove() 從迭代器指向的集合中移除迭代器返回的最後一個元素(可選操做)。
/** * An optimized version of AbstractList.Itr */ private class Itr implements Iterator<E> { int cursor; // index of next element to return int lastRet = -1; // index of last element returned; -1 if no such int expectedModCount = modCount; public boolean hasNext() { // Racy but within spec, since modifications are checked // within or after synchronization in next/previous return cursor != elementCount; } public E next() { synchronized (Vector.this) { checkForComodification(); int i = cursor; if (i >= elementCount) throw new NoSuchElementException(); cursor = i + 1; return elementData(lastRet = i); } } public void remove() { if (lastRet == -1) throw new IllegalStateException(); synchronized (Vector.this) { checkForComodification(); Vector.this.remove(lastRet); expectedModCount = modCount; } cursor = lastRet; lastRet = -1; } final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } }
解釋:cursor默認爲0。返回元素以後,cursor加一,同時將未加一的cursor賦值給lastRet。刪除的時候也是根據上次返回的刪除,因此調用刪除以前必須先執行next()方法。cursor始終比lastRet大一。
好比第一次訪問next()以後: cursor爲1,lastRet爲0,返回下標爲lastRet的元素(返回第一個元素)。調用remove()方法的時候刪除下標爲lastRet的元素,刪除第一個元素。
next():將cursor賦值給lastRet,將cursor加一,返回元素的時候返回下標爲lastRet的元素。
1. while循環
public static void main(String[] args) { ArrayList list = new ArrayList(); // 增長:add() 將指定對象存儲到容器中 list.add("計算機網絡"); list.add("現代操做系統"); list.add("java編程思想"); list.add("java核心技術"); list.add("java語言程序設計"); System.out.println(list); Iterator it = list.iterator(); while (it.hasNext()) { String next = (String) it.next(); System.out.println(next); } }
2. for循環
public class Demo2 { public static void main(String[] args) { ArrayList list = new ArrayList(); // 增長:add() 將指定對象存儲到容器中 list.add("計算機網絡"); list.add("現代操做系統"); list.add("java編程思想"); list.add("java核心技術"); list.add("java語言程序設計"); System.out.println(list); for (Iterator it = list.iterator(); it.hasNext();) { //迭代器的next方法返回值類型是Object,因此要記得類型轉換。 String next = (String) it.next(); System.out.println(next); } } }
須要取出全部元素時,能夠經過循環,java 建議使用for 循環。由於能夠對內存進行一下優化。
3:使用迭代器清空集合
public class Demo1 { public static void main(String[] args) { Collection coll = new ArrayList(); coll.add("aaa"); coll.add("bbb"); coll.add("ccc"); coll.add("ddd"); System.out.println(coll); Iterator it = coll.iterator(); while (it.hasNext()) { it.next(); it.remove(); } System.out.println(coll); } }
細節一:
若是迭代器的指針已經指向了集合的末尾,那麼若是再調用next()會返回NoSuchElementException異常
細節二:
若是調用remove以前沒有調用next是不合法的,會拋出IllegalStateException
4:迭代器原理
查看ArrayList源碼
private class Itr implements Iterator<E> { int cursor = 0; int lastRet = -1; int expectedModCount = modCount; public boolean hasNext() { return cursor != size(); } public E next() { checkForComodification(); try { E next = get(cursor); lastRet = cursor++; return next; } catch (IndexOutOfBoundsException e) { checkForComodification(); throw new NoSuchElementException(); } } public void remove() { if (lastRet == -1) throw new IllegalStateException(); checkForComodification(); try { AbstractList.this.remove(lastRet); if (lastRet < cursor) cursor--; lastRet = -1; expectedModCount = modCount; } catch (IndexOutOfBoundsException e) { throw new ConcurrentModificationException(); } } }
5. 注意在對集合進行迭代過程當中,不容許出現迭代器之外的對元素的操做,由於這樣會產生安全隱患,java會拋出異常併發修改異常(ConcurrentModificationException),普通迭代器只支持在迭代過程當中的刪除動做。
注意: ConcurrentModificationException: 當一個集合在循環中即便用引用變量操做集合又使用迭代器操做集合對象, 會拋出該異常。
import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; public class Demo1 { public static void main(String[] args) { Collection coll = new ArrayList(); coll.add("aaa"); coll.add("bbb"); coll.add("ccc"); coll.add("ddd"); System.out.println(coll); Iterator it = coll.iterator(); while (it.hasNext()) { it.next(); it.remove(); coll.add("abc"); // 出現了迭代器之外的對元素的操做 } System.out.println(coll); } }
若是是List集合,想要在迭代中操做元素可使用List集合的特有迭代器ListIterator,該迭代器支持在迭代過程當中,添加元素和修改元素。
public interface ListIterator extends Iterator
ListIterator<E> listIterator()
---| Iterator hasNext() next() remove() ------| ListIterator Iterator子接口 List專屬的迭代器 add(E e) 將指定的元素插入列表(可選操做)。該元素直接插入到 next 返回的下一個元素的前面(若是有) void set(E o) 用指定元素替換 next 或 previous 返回的最後一個元素 hasPrevious() 逆向遍歷列表,列表迭代器有多個元素,則返回 true。 previous() 返回列表中的前一個元素。
Iterator在迭代時,只能對元素進行獲取(next())和刪除(remove())的操做。
對於 Iterator 的子接口ListIterator 在迭代list 集合時,還能夠對元素進行添加
(add(obj)),修改set(obj)的操做。
import java.util.ArrayList; import java.util.ListIterator; public class Demo2 { public static void main(String[] args) { ArrayList list = new ArrayList(); // 增長:add() 將指定對象存儲到容器中 list.add("計算機網絡"); list.add("現代操做系統"); list.add("java編程思想"); list.add("java核心技術"); list.add("java語言程序設計"); System.out.println(list); // 獲取List專屬的迭代器 ListIterator lit = list.listIterator(); while (lit.hasNext()) { String next = (String) lit.next(); System.out.println(next); } } }
倒序遍歷
import java.util.ArrayList; import java.util.ListIterator; public class Demo2 { public static void main(String[] args) { ArrayList list = new ArrayList(); // 增長:add() 將指定對象存儲到容器中 list.add("計算機網絡"); list.add("現代操做系統"); list.add("java編程思想"); list.add("java核心技術"); list.add("java語言程序設計"); System.out.println(list); // 獲取List專屬的迭代器 ListIterator lit = list.listIterator(); while (lit.hasNext()) { String next = (String) lit.next(); System.out.println(next); } System.out.println("***************"); while (lit.hasPrevious()) { String next = (String) lit.previous(); System.out.println(next); } } }
Set方法:用指定元素替換 next或 previous返回的最後一個元素
import java.util.ArrayList; import java.util.ListIterator; public class Demo2 { public static void main(String[] args) { ArrayList list = new ArrayList(); // 增長:add() 將指定對象存儲到容器中 list.add("計算機網絡"); list.add("現代操做系統"); list.add("java編程思想"); list.add("java核心技術"); list.add("java語言程序設計"); System.out.println(list); ListIterator lit = list.listIterator(); lit.next(); // 計算機網絡 lit.next(); // 現代操做系統 System.out.println(lit.next()); // java編程思想 //用指定元素替換 next 或 previous 返回的最後一個元素 lit.set("平凡的世界");// 將java編程思想替換爲平凡的世界 System.out.println(list); } }
add方法將指定的元素插入列表,該元素直接插入到 next 返回的元素的後
public class Demo2 { public static void main(String[] args) { ArrayList list = new ArrayList(); // 增長:add() 將指定對象存儲到容器中 list.add("計算機網絡"); list.add("現代操做系統"); list.add("java編程思想"); list.add("java核心技術"); list.add("java語言程序設計"); System.out.println(list); ListIterator lit = list.listIterator(); lit.next(); // 計算機網絡 lit.next(); // 現代操做系統 System.out.println(lit.next()); // java編程思想 // 將指定的元素插入列表,該元素直接插入到 next 返回的元素的後 lit.add("平凡的世界");// 在java編程思想後添加平凡的世界 System.out.println(list); } }
Set:注重獨一無二的性質,該體系集合能夠知道某物是否已近存在於集合中,不會存儲重複的元素。原理是內部基於Map實現,咱們的元素添加在map的key上,value是一個固定的Object。
以HashSet爲例分析不能重複的原理:
(1)HashSet初始化的時候建立一個hashmap:
private transient HashMap<E,Object> map; private static final Object PRESENT = new Object(); public HashSet() { map = new HashMap<>(); }
(2)添加元素的時候將咱們傳下來的元素做爲map的key保存到map中,value是一個Object常量。
public boolean add(E e) { return map.put(e, PRESENT)==null; }
(3)清空的時候調用map的clear()方法:
public void clear() { map.clear(); }
用於存儲無序(存入和取出的順序不必定相同)元素,值不能重複。
對象的相等性
引用到堆上同一個對象的兩個引用是相等的。若是對兩個引用調用hashCode方法,會獲得相同的結果,若是對象所屬的類沒有覆蓋Object的hashCode方法的話,hashCode會返回每一個對象特有的序號(java是依據對象的內存地址計算出的此序號),因此兩個不一樣的對象的hashCode值是不可能相等的。
若是想要讓兩個不一樣的Person對象視爲相等的,就必須覆蓋Object繼下來的hashCode方法和equals方法,由於Object hashCode方法返回的是該對象的內存地址,因此必須重寫hashCode方法,才能保證兩個不一樣的對象具備相同的hashCode,同時也須要兩個不一樣對象比較equals方法會返回true
該集合中沒有特有的方法,直接繼承自Collection。
---| Itreable 接口 實現該接口可使用加強for循環 ---| Collection 描述全部集合共性的接口 ---| List接口 能夠有重複元素的集合 ---| ArrayList ---| LinkedList ---| Set接口 不能夠有重複元素的集合
import java.util.HashSet; import java.util.Iterator; import java.util.Set; public class Demo4 { public static void main(String[] args) { //Set 集合存和取的順序不一致。 Set hs = new HashSet(); hs.add("世界軍事"); hs.add("兵器知識"); hs.add("艦船知識"); hs.add("漢和防務"); System.out.println(hs); // [艦船知識, 世界軍事, 兵器知識, 漢和防務] Iterator it = hs.iterator(); while (it.hasNext()) { System.out.println(it.next()); } } }
---| Itreable 接口實現該接口可使用加強for循環 ---| Collection 描述全部集合共性的接口 ---| List接口 能夠有重複元素的集合 ---| ArrayList ---| LinkedList ---| Set接口 不能夠有重複元素的集合 ---| HashSet 線程不安全,存取速度快。底層是以哈希表實現的。 |
HashSet
哈希表邊存放的是哈希值。HashSet存儲元素的順序並非按照存入時的順序(和List顯然不一樣)是按照哈希值來存的因此取數據也是按照哈希值取得。
HashSet不存入重複元素的規則.使用hashcode和equals
因爲Set集合是不能存入重複元素的集合。那麼HashSet也是具有這一特性的。HashSet如何檢查重複?HashSet會經過元素的hashcode()和equals方法進行判斷元素師否重複。
當你試圖把對象加入HashSet時,HashSet會使用對象的hashCode來判斷對象加入的位置。同時也會與其餘已經加入的對象的hashCode進行比較,若是沒有相等的hashCode,HashSet就會假設對象沒有重複出現。
簡單一句話,若是對象的hashCode值是不一樣的,那麼HashSet會認爲對象是不可能相等的。
所以咱們自定義類的時候須要重寫hashCode,來確保對象具備相同的hashCode值。
若是元素(對象)的hashCode值相同,是否是就沒法存入HashSet中了? 固然不是,會繼續使用equals 進行比較.若是 equals爲true 那麼HashSet認爲新加入的對象重複了,因此加入失敗。若是equals 爲false那麼HashSet 認爲新加入的對象沒有重複.新元素能夠存入.
總結:
元素的哈希值是經過元素的hashcode方法來獲取的, HashSet首先判斷兩個元素的哈希值,若是哈希值同樣,接着會比較equals方法若是 equls結果爲true ,HashSet就視爲同一個元素。若是equals 爲false就不是同一個元素。
哈希值相同equals爲false的元素是怎麼存儲呢,就是在一樣的哈希值下順延(能夠認爲哈希值相同的元素放在一個哈希桶中)。也就是哈希同樣的存一列。
hashtable
HashSet:經過hashCode值來肯定元素在內存中的位置。一個hashCode位置上能夠存放多個元素。
當hashcode() 值相同equals() 返回爲true 時,hashset 集合認爲這兩個元素是相同的元素.只存儲一個(重複元素沒法放入)。調用原理:先判斷hashcode 方法的值,若是相同纔會去判斷equals 若是不相同,是不會調用equals方法的。
HashSet究竟是如何判斷兩個元素重複。
經過hashCode方法和equals方法來保證元素的惟一性,add()返回的是boolean類型
判斷兩個元素是否相同,先要判斷元素的hashCode值是否一致,只有在該值一致的狀況下,纔會判斷equals方法,若是存儲在HashSet中的兩個對象hashCode方法的值相同equals方法返回的結果是true,那麼HashSet認爲這兩個元素是相同元素,只存儲一個(重複元素沒法存入)。
注意:HashSet集合在判斷元素是否相同先判斷hashCode方法,若是相同纔會判斷equals。若是不相同,是不會調用equals方法的。
HashSet 和ArrayList集合都有判斷元素是否相同的方法,
boolean contains(Object o)
HashSet使用hashCode和equals方法,ArrayList使用了equals方法
import java.util.HashSet; import java.util.Iterator; import java.util.Set; public class Demo4 { public static void main(String[] args) { // Set 集合存和取的順序不一致。 Set hs = new HashSet(); hs.add("世界軍事"); hs.add("兵器知識"); hs.add("艦船知識"); hs.add("漢和防務"); // 返回此 set 中的元素的數量 System.out.println(hs.size()); // 4 // 若是此 set 還沒有包含指定元素,則返回 true boolean add = hs.add("世界軍事"); // false System.out.println(add); // 返回此 set 中的元素的數量 System.out.println(hs.size());// 4 Iterator it = hs.iterator(); while (it.hasNext()) { System.out.println(it.next()); } } }
package cn.itcast.gz.map; import java.util.HashSet; import java.util.Iterator; public class Demo4 { public static void main(String[] args) { HashSet hs = new HashSet(); hs.add(new Person("jack", 20)); hs.add(new Person("rose", 20)); hs.add(new Person("hmm", 20)); hs.add(new Person("lilei", 20)); hs.add(new Person("jack", 20)); Iterator it = hs.iterator(); while (it.hasNext()) { Object next = it.next(); System.out.println(next); } } } class Person { private String name; private int age; Person() { } public Person(String name, int age) { this.name = name; this.age = 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; } @Override public int hashCode() { System.out.println("hashCode:" + this.name); return this.name.hashCode() + age * 37; } @Override public boolean equals(Object obj) { System.out.println(this + "---equals---" + obj); if (obj instanceof Person) { Person p = (Person) obj; return this.name.equals(p.name) && this.age == p.age; } else { return false; } } @Override public String toString() { return "Person@name:" + this.name + " age:" + this.age; } }
import java.util.TreeSet; public class Demo5 { public static void main(String[] args) { TreeSet ts = new TreeSet(); ts.add("ccc"); ts.add("aaa"); ts.add("ddd"); ts.add("bbb"); System.out.println(ts); // [aaa, bbb, ccc, ddd] } }
---| Itreable 接口實現該接口可使用加強for循環 ---| Collection 描述全部集合共性的接口 ---| List接口 有序,能夠重複,有角標的集合 ---| ArrayList ---| LinkedList ---| Set接口 無序,不能夠重複的集合 ---| HashSet 線程不安全,存取速度快。底層是以hash表實現的。 ---| TreeSet 紅-黑樹的數據結構,默認對元素進行天然排序(String)。若是比較的時候兩個對象返回值爲0,那麼元素重複。 |
紅-黑樹
紅黑樹是一種特定類型的二叉樹
紅黑樹算法的規則: 左小右大。
既然TreeSet能夠天然排序,那麼TreeSet一定是有排序規則的。
1:讓存入的元素自定義比較規則。
2:給TreeSet指定排序規則。
方式一:元素自身具有比較性
元素自身具有比較性,須要元素實現Comparable接口,重寫compareTo方法,也就是讓元素自身具有比較性,這種方式叫作元素的天然排序也叫作默認排序。
方式二:容器具有比較性
當元素自身不具有比較性,或者自身具有的比較性不是所須要的。那麼此時可讓容器自身具有。須要定義一個類實現接口Comparator,重寫compare方法,並將該接口的子類實例對象做爲參數傳遞給TreeMap集合的構造方法。
注意:當Comparable比較方式和Comparator比較方式同時存在時,以Comparator的比較方式爲主;
注意:在重寫compareTo或者compare方法時,必需要明確比較的主要條件相等時要比較次要條件。(假設姓名和年齡一直的人爲相同的人,若是想要對人按照年齡的大小來排序,若是年齡相同的人,須要如何處理?不能直接return 0,由於可能姓名不一樣(年齡相同姓名不一樣的人是不一樣的人)。此時就須要進行次要條件判斷(須要判斷姓名),只有姓名和年齡同時相等的才能夠返回0.)
經過return 0來判斷惟一性。
問題:爲何使用TreeSet存入字符串,字符串默認輸出是按升序排列的?由於字符串實現了一個接口,叫作Comparable 接口.字符串重寫了該接口的compareTo 方法,因此String對象具有了比較性.那麼一樣道理,個人自定義元素(例如Person類,Book類)想要存入TreeSet集合,就須要實現該接口,也就是要讓自定義對象具有比較性.
存入TreeSet集合中的元素要具有比較性.
比較性要實現Comparable接口,重寫該接口的compareTo方法
TreeSet屬於Set集合,該集合的元素是不能重複的,TreeSet如何保證元素的惟一性
經過compareTo或者compare方法中的來保證元素的惟一性。
添加的元素必需要實現Comparable接口。當compareTo()函數返回值爲0時,說明兩個對象相等,此時該對象不會添加進來。
比較器接口
----| Comparable compareTo(Object o) 元素自身具有比較性 ----| Comparator compare( Object o1, Object o2 ) 給容器傳入比較器 |
TreeSet集合排序的兩種方式:
一,讓元素自身具有比較性。
也就是元素須要實現Comparable接口,覆蓋compareTo 方法。
這種方式也做爲元素的天然排序,也可稱爲默認排序。
年齡按照搜要條件,年齡相同再比姓名。
import java.util.TreeSet; public class Demo4 { public static void main(String[] args) { TreeSet ts = new TreeSet(); ts.add(new Person("aa", 20, "男")); ts.add(new Person("bb", 18, "女")); ts.add(new Person("cc", 17, "男")); ts.add(new Person("dd", 17, "女")); ts.add(new Person("dd", 15, "女")); ts.add(new Person("dd", 15, "女")); System.out.println(ts); System.out.println(ts.size()); // 5 } } class Person implements Comparable { private String name; private int age; private String gender; public Person() { } public Person(String name, int age, String gender) { this.name = name; this.age = age; this.gender = gender; } 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 getGender() { return gender; } public void setGender(String gender) { this.gender = gender; } @Override public int hashCode() { return name.hashCode() + age * 37; } public boolean equals(Object obj) { System.err.println(this + "equals :" + obj); if (!(obj instanceof Person)) { return false; } Person p = (Person) obj; return this.name.equals(p.name) && this.age == p.age; } public String toString() { return "Person [name=" + name + ", age=" + age + ", gender=" + gender + "]"; } @Override public int compareTo(Object obj) { Person p = (Person) obj; System.out.println(this+" compareTo:"+p); if (this.age > p.age) { return 1; } if (this.age < p.age) { return -1; } return this.name.compareTo(p.name); } }
二,讓容器自身具有比較性,自定義比較器。
需求:當元素自身不具有比較性,或者元素自身具有的比較性不是所需的。
那麼這時只能讓容器自身具有。
定義一個類實現Comparator 接口,覆蓋compare方法。
並將該接口的子類對象做爲參數傳遞給TreeSet集合的構造函數。
當Comparable比較方式,及Comparator比較方式同時存在,以Comparator
比較方式爲主。
import java.util.Comparator; import java.util.TreeSet; public class Demo5 { public static void main(String[] args) { TreeSet ts = new TreeSet(new MyComparator()); ts.add(new Book("think in java", 100)); ts.add(new Book("java 核心技術", 75)); ts.add(new Book("現代操做系統", 50)); ts.add(new Book("java就業教程", 35)); ts.add(new Book("think in java", 100)); ts.add(new Book("ccc in java", 100)); System.out.println(ts); } } class MyComparator implements Comparator { public int compare(Object o1, Object o2) { Book b1 = (Book) o1; Book b2 = (Book) o2; System.out.println(b1+" comparator "+b2); if (b1.getPrice() > b2.getPrice()) { return 1; } if (b1.getPrice() < b2.getPrice()) { return -1; } return b1.getName().compareTo(b2.getName()); } } class Book { private String name; private double price; public Book() { } public String getName() { return name; } public void setName(String name) { this.name = name; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } public Book(String name, double price) { this.name = name; this.price = price; } @Override public String toString() { return "Book [name=" + name + ", price=" + price + "]"; } }
會保存插入的順序。
看到array,就要想到角標。
看到link,就要想到first,last。
看到hash,就要想到hashCode,equals.
看到tree,就要想到兩個接口。Comparable,Comparator。
若是程序中存儲了幾百萬個學生,並且常常須要使用學號來搜索某個學生,那麼這個需求有效的數據結構就是Map。Map是一種依照鍵(key)存儲元素的容器,鍵(key)很像下標,在List中下標是整數。在Map中鍵(key)可使任意類型的對象。Map中不能有重複的鍵(Key),每一個鍵(key)都有一個對應的值(value)。一個鍵(key)和它對應的值構成map集合中的一個元素。
Map中的元素是兩個對象,一個對象做爲鍵,一個對象做爲值。鍵不能夠重複,可是值能夠重複。
看頂層共性方法找子類特有對象.
Map與Collection在集合框架中屬並列存在
Map存儲的是鍵值對
Map存儲元素使用put方法,Collection使用add方法
Map集合沒有直接取出元素的方法,而是先轉成Set集合,在經過迭代獲取元素
Map集合中鍵要保證惟一性
也就是Collection是單列集合, Map 是雙列集合。
總結:
Map一次存一對元素, Collection 一次存一個。Map 的鍵不能重複,保證惟一。
Map 一次存入一對元素,是以鍵值對的形式存在.鍵與值存在映射關係.必定要保證鍵的惟一性.
查看api文檔:
interface Map<K,V>
K - 此映射所維護的鍵的類型
V - 映射值的類型
概念
將鍵映射到值的對象。一個映射不能包含重複的鍵;每一個鍵最多隻能映射到一個值。
特色
Key和Value是1對1的關係,如:門牌號:家 老公:老婆
雙列集合
Map學習體系: ---| Map 接口 將鍵映射到值的對象。一個映射不能包含重複的鍵;每一個鍵最多隻能映射到一個值。 ---| HashMap 採用哈希表實現,因此無序 ---| TreeMap 能夠對健進行排序 |
---|Hashtable: 底層是哈希表數據結構,線程是同步的,不能夠存入null鍵,null值。 效率較低,被HashMap 替代。 ---|HashMap: 底層是哈希表數據結構,線程是不一樣步的,能夠存入null鍵,null值。 要保證鍵的惟一性,須要覆蓋hashCode方法,和equals方法。 ---| LinkedHashMap: 該子類基於哈希表又融入了鏈表。能夠Map集合進行增刪提升效率。 ---|TreeMap: 底層是二叉樹數據結構。(紅黑樹)能夠對map集合中的鍵進行排序。須要使用Comparable或者Comparator 進行比較排序。return 0,來判斷鍵的惟一性。 |
常見方法
一、添加: 一、V put(K key, V value) (能夠相同的key值,可是添加的value值會覆 蓋前面的,返回值是前一個,若是沒有就返回null) 二、putAll(Map<? extends K,? extends V> m) 從指定映射中將全部映射關 系複製到此映射中(可選操做)。 二、刪除 一、remove() 刪除關聯對象,指定key對象 二、clear() 清空集合對象 三、獲取 1:value get(key); 能夠用於判斷鍵是否存在的狀況。當指定的鍵不存在的時候,返 回的是null。
三、判斷: 一、boolean isEmpty() 長度爲0返回true不然false 二、boolean containsKey(Object key) 判斷集合中是否包含指定的key 三、boolean containsValue(Object value) 判斷集合中是否包含指定的value 四、長度: Int size()
|
添加:
該案例使用了HashMap,創建了學生姓名和年齡之間的映射關係。並試圖添加劇復的鍵。
import java.util.HashMap; import java.util.Map;
public class Demo1 { public static void main(String[] args) { // 定義一個Map的容器對象 Map<String, Integer > map1 = new HashMap<String, Integer >(); map1.put("jack", 20); map1.put("rose", 18); map1.put("lucy", 17); map1.put("java", 25); System.out.println(map1); // 添加劇復的鍵值(值不一樣),會返回集合中原有(重複鍵)的值, System.out.println(map1.put("jack", 30)); //20
Map<String, Integer> map2 = new HashMap<String, Integer>(); map2.put("張三丰", 100); map2.put("虛竹", 20); System.out.println("map2:" + map2); // 從指定映射中將全部映射關係複製到此映射中。 map1.putAll(map2); System.out.println("map1:" + map1); // } }
|
刪除:
// 刪除: // remove() 刪除關聯對象,指定key對象 // clear() 清空集合對象
Map<String, Integer> map1 = new HashMap<String, Integer>(); map1.put("jack", 20); map1.put("rose", 18); map1.put("lucy", 17); map1.put("java", 25); System.out.println(map1); // 指定key,返回刪除的鍵值對映射的值。 System.out.println("value:" + map1.remove("java")); map1.clear(); System.out.println("map1:" + map1); |
獲取:
// 獲取: // V get(Object key) 經過指定的key對象獲取value對象 // int size() 獲取容器的大小 Map<String, Integer> map1 = new HashMap<String, Integer>(); map1.put("jack", 20); map1.put("rose", 18); map1.put("lucy", 17); map1.put("java", 25); System.out.println(map1); // V get(Object key) 經過指定的key對象獲取value對象 // int size() 獲取容器的大小 System.out.println("value:" + map1.get("jack")); System.out.println("map.size:" + map1.size()); |
判斷:
// 判斷: // boolean isEmpty() 長度爲0返回true不然false // boolean containsKey(Object key) 判斷集合中是否包含指定的key // boolean containsValue(Object value)
Map<String, Integer> map1 = new HashMap<String, Integer>(); map1.put("jack", 20); map1.put("rose", 18); map1.put("lucy", 17); map1.put("java", 25); System.out.println(map1); System.out.println("isEmpty:" + map1.isEmpty()); System.out.println("containskey:" + map1.containsKey("jack")); System.out.println("containsvalues:" + map1.containsValue(100)); |
遍歷Map的方式:
一、將map 集合中全部的鍵取出存入set集合。 Set<K> keySet() 返回全部的key對象的Set集合 再經過get方法獲取鍵對應的值。 二、 values() ,獲取全部的值. Collection<V> values()不能獲取到key對象 三、 Map.Entry對象 推薦使用 重點 Set<Map.Entry<k,v>> entrySet() 將map 集合中的鍵值映射關係打包成一個對象 Map.Entry對象經過Map.Entry 對象的getKey, getValue獲取其鍵和值。 |
|
第一種方式:使用keySet
將Map轉成Set集合(keySet()),經過Set的迭代器取出Set集合中的每個元素(Iterator)就是Map集合中的全部的鍵,再經過get方法獲取鍵對應的值。
import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set;
public class Demo2 { public static void main(String[] args) { Map<Integer, String> map = new HashMap<Integer, String>(); map.put(1, "aaaa"); map.put(2, "bbbb"); map.put(3, "cccc"); System.out.println(map);
// // 獲取方法: // 第一種方式: 使用keySet // 須要分別獲取key和value,沒有面向對象的思想 // Set<K> keySet() 返回全部的key對象的Set集合
Set<Integer> ks = map.keySet(); Iterator<Integer> it = ks.iterator(); while (it.hasNext()) { Integer key = it.next(); String value = map.get(key); System.out.println("key=" + key + " value=" + value); } } }
|
第二種方式: 經過values 獲取全部值,不能獲取到key對象
public static void main(String[] args) { Map<Integer, String> map = new HashMap<Integer, String>(); map.put(1, "aaaa"); map.put(2, "bbbb"); map.put(3, "cccc"); System.out.println(map); // 第二種方式: // 經過values 獲取全部值,不能獲取到key對象 // Collection<V> values()
Collection<String> vs = map.values(); Iterator<String> it = vs.iterator(); while (it.hasNext()) { String value = it.next(); System.out.println(" value=" + value); } } |
第三種方式: Map.Entry(重要)
public static interface Map.Entry<K,V>
經過Map中的entrySet()方法獲取存放Map.Entry<K,V>對象的Set集合。
Set<Map.Entry<K,V>> entrySet()
面向對象的思想將map集合中的鍵和值映射關係打包爲一個對象,就是Map.Entry,將該對象存入Set集合,Map.Entry是一個對象,那麼該對象具有的getKey,getValue得到鍵和值。
public static void main(String[] args) { Map<Integer, String> map = new HashMap<Integer, String>(); map.put(1, "aaaa"); map.put(2, "bbbb"); map.put(3, "cccc"); System.out.println(map); // 第三種方式: Map.Entry對象 推薦使用 重點 // Set<Map.Entry<K,V>> entrySet()
// 返回的Map.Entry對象的Set集合 Map.Entry包含了key和value對象 Set<Map.Entry<Integer, String>> es = map.entrySet();
Iterator<Map.Entry<Integer, String>> it = es.iterator();
while (it.hasNext()) {
// 返回的是封裝了key和value對象的Map.Entry對象 Map.Entry<Integer, String> en = it.next();
// 獲取Map.Entry對象中封裝的key和value對象 Integer key = en.getKey(); String value = en.getValue();
System.out.println("key=" + key + " value=" + value); } } |
底層是哈希表數據結構,線程是不一樣步的,能夠存入null鍵,null值。要保證鍵的惟一性,須要覆蓋hashCode方法,和equals方法。
案例:自定義對象做爲Map的鍵。
package cn.itcast.gz.map;
import java.util.HashMap; import java.util.Iterator; import java.util.Map.Entry; import java.util.Set;
public class Demo3 { public static void main(String[] args) { HashMap<Person, String> hm = new HashMap<Person, String>(); hm.put(new Person("jack", 20), "1001"); hm.put(new Person("rose", 18), "1002"); hm.put(new Person("lucy", 19), "1003"); hm.put(new Person("hmm", 17), "1004"); hm.put(new Person("ll", 25), "1005"); System.out.println(hm); System.out.println(hm.put(new Person("rose", 18), "1006"));
Set<Entry<Person, String>> entrySet = hm.entrySet(); Iterator<Entry<Person, String>> it = entrySet.iterator(); while (it.hasNext()) { Entry<Person, String> next = it.next(); Person key = next.getKey(); String value = next.getValue(); System.out.println(key + " = " + value); } } }
class Person { private String name; private int age;
Person() {
}
public Person(String name, int age) {
this.name = name; this.age = 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; }
@Override public int hashCode() {
return this.name.hashCode() + age * 37; }
@Override public boolean equals(Object obj) { if (obj instanceof Person) { Person p = (Person) obj; return this.name.equals(p.name) && this.age == p.age; } else { return false; } }
@Override public String toString() {
return "Person@name:" + this.name + " age:" + this.age; }
} }
|
TreeMap的排序,TreeMap能夠對集合中的鍵進行排序。如何實現鍵的排序?
方式一:元素自身具有比較性
和TreeSet同樣原理,須要讓存儲在鍵位置的對象實現Comparable接口,重寫compareTo方法,也就是讓元素自身具有比較性,這種方式叫作元素的天然排序也叫作默認排序。
方式二:容器具有比較性
當元素自身不具有比較性,或者自身具有的比較性不是所須要的。那麼此時可讓容器自身具有。須要定義一個類實現接口Comparator,重寫compare方法,並將該接口的子類實例對象做爲參數傳遞給TreeMap集合的構造方法。
注意:當Comparable比較方式和Comparator比較方式同時存在時,以Comparator的比較方式爲主;
注意:在重寫compareTo或者compare方法時,必需要明確比較的主要條件相等時要比較次要條件。(假設姓名和年齡一直的人爲相同的人,若是想要對人按照年齡的大小來排序,若是年齡相同的人,須要如何處理?不能直接return 0,覺得可能姓名不一樣(年齡相同姓名不一樣的人是不一樣的人)。此時就須要進行次要條件判斷(須要判斷姓名),只有姓名和年齡同時相等的才能夠返回0.)
經過return 0來判斷惟一性。
import java.util.TreeMap;
public class Demo4 { public static void main(String[] args) { TreeMap<String, Integer> tree = new TreeMap<String, Integer>(); tree.put("張三", 19); tree.put("李四", 20); tree.put("王五", 21); tree.put("趙六", 22); tree.put("周七", 23); tree.put("張三", 24); System.out.println(tree); System.out.println("張三".compareTo("李四"));//-2094 } } |
自定義元素排序
package cn.itcast.gz.map;
import java.util.Comparator; import java.util.Iterator; import java.util.Map.Entry; import java.util.Set; import java.util.TreeMap;
public class Demo3 { public static void main(String[] args) { TreeMap<Person, String> hm = new TreeMap<Person, String>( new MyComparator()); hm.put(new Person("jack", 20), "1001"); hm.put(new Person("rose", 18), "1002"); hm.put(new Person("lucy", 19), "1003"); hm.put(new Person("hmm", 17), "1004"); hm.put(new Person("ll", 25), "1005"); System.out.println(hm); System.out.println(hm.put(new Person("rose", 18), "1006"));
Set<Entry<Person, String>> entrySet = hm.entrySet(); Iterator<Entry<Person, String>> it = entrySet.iterator(); while (it.hasNext()) { Entry<Person, String> next = it.next(); Person key = next.getKey(); String value = next.getValue(); System.out.println(key + " = " + value); } } }
class MyComparator implements Comparator<Person> {
@Override public int compare(Person p1, Person p2) { if (p1.getAge() > p2.getAge()) { return -1; } else if (p1.getAge() < p2.getAge()) { return 1; } return p1.getName().compareTo(p2.getName()); }
}
class Person implements Comparable<Person> { private String name; private int age;
Person() {
}
public Person(String name, int age) {
this.name = name; this.age = 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; }
@Override public int hashCode() {
return this.name.hashCode() + age * 37; }
@Override public boolean equals(Object obj) { if (obj instanceof Person) { Person p = (Person) obj; return this.name.equals(p.name) && this.age == p.age; } else { return false; } }
@Override public String toString() {
return "Person@name:" + this.name + " age:" + this.age; }
@Override public int compareTo(Person p) {
if (this.age > p.age) { return 1; } else if (this.age < p.age) { return -1; } return this.name.compareTo(p.name); }
} |
注意:
Set的元素不可重複,Map的鍵不可重複,若是存入重複元素如何處理
Set元素重複元素不能存入add方法返回false
Map的重複健將覆蓋舊鍵,將舊值返回。
集合框架中的工具類:特色:該工具類中的方法都是靜態的。
Collections:常見方法: 1, 對list進行二分查找: 前提該集合必定要有序。 int binarySearch(list,key); //必須根據元素天然順序對列表進行升級排序 //要求list 集合中的元素都是Comparable 的子類。 int binarySearch(list,key,Comparator); 2,對list集合進行排序。 sort(list); //對list進行排序,其實使用的事list容器中的對象的compareTo方法 sort(list,comaprator); //按照指定比較器進行排序 3,對集合取最大值或者最小值。 max(Collection) max(Collection,comparator) min(Collection) min(Collection,comparator) 4,對list集合進行反轉。 reverse(list); 5,對比較方式進行強行逆轉。 Comparator reverseOrder(); Comparator reverseOrder(Comparator); 6,對list集合中的元素進行位置的置換。 swap(list,x,y); 7,對list集合進行元素的替換。若是被替換的元素不存在,那麼原集合不變。 replaceAll(list,old,new); 8,能夠將不一樣步的集合變成同步的集合。 Set synchronizedSet(Set<T> s) Map synchronizedMap(Map<K,V> m) List synchronizedList(List<T> list) 9. 若是想要將集合變數組: 可使用Collection 中的toArray 方法。注意:是Collection不是Collections工具類 傳入指定的類型數組便可,該數組的長度最好爲集合的size。
Arrays:用於對數組操做的工具類
1,二分查找,數組須要有序 binarySearch(int[]) binarySearch(double[]) 2,數組排序 sort(int[]) sort(char[])…… 1, 將數組變成字符串。 toString(int[]) 2, 複製數組。 copyOf(); 3, 複製部分數組。 copyOfRange(): 4, 比較兩個數組是否相同。 equals(int[],int[]); 5, 將數組變成集合。 List asList(T[]); 這樣能夠經過集合的操做來操做數組中元素, 可是不可使用增刪方法,add,remove。由於數組長度是固定的,會出現 UnsupportOperationExcetion。 可使用的方法:contains,indexOf。。。 若是數組中存入的基本數據類型,那麼asList會將數組實體做爲集合中的元素。 若是數組中的存入的引用數據類型,那麼asList會將數組中的元素做爲集合中 的元素。
import java.util.ArrayList; import java.util.Collections; import java.util.Arrays; import java.util.List; class Demo1 { public static void main(String[] args) { ArrayList<Integer> list = new ArrayList<Integer>(); list.add(4); list.add(3); list.add(1); list.add(2); list.add(3); // 排序 Collections.sort(list); // 折半查找的前提是排序好的元素 System.out.println( Collections.binarySearch( list , 8 ) ); // 找不到返回-插入點-1 // 反序集合輸出 Collections.reverse( list ); System.out.println( list ); // 求最值 System.out.println( Collections.max( list ) ); // 4 // fill() 使用指定的元素替換指定集合中的全部元素 // Collections.fill( list, 5 ); System.out.println( list ); // 將數組轉換爲集合 Integer is[] = new Integer[]{6,7,8}; List<Integer> list2 = Arrays.asList(is); list.addAll( list2 ); System.out.println( list ); // 將List轉換爲數組 Object [] ins = list.toArray(); System.out.println( Arrays.toString( ins ) ); } }
List<String> list = new ArrayList<String>(); list.add("China"); list.add("Switzerland"); list.add("Italy"); list.add("France"); String [] countries = list.toArray(new String[list.size()]);
String[] countries = {"China", "Switzerland", "Italy", "France"};
List list = Arrays.asList(countries);
List<Value> list = new ArrayList<Value>(map.values());
String [] countries = {"India", "Switzerland", "Italy"}; Set<String> set = new HashSet<String>(Arrays.asList(countries)); System.out.println(set);
Map<Integer, String> sourceMap = createMap(); Set<String> targetSet = new HashSet<>(sourceMap.values());
List<String> datas = getHibernateTemplate().find(hql); Set result = new HashSet<String>(datas);
補充:
在ArrayList,LinkedList,HashMap等等的內部實現增,刪,改中咱們總能看到modCount的身影,modCount字面意思就是修改次數,但爲何要記錄modCount的修改次數呢?
你們發現一個公共特色沒有,全部使用modCount屬性的全是線程不安全的,這是爲何呢?說明這個玩意確定和線程安全有關係嘍,那有什麼關係呢
在ArrayList,LinkedList,HashMap等等的內部實現增,刪,改中咱們總能看到modCount的身影,modCount字面意思就是修改次數,但爲何要記錄modCount的修改次數呢?
你們發現一個公共特色沒有,全部使用modCount屬性的全是線程不安全的,這是爲何呢?說明這個玩意確定和線程安全有關係嘍,那有什麼關係呢
private abstract class HashIterator<E> implements Iterator<E> { Entry<K,V> next; // next entry to return int expectedModCount; // For fast-fail int index; // current slot Entry<K,V> current; // current entry HashIterator() { expectedModCount = modCount; if (size > 0) { // advance to first entry Entry[] t = table; while (index < t.length && (next = t[index++]) == null) ; } } public final boolean hasNext() { return next != null; } final Entry<K,V> nextEntry() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); Entry<K,V> e = next; if (e == null) throw new NoSuchElementException(); if ((next = e.next) == null) { Entry[] t = table; while (index < t.length && (next = t[index++]) == null) ; } current = e; return e; } public void remove() { if (current == null) throw new IllegalStateException(); if (modCount != expectedModCount) throw new ConcurrentModificationException(); Object k = current.key; current = null; HashMap.this.removeEntryForKey(k); expectedModCount = modCount; } }
由以上代碼能夠看出,在一個迭代器初始的時候會賦予它調用這個迭代器的對象的mCount,若是在迭代器遍歷的過程當中,一旦發現這個對象的mcount和迭代器中存儲的mcount不同那就拋異常
好的,下面是這個的完整解釋
Fail-Fast 機制
咱們知道 java.util.HashMap 不是線程安全的,所以若是在使用迭代器的過程當中有其餘線程修改了map,那麼將拋出ConcurrentModificationException,這就是所謂fail-fast策略。這一策略在源碼中的實現是經過 modCount 域,modCount 顧名思義就是修改次數,對HashMap 內容的修改都將增長這個值,那麼在迭代器初始化過程當中會將這個值賦給迭代器的 expectedModCount。在迭代過程當中,判斷 modCount 跟 expectedModCount 是否相等,若是不相等就表示已經有其餘線程修改了 Map:注意到 modCount 聲明爲 volatile,保證線程之間修改的可見性。
想要將map中的key直接所有加上"prefix-",例如:{1=1}變爲{prefix-1=1}
1.使用迭代器遍歷keySet進行先添加元素後刪除元素,報錯
package cn.qlq.test.test; import java.util.HashMap; import java.util.Map; import java.util.Set; /** * @Author: qlq * @Description * @Date: 21:12 2018/9/30 */ public class ForTest { private static final String prefix = "prefix-"; public static void main(String[] args) throws InterruptedException { // 原來的Map HashMap<String, Object> oriMap = new HashMap<String, Object>(); oriMap.put("key1", "111"); oriMap.put("key2", "222"); // 2替換map中的key disposeMap(oriMap); System.out.println(oriMap); } /** * 直接處理報錯(java.util.ConcurrentModificationException) * * @param oriMap */ private static void disposeMap(HashMap<String, Object> oriMap) { Set<String> strings = oriMap.keySet(); for(String key:strings){ Object o = oriMap.get(key); oriMap.put(prefix+key,o); oriMap.remove(key); } } }
編譯後代碼:
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package cn.qlq.test.test; import java.util.HashMap; import java.util.Iterator; import java.util.Set; public class ForTest { private static final String prefix = "prefix-"; public ForTest() { } public static void main(String[] args) throws InterruptedException { HashMap<String, Object> oriMap = new HashMap(); oriMap.put("key1", "111"); oriMap.put("key2", "222"); disposeMap(oriMap); System.out.println(oriMap); } private static void disposeMap(HashMap<String, Object> oriMap) { Set<String> strings = oriMap.keySet(); Iterator i$ = strings.iterator(); while(i$.hasNext()) { String key = (String)i$.next(); Object o = oriMap.get(key); oriMap.put("prefix-" + key, o); oriMap.remove(key); } } }
報錯:
Exception in thread "main" java.util.ConcurrentModificationException at java.util.HashMap$HashIterator.nextEntry(HashMap.java:922) at java.util.HashMap$KeyIterator.next(HashMap.java:956) at cn.qlq.test.test.ForTest.disposeMap(ForTest.java:32) at cn.qlq.test.test.ForTest.main(ForTest.java:21)
原理:其實第一次是遍歷的時候能夠刪除元素與增長元素,可是第二次就會報錯。(也就是map只有一個元素的時候是能夠採用這種方法操做)
執行nextEntry()方法的時候會驗證 modCount 是否等於 expectedModCount,expectedModCount是一個定值(大小等於map元素個數),當modCount!=expectedModCount的時候會拋出異常,每次對元素進行put操做和remove操做以後modCount +1 ,因此上面一輪遍歷以後modCount = 4,而expectedModCount=2因此拋出異常。
package cn.qlq.test.test; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; /** * @Author: qlq * @Description * @Date: 21:12 2018/9/30 */ public class ForTest { private static final String prefix = "prefix-"; public static void main(String[] args) throws InterruptedException { // 原來的Map Map<String, Object> oriMap = new HashMap<String, Object>(); oriMap.put("key1", "111"); oriMap.put("key2", "222"); // 2替換map中的key oriMap = disposeMap2(oriMap); System.out.println(oriMap); } /** * 從新建立一個map進行修改(有效,浪費資源) * * @param oriMap */ private static Map disposeMap2(Map<String, Object> oriMap) { Map result = new HashMap(); Set<String> keys = oriMap.keySet(); for (String key : keys) { Object value = oriMap.get(key); result.put("prefix-" + key, value); } return result; } }
結果:
{prefix-key1=111, prefix-key2=222}
此處有點浪費內存,沒有找到更好的辦法。。。。。。。
需求是咱們但願根據集合map的某個key相同的進行合併,好比:
[{key2=222, key1=value00}, {key1=value00, key22=key22}, {key02=value02, key1=value01}] 合併爲:
[{key2=222, key1=value00, key22=key22}, {key02=value02, key1=value01}]
代碼以下:(思路:遍歷須要合併的map,內部遍歷合併後的map,若是指定key的value相同就將外層map合併到內層,而且進行下層循環,若是內層沒找到就將map添加到合併後的map集合中)
package cn.qlq.test; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; /** * * @author Administrator * */ @SuppressWarnings("all") public class Test2 { public static void main(String[] args) throws InterruptedException { // 原來的Map Map<String, Object> map1 = new HashMap<String, Object>(); map1.put("key1", "value00"); map1.put("key2", "222"); Map<String, Object> map2 = new HashMap<String, Object>(); map2.put("key1", "value00"); map2.put("key22", "key22"); Map<String, Object> map3 = new HashMap<String, Object>(); map3.put("key1", "value01"); map3.put("key02", "value02"); List<Map<String, Object>> list = new ArrayList(); list.add(map1); list.add(map2); list.add(map3); System.out.println(list); list = disposeLise(list); System.out.println(list); } /** * 根據某個字段合併map的算法 * * @param list * @return */ private static List<Map<String, Object>> disposeLise(List<Map<String, Object>> list) { List<Map<String, Object>> result = new ArrayList<>(); outer: for (int i = 0; i < list.size(); i++) { Map map = list.get(i); String str = (String) map.get("key1"); if (result.size() == 0) { Map map2 = new HashMap(); map2.put("key1", str); result.add(map); } inner: for (int j = 0; j < result.size(); j++) { Map<String, Object> map2 = result.get(j); if (str.equals(map2.get("key1"))) { map2.putAll(map); continue outer; } } result.add(map); } return result; } }
其實每一個Entry內部維護了一個before和一個after,所以變爲有序的。
可是第一個元素和最後一個元素是如何肯定的?
先看下面HashMap和LinkedHashMap的結構:
代碼:
package cn.xm.exam.test; import java.util.HashMap; import java.util.LinkedHashMap; public class test { public static void main(String[] args) { HashMap map1 = new HashMap<>(); map1.put("張三", 22); map1.put("lisi", 22); map1.put("趙六", 22); map1.put("aldf", 22); System.out.println(map1.keySet()); LinkedHashMap map2 = new LinkedHashMap<>(); map2.put("張三", 22); map2.put("lisi", 22); map2.put("趙六", 22); map2.put("aldf", 22); System.out.println(map2.keySet()); } }
結果:
[aldf, 趙六, 張三, lisi]
[張三, lisi, 趙六, aldf]
Debug查看兩個map結構:
map1是普通的hashmap,沒什麼特殊之處。
aldf和趙六的hash值相等,可是先添加的趙六,最後用aldf佔用趙六的bucket,而且趙六自身做爲aldf的next元素。知足數組+鏈表
Map2與上面的結構同樣,只是內部多維護了一個before和after,因此變爲有序的。(JDK7查看的結構)
JDK7header元素的after和before分別維護了第一個元素和最後一個元素:
JDK8再次查看LinedHashMap的結構:(其內部有一個head和一個tail,分別記錄頭元素和尾元素)
補充:高度注意 Map 類集合 K/V 能不能存儲 null 值的狀況
LinkedHashMap繼承HashMap,所以二者同樣。
補充:基於TreeMap和TreeSet中的元素或者比較器返回0的時候致使元素丟失:
今天遇到一個TreeSet中傳入的比較器返回0的狀況致使元素丟失,以下:
public class User implements Comparable<User> { private int age; private String username; public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } @Override public int compareTo(User o) { if (o.getAge() < this.age) { return -1; } if (o.getAge() > this.age) { return 1; } return 0; } public User(int age, String username) { super(); this.age = age; this.username = username; } @Override public String toString() { return "User [age=" + age + ", username=" + username + "]"; } }
上面按年齡逆序排序,若是年齡相等返回0不排序。
將User做爲TreeMap的key查看,以下
import java.util.Map; import java.util.TreeMap; public class Client { public static void main(String[] args) { Map<Object, Object> map = new TreeMap<>(); User user1 = new User(26, "26"); User user2 = new User(25, "25"); User user3 = new User(27, "27"); User user4 = new User(26, "262"); map.put(user1, 1); map.put(user2, 2); map.put(user3, 3); map.put(user4, 4); System.out.println(map); } }
結果:
{User [age=27, username=27]=3, User [age=26, username=26]=4, User [age=25, username=25]=2}
能夠看到雖然存進去4個值,可是兩個age爲26的值只存了一個,並且是第一個存進去的26,value被替換爲第二個26的4.
查看TreeMap的put(K,V)源碼:
public V put(K key, V value) { Entry<K,V> t = root; if (t == null) { compare(key, key); // type (and possibly null) check root = new Entry<>(key, value, null); size = 1; modCount++; return null; } int cmp; Entry<K,V> parent; // split comparator and comparable paths Comparator<? super K> cpr = comparator; if (cpr != null) { do { parent = t; cmp = cpr.compare(key, t.key); if (cmp < 0) t = t.left; else if (cmp > 0) t = t.right; else return t.setValue(value); } while (t != null); } else { if (key == null) throw new NullPointerException(); Comparable<? super K> k = (Comparable<? super K>) key; do { parent = t; cmp = k.compareTo(t.key); if (cmp < 0) t = t.left; else if (cmp > 0) t = t.right; else return t.setValue(value); } while (t != null); } Entry<K,V> e = new Entry<>(key, value, parent); if (cmp < 0) parent.left = e; else parent.right = e; fixAfterInsertion(e); size++; modCount++; return null; }
能夠看到當比較的值返回0的時候是將值進行替換 t.setValue(value)。因此當集合是TreeMap的時候(TreeSet內部也是維護TreeMap)若是比較器返回的是0會替換值,也就形成值丟失。
最後:
關於數據結構能夠查看以下網站: