在平常開發中,咱們常常會碰到須要在運行時才知道對象個數的狀況,這種狀況不能使用數組,由於數組是固定數量的,這個時候咱們就會使用集合,由於集合能夠存儲數量不肯定的對象。算法
集合類是特別有用的工具類,不只能夠存儲數量不等的對象,還能夠實現經常使用的數據結構,而且可以存儲有映射關聯的關聯數組。數組
集合類和數組不同,數據既能夠存儲基本類型,也能夠存儲對象,而集合只能存儲對象(對象的引用變量)。安全
Java集合大體分爲:數據結構
Set :無序,不可重複集合多線程
List:有序,可重複集合框架
Map:具備映射關係集合函數
Queue:隊列集合工具
Java的集合類主要是由兩個接口派生出來:Collection 和Map 。性能
集合的框架可看此圖:http://img.blog.csdn.net/20160124221843905spa
Collection collection = new ArrayList(); //添加 collection.add("晚安"); collection.add(9); //返回長度 System.out.println(collection.size()); //移除 collection.remove(9); //是否包含 System.out.println(collection.contains("晚安")); //是否爲空 System.out.println(collection.isEmpty()); Collection books = new HashSet(); books.add("晚安"); books.add("願長夜無夢"); books.add("在全部夜晚安眠"); //去掉collection 包含的元素 books.removeAll(collection); System.out.println(books); books.add("晚安"); //保留二者都有的數據 books.retainAll(collection); System.out.println(books);
Collection 繼承了Iterable接口,Java 8爲Iterable提供了forEach方法,且這個方法的參數是一個函數式接口,咱們能夠經過這個方法進行集合遍歷,而且可使用Lambda表達式。
books.forEach(p -> System.out.println(p));
//獲取迭代器 Iterator iterator = books.iterator(); //判斷是否遍歷完成 while (iterator.hasNext()){ //獲取集合中的下一個元素,返回的Object對象,須要強制轉換 String text = (String)iterator.next(); System.out.println(text); //這裏的刪除對象是迭代器中刪除,刪除的是上一個next返回的方法,並且不會真正刪除books中的內容 iterator.remove(); //會報錯 books.remove(text); }
咱們看到這裏有一個刪除方法,可是刪除的並非books的內容,並且若是修改了其中的內容,實際的內容也不會改變,這裏咱們就能夠得出結論:集合並非把自己傳給了迭代器,而是將集合中的元素傳遞給了迭代器
迭代器採用的是快速失敗機制,一旦在迭代過程當中發現集合被改變,當即拋出錯誤,這樣能夠避免共享了資源而致使數據不一致問題。
咱們也能夠直接經過forEachRemaining 來遍歷,這也是一個函數式接口
iterator.forEachRemaining(p-> System.out.println(p));
for (Object s : books) { System.out.println(s); }
與迭代器相同,這裏循環的也不是集合自己,而是元素,而且也不能修改。
books.removeIf(p -> ((String) p).length() > 5);
這個Predicate 咱們能夠充分的利用,它能夠充分簡化集合運算,如:
public static int count(Predicate predicate, Collection collection) { int total = 0; for (Object object : collection) { //判斷是否知足條件 if (predicate.test(object)) { total++; } } return total; }
System.out.println(count(p -> ((String) p).length() > 5, books));
Collection 還有一個Stream()流式API,流式API在JQuery中經常會用到,主要分爲中間方法和末端方法,顧名思義,中間方法就是容許繼續調用後續方法,而末端方法是最終的操做。Stream的引入極大的豐富了集合的操做。
經常使用的中間方法有
filter(Predicate<? super T> predicate) :過濾不符合條件的集合
sorted:排序
limit(long maxSize) :對數量進行控制,通常是排序以後的操做
distinct():去重
經常使用的末端方法有
forEach(Consumer<? super T> action):遍歷
toArray():轉換成數據
min(Comparator<? super T> comparator):獲取最小值
max(Comparator<? super T> comparator) :獲取最大值
count() :總數
咱們能夠很方便的組合這些API,而對集合進行操做,簡單的例子以下:
System.out.println(books.stream().filter(p->((String) p).contains("夜")).count());
在平時的開發咱們能夠慢慢熟悉這些寫法。
顧名思義,HashSet是按照Hash算法來存儲集合中的元素,所以具備很好的存儲和查找性能,Hashset不是線程安全的,在多線程狀況下,咱們須要經過代碼來保證其同步,HashSet元素值能夠是null。
HashSet是經過判斷兩個對象equals()相等而且hashCode()的返回值也相等來決定這兩個對象是否爲同一對象的。
那這個時候就有些問題了,
若是兩個對象的equals()爲true,可是hashCode()的返回值不相等,那這個時候HashSet認爲這兩個對象不等,都會保存,可是其實與咱們的指望就不同了。
若是兩個對象的hashCode()返回值相等,可是equals()爲false,這個時候也會保存,可是會保存在同一個位置,並經過鏈式結構來保存,這樣會對性能產生影響。
因此咱們要將對象保存到HashSet中,咱們就要儘可能保證兩個對象在equals()爲true時,其返回的hashCode()的值也要相等。
LinkedHashSet是HashSet的子類,LinkedHashSet也是經過hashCode來肯定位置的,可是從名字中能夠看出,它還經過鏈表進行了插入次序的維護,也就說是遍歷的時候能夠是有順序的,可是加入了排序意味着性能的下降。
TreeSet是SortedSet的實現類,這就意味着TreeSet能夠確保集合元素處於排序狀態,既然須要排序,那就有排序規則,TreeSet有兩個排序方法:天然排序和定製排序。
天然排序:TreeSet是調用compareTo方法來比較元素之間的大小。
定製排序:定製排序就是咱們按照咱們制定的規則來進行排序。
TreeSet treeSet=new TreeSet((o1,o2)-> { String m1 = (String)o1; String m2=(String)o2; return m1.length()>m2.length()?-1:0; });
因爲要進行排序,因此TreeSet添加的必須是同一個類元素,不然會報錯。
由於增長了排序,因此相應的也增長了一些方法:
TreeSet<Integer> treeSet1 = new TreeSet<>(); treeSet1.add(1); treeSet1.add(2); treeSet1.add(3); //以前的一個元素 System.out.println(treeSet1.lower(2)); //後一個元素 System.out.println(treeSet1.higher(2)); //第一個元素 System.out.println(treeSet1.first()); //最後一個元素 System.out.println(treeSet1.last());
EnumSet是專門存儲枚舉的集合,全部的元素都必須是指定枚舉類型的枚舉值,EnumSet也是有序的,排序規則與枚舉定義的順序相同。
EnumSet在內部以位向量方式存儲,存儲很是緊湊、高效,運行效率也很好,EnumSet不容許加null。
List list = new ArrayList(); list.add("晚安"); list.add("願路途遙遠"); list.add("都有人陪在身邊"); list.forEach(p-> System.out.println(p)); list.remove(1); //在索引處添加數據 list.add(1, "願路途遙遠"); //獲取指定索引位置元素 System.out.println(list.get(2)); System.out.println(list.size()); //設置索引位置的數據,index必須在現有的長度以內 list.set(2, "想要說的話還沒說完"); //返回fromIndex(包含),到toIndex(不包含)集合至新集合 List list1 = list.subList(0, 2); //排序,比較函數 list.sort((o1, o2) -> ((String) o1).length() - ((String) o2).length()); //將字符串長度做爲新的集合元素替換原來的集合 list.replaceAll(p -> ((String) p).length()); list.forEach(p-> System.out.println(p));
ArrayList 、Vector、LinkedList 是list的三個實現類,徹底支持前面list接口實現的所有功能。
ArrayList 、Vector 是基於數組實現的,內部封裝了一個動態的、容許再分配的Object[] 數組,初始化是經過initialCapacity參數肯定初始長度,若是不指定的話默認是10,當咱們能肯定數組的長度時,咱們能夠給出,這樣能夠減小從新分配的次數,而提升性能。
ArrayList 、Vector在使用上徹底相同,而Vector出現的較早,全部其中的一些方法名較長,然後改爲List接口的方法後增長了一些方法,可是與其以前的方法有一些重複,咱們通常都喜歡使用新東西的嘛,雖然Vector 線程安全,但若是咱們使用Collections工具類一樣可使ArrayList 線程安全,因此總結就是使用ArrayList 就完事了。
LinkedList的內部實現與ArrayList 、Vector徹底不一樣,它的內部實現是經過鏈表來存儲的,而且它還繼承了Deque接口,也便是能夠當作雙端隊列來使用,因而可知它功能的強大。
LinkedList<String> linkedList = new LinkedList(); //將字符串放入隊列尾部 linkedList.offer("隊列尾部字符串"); //將字符放入棧頂部 linkedList.push("棧頂部字符串"); //將字符串放入到隊列的頭部 linkedList.offerFirst("隊列頭部字符串"); linkedList.forEach(p-> System.out.println(p)); //訪問不刪除棧頂元素 System.out.println(linkedList.peekFirst()); //訪問不刪除隊列的最後一個元素 System.out.println(linkedList.peekLast()); //彈出棧頂元素 System.out.println(linkedList.pop()); //訪問並刪除隊列的最後一個元素 System.out.println(linkedList.pollLast());
Queue 用於模擬隊列這種數據結構,也就是先進先出的容器,隊列簡單理解就是排隊打飯,先排隊的人先吃飯,後來的就到隊列尾部,隊列一般不容許隨機訪問數據(這樣就至關於插隊了)。有如下方法:add(E e)
add(E e) :添加元素到尾部。
offer(E e):也是添加元素到尾部,不過在使用容量有限制的隊列時,效率比add要高。
remove():獲取頭部元素並刪除。
poll():獲取尾部元素並刪除。
element():獲取頭部元素,但不刪除。
peek():獲取頭部元素,但不刪除,隊列爲空返回null
Queue接口有PriorityQueue 實現類,除此以外,Queue 還有一個Deque 子接口,是一個雙端隊列,能夠從兩端來添加和刪除元素,這樣Deque實現類既能夠當隊列使用,也能夠當棧使用,上面的LinkedList就是其實現子類,另外還有一個ArrayDeque。
ArrayDeque實現的是Deque,也就是說它是雙端隊列,簡單理解就是既能夠當隊列使用,又能夠當棧使用,當咱們須要棧這種數據結構時,推薦使用ArrayDeque,Stack是古老的集合,不推薦使用。
咱們分別將ArrayDeque 當作棧和隊列來使用下:
棧:
ArrayDeque<String> stack = new ArrayDeque(); stack.push("晚安"); stack.push("願路途遙遠"); stack.push("都有人陪在身邊"); System.out.println(stack); //訪問第一個元素,但不彈出 System.out.println(stack.peek()); //訪問第一個元素,而且彈出 System.out.println(stack.pop()); System.out.println(stack);
隊列:
ArrayDeque<String> queue=new ArrayDeque<>(); queue.offer("晚安"); queue.offer("願長夜無夢"); queue.offer("在每一個夜晚安眠"); System.out.println(queue); //訪問隊列頭部元素,但不刪除 System.out.println(queue.peek()); //訪問隊列頭部元素,而且刪除 System.out.println(queue.poll()); System.out.println(queue);
Map用於存儲具備映射關係的數據,也就是鍵值對,Map集合保存着兩組值,一組存key,另外一組存value,這兩組數據能夠是任何應用類型的數據,key不容許重複,key和value存在單向的一對一關係。
Map中key 組合起來是一個Set集合,key沒有順序,也不能重複,Map中有個keySet()方法就是獲取key集合。
Map的一些經常使用方法以下:
HashMap<Integer, String> map = new HashMap<>(); //放入數據 map.put(1,"宋江"); map.put(2,"盧俊義"); map.put(3,"吳用"); //若是原先位置存在數據時會返回原先的數據 System.out.println(map.put(3,"武松")); //是否存在某key System.out.println(map.containsKey(2)); //是否存在某value System.out.println(map.containsValue("武松")); //是否爲空 System.out.println(map.isEmpty()); //獲取長度 System.out.println(map.size()); //循環key值 for (Object key: map.keySet()) { //經過key值直接獲取value System.out.println(map.get(key)); } //根據key移除元素 System.out.println(map.remove(3)); //新的循環方式 map.forEach((key,value)-> System.out.println(key+":"+value)); //獲取value,不存在則返回默認值 map.getOrDefault(8,"查無此人"); //只是替換,不會新增 map.replace(2,"林沖"); //清空數據 map.clear();
HashMap與Hashtable都是Map接口的典型實現類,他們關係相似ArrayList與Vector,Hashtable早出現且線程安全,可是實現並很差,HashMap性能更好但線程不安全,Hashtable的key和value不容許爲空,可是HashMap能夠,咱們通常也是推薦使用HashMap,即便須要線程安全也可使用Collections工具類。
咱們要正確的存儲key,就要讓做爲key的對象必須實現hashCode()和equals()方法,那咱們判斷兩個key值是否相等,也是和HashSet相同,必須hashCode()相等,equals()返回爲true。
除了key值以外,咱們有時候也要比較value值是否相等containsValue(),這裏判斷的話只須要equals()返回爲true便可。
TreeMap是一個紅黑樹數據結構,每個key-value即爲紅黑樹的一個節點,存儲時根據key進行節點排序,TreeMap保證key-value處於有序狀態,也是兩個排序機制,天然排序和定製排序,跟以前講的相似。
由於TreeMap是有序的,那麼就會提供一些訪問前一個,後一個,第一個,最後一個這種方法,具體方法參考API文檔。
ArrayList<Integer> list = new ArrayList<>(); list.add(2); list.add(8); list.add(5); list.add(10); list.add(7); System.out.println("----天然排序----"); //天然排序 Collections.sort(list); list.forEach(p-> System.out.println(p)); System.out.println("----反轉----"); //反轉 Collections.reverse(list); list.forEach(p-> System.out.println(p)); System.out.println("----隨機排序----"); //隨機排序,至關於洗牌 Collections.shuffle(list); list.forEach(p-> System.out.println(p)); System.out.println("----定製排序規則----"); //定製排序規則 Collections.sort(list,(o1,o2)->(o1-o2)); list.forEach(p-> System.out.println(p)); System.out.println("----定製排序規則----"); //調換list中指定位置的順序 Collections.swap(list,2,4); list.forEach(p-> System.out.println(p)); System.out.println("----將list最後的兩個元素移到前面----"); //將list最後的兩個元素移到前面 Collections.rotate(list,2); list.forEach(p-> System.out.println(p)); System.out.println("----將list最後的兩個元素移到前面----"); //將list中前面的兩個元素移到後面 Collections.rotate(list,-2); list.forEach(p-> System.out.println(p));
ArrayList<Integer> list = new ArrayList<>(); list.add(2); list.add(8); list.add(5); list.add(10); list.add(7); list.add(7); //天然排序 Collections.sort(list); //二分法查找list,帶入的參數爲value,返回的爲索引值(必須是排序以後) System.out.println(Collections.binarySearch(list,10)); //最大值 System.out.println(Collections.max(list)); //最小值 System.out.println(Collections.min(list)); //出現的次數 System.out.println(Collections.frequency(list,8)); //新值替換全部的舊值 Collections.replaceAll(list,8,6); list.forEach(p-> System.out.println(p)); //所有替換 Collections.fill(list,8);
上面提過不少次可使用Collections能夠是集合變成線程安全,只要調用synchronizedXXX()即可以建立線程按照的集合
如:
Collection<Object> objects = Collections.synchronizedCollection(new ArrayList<>());
Collections提供了三類方法來獲取不可變集合
emptyXXX():返回一個不可變的、空的集合對象
singletonXXX():返回一個只包含一個對象的,不可變的集合
unmodifiableXXX():返回指定集合的不可變視圖
Collections.emptyList(); Collections.singletonList("原來是這樣"); ArrayList<Integer> list = new ArrayList<>(); Collections.unmodifiableCollection(list);
集合的介紹和基本用法就是這樣,固然這只是使用,後面還會進行源碼的分析