集合與數組存儲概述
集合、數組都是對多個數據進行存儲操作的結構,簡稱Java容器。此時的存儲,主要指的是內存層面的存儲,不涉及到持久化的存儲(.txt,.jpg,.avi,數據庫中)
數組存儲的特點:
- 數組存儲數據的特點:有序、可重復。對於無序、不可重復的需求,不能滿足。
數組存儲的弊端:
-
一旦初始化以后,其長度就不可修改,不便於擴展。
-
數組一旦定義好,其元素的類型也就確定了。我們也就只能操作指定類型的數據了。
-
數組中提供的方法非常有限,對於添加、刪除、插入數據等操作,非常不便,同時效率不高。
解決數組存儲數據方面的弊端。
集合框架概述
Java 集合可分為 Collection 和 Map 兩種體系
Collection
接口:單列數據,定義了存取一組對象的方法的集合List
:元素有序、可重復的集合
ArrayList、LinkedList、VectorSet
:元素無序、不可重復的集合
HashSet、LinkedHashSet、TreeSet
Map
接口:雙列數據,保存具有映射關系key-value
鍵值對的集合
Collection接口
-
Collection 接口是 List、Set 和 Queue 接口的父接口,該接口里定義的方法既可用於操作 Set 集合,也可用於操作 List 和 Queue 集合。
-
JDK不提供此接口的任何直接實現,而是提供更具體的子接口(如:Set和List)實現。
-
在 Java5 之前,Java 集合會丟失容器中所有對象的數據類型,把所有對象都當成 Object 類型處理;從 JDK 5.0 增加了泛型以后,Java 集合可以記住容器中對象的數據類型

List實現類之一:ArrayList
該類是實現了List的接口,實現了可變大小的數組,隨機訪問和遍歷元素時,提供更好的性能。該類也是非同步的,在多線程的情況下不要使用。ArrayList 增長當前長度的50%,插入刪除需要移動后續元素,效率低。
import java.util.ArrayList; // 引入 ArrayList 類
ArrayList<E> objectName =new ArrayList<>(); // 初始化
方法 | 描述 |
---|---|
add() | 將元素插入到指定位置的 arraylist 中 |
addAll() | 添加集合中的所有元素到 arraylist 中 |
clear() | 刪除 arraylist 中的所有元素 |
clone() | 復制一份 arraylist |
contains() | 判斷元素是否在 arraylist |
get() | 通過索引值獲取 arraylist 中的元素 |
indexOf() | 返回 arraylist 中元素的索引值 |
removeAll() | 刪除存在於指定集合中的 arraylist 里的所有元素 |
remove() | 刪除 arraylist 里的單個元素 |
size() | 返回 arraylist 里元素數量 |
isEmpty() | 判斷 arraylist 是否為空 |
subList() | 截取部分 arraylist 的元素 |
set() | 替換 arraylist 中指定索引的元素 |
sort() | 對 arraylist 元素進行排序 |
toArray() | 將 arraylist 轉換為數組 |
toString() | 將 arraylist 轉換為字符串 |
ensureCapacity() | 設置指定容量大小的 arraylist |
lastIndexOf() | 返回指定元素在 arraylist 中最后一次出現的位置 |
retainAll() | 保留 arraylist 中在指定集合中也存在的那些元素 |
containsAll() | 查看 arraylist 是否包含指定集合中的所有元素 |
trimToSize() | 將 arraylist 中的容量調整為數組中的元素個數 |
removeRange() | 刪除 arraylist 中指定索引之間存在的元素 |
replaceAll() | 將給定的操作內容替換掉數組中每一個元素 |
removeIf() | 刪除所有滿足特定條件的 arraylist 元素 |
forEach() | 遍歷 arraylist 中每一個元素並執行特定操作 |
List實現類之二:LinkedList
前言
鏈表(Linked list)是一種常見的基礎數據結構,是一種線性表,但是並不會按線性的順序存儲數據,而是在每一個節點里存到下一個節點的地址。
鏈表可分為單向鏈表和雙向鏈表。
一個單向鏈表包含兩個值: 當前節點的值和一個指向下一個節點的鏈接。
一個雙向鏈表有三個整數值: 數值、向后的節點鏈接、向前的節點鏈接。
Java LinkedList(鏈表) 類似於 ArrayList,是一種常用的數據容器。
與 ArrayList 相比,LinkedList 的增加和刪除對操作效率更高,而查找和修改的操作效率較低。對於頻繁的插入或刪除元素的操作,建議使用LinkedList類。
概述
LinkedList:雙向鏈表,內部沒有聲明數組,而是定義了Node類型的first和last,用於記錄首末元素。同時,定義內部類Node,作為LinkedList中保存數據的基本結構。Node除了保存數據,還定義了兩個變量:
- prev變量記錄前一個元素的位置
- next變量記錄下一個元素的位置
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
常用方法
方法 | 描述 |
---|---|
public boolean add(E e) | 鏈表末尾添加元素,返回是否成功,成功為 true,失敗為 false。 |
public void add(int index, E element) | 向指定位置插入元素。 |
public boolean addAll(Collection c) | 將一個集合的所有元素添加到鏈表后面,返回是否成功,成功為 true,失敗為 false。 |
public boolean addAll(int index, Collection c) | 將一個集合的所有元素添加到鏈表的指定位置后面,返回是否成功,成功為 true,失敗為 false。 |
public void addFirst(E e) | 元素添加到頭部。 |
public void addLast(E e) | 元素添加到尾部。 |
public boolean offer(E e) | 向鏈表末尾添加元素,返回是否成功,成功為 true,失敗為 false。 |
public boolean offerFirst(E e) | 頭部插入元素,返回是否成功,成功為 true,失敗為 false。 |
public boolean offerLast(E e) | 尾部插入元素,返回是否成功,成功為 true,失敗為 false。 |
public void clear() | 清空鏈表。 |
public E removeFirst() | 刪除並返回第一個元素。 |
public E removeLast() | 刪除並返回最后一個元素。 |
public boolean remove(Object o) | 刪除某一元素,返回是否成功,成功為 true,失敗為 false。 |
public E remove(int index) | 刪除指定位置的元素。 |
public E poll() | 刪除並返回第一個元素。 |
public E remove() | 刪除並返回第一個元素。 |
public boolean contains(Object o) | 判斷是否含有某一元素。 |
public E get(int index) | 返回指定位置的元素。 |
public E getFirst() | 返回第一個元素。 |
public E getLast() | 返回最后一個元素。 |
public int indexOf(Object o) | 查找指定元素從前往后第一次出現的索引。 |
public int lastIndexOf(Object o) | 查找指定元素最后一次出現的索引。 |
public E peek() | 返回第一個元素。 |
public E element() | 返回第一個元素。 |
public E peekFirst() | 返回頭部元素。 |
public E peekLast() | 返回尾部元素。 |
public E set(int index, E element) | 設置指定位置的元素。 |
public Object clone() | 克隆該列表。 |
public Iterator descendingIterator() | 返回倒序迭代器。 |
public int size() | 返回鏈表元素個數。 |
public ListIterator listIterator(int index) | 返回從指定位置開始到末尾的迭代器。 |
public Object[] toArray() | 返回一個由鏈表元素組成的數組。 |
public T[] toArray(T[] a) | 返回一個由鏈表元素轉換類型而成的數組。 |
Set接口
存儲的數據特點:無序的、不可重復的元素,且既有數組也有鏈表
- 無序性:不等於隨機性。存儲的數據在底層數組中並非按照數組索引的順序添加,而是根據數據的哈希值決定的。
- 不可重復性:保證添加的元素照equals()判斷時,不能返回true.即:相同的元素只能添加一個。
Set接口是Collection的子接口,set接口沒有提供額外的方法。Set 集合不允許包含相同的元素,如果試把兩個相同的元素加入同一個Set 集合中,則添加操作失敗。Set 判斷兩個對象是否相同不是使用 ==
運算符,而是根據 equals()
方法
常用實現類
- Set接口:存儲無序的、不可重復的數據 -->類似於高中講的“集合”
HashSet
:作為Set接口的主要實現類;線程不安全的;可以存儲null值LinkedHashSet
:作為HashSet的子類;遍歷其內部數據時,可以照添加的順序遍歷。在添加數據的同時,每個據還維護了兩個引用,記錄此數據前一個數據和后一個數據。對於頻繁的遍歷操作,LinkedHashSet效率高於HashSet.
TreeSet
:可以照添加對象的指定屬性,進行排序。
元素添加過程:(以HashSet為例)
-
向HashSet中添加元素a,首先調用元素a所在類的hashCode()方法,計算出元素a的哈希值
-
此哈希值接着通過某種散列函數計算出在HashSet底層數組中的存放位置(即索引位置),然后判斷此位置上是否已經元素:
- 如果此位置上沒其他元素,則元素a添加成功。 --->情況1
-
如果此位置上有其他元素b,則比較元素a與元素b的hash值,若有多個元素則挨個比較並以鏈表形式存儲
- 如果hash值不相同,則元素a添加成功。--->情況2
- 如果hash值相同,進而需要調用元素a所在類的equals()方法:
- equals()返回true表明該元素已存在,元素a添加失敗;equals()返回false,則元素a添加成功。-->情況3
對於添加成功的情況2和情況3而言:元素a 與已經存在指定索引位置上數據以鏈表的方式存儲。
jdk 7 :元素a指向原來的元素。
jdk 8 :原來的元素指向元素a
總結:七上八下
注意
- 向Set(主要指:HashSet、LinkedHashSet)中添加的數據,其所在的類一定要重寫
hashCode()
和equals()
- 重寫的
hashCode()
和equals()
盡可能保持一致性:相等的對象必須具有相等的散列碼
結論:重寫equals方法的時候一般都需要同時重寫hashCode方法。通常參與計算hashCode 的對象的屬性也應該參與到equals() 中進行計算
Set實現類之一:HashSet
- HashSet 基於 HashMap 來實現的,是一個不允許有重復元素的集合。
- HashSet 允許有 null 值。
- HashSet 是無序的,即不會記錄插入的順序。
- HashSet 不是線程安全的, 如果多個線程嘗試同時修改 HashSet,則最終結果是不確定的。 您必須在多線程訪問時顯式同步對 HashSet 的並發訪問。
- HashSet 實現了 Set 接口。
- HashSet 中的元素實際上是對象,一些常見的基本類型可以使用它的包裝類
變量和類型 | 方法 | 描述 |
---|---|---|
boolean |
add(E e) |
如果指定的元素尚不存在,則將其添加到此集合中。 |
void |
clear() |
從該集中刪除所有元素。 |
Object |
clone() |
返回此 HashSet 實例的淺表副本:未克隆元素本身。 |
boolean |
contains(Object o) |
如果此set包含指定的元素,則返回 true 。 |
boolean |
isEmpty() |
如果此集合不包含任何元素,則返回 true 。 |
Iterator<E> |
iterator() |
返回此set中元素的迭代器。 |
boolean |
remove(Object o) |
如果存在,則從該集合中移除指定的元素。 |
int |
size() |
返回此集合中的元素數(基數)。 |
Spliterator<E> |
spliterator() |
在此集合中的元素上創建late-binding和失敗快速 Spliterator |
Set實現類之二:LinkedHashSet
-
LinkedHashSet 是 HashSet 的子類,LinkedHashSet 根據元素的 hashCode 值來決定元素的存儲位置,但它同時使用雙向鏈表維護元素的次序,這使得元素看起來是以插入順序保存的。
-
LinkedHashSet插入性能略低於 HashSet,但在頻繁的地遍歷操作時有很好的性能。LinkedHashSet的存在就是為了頻繁地遍歷
Set實現類之三:TreeSet的使用
TreeSet和后面要講的TreeMap,采用紅黑樹的存儲結構
特點:有序,查詢速度比List快
TreeSet 兩種排序方法:自然排序和定制排序。默認情況下,TreeSet 采用自然排序
-
自然排序中,比較兩個對象是否相同的標准為:重寫的compareTo()返回0,不再是equals()
-
如果試圖把一個對象添加到 TreeSet 時,則該對象的類必須實現
Comparable
接口。 -
向 TreeSet 中添加元素時,只有第一個元素無須比較compareTo()方法,后面添加的所有元素都會調用compareTo()方法進行比較。
-
因為只有相同類的兩個實例才會比較大小,所以向 TreeSet 中添加的應該是同
一個類的對象。
-
重寫該對象對應的 equals() 方法時,應保證該方法與 compareTo(Object obj) 方法有一致的結果
-
-
定制排序中,比較兩個對象是否相同的標准為:compare()返回0,不再是equals()。TreeSet的自然排序要求元素所屬的類實現Comparable接口,如果元素所屬的類沒有實現Comparable接口,或不希望按照升序(默認情況)的方式排列元素或希望按照其它屬性大小進行排序,則考慮使用定制排序。
-
利用int compare(T o1,T o2)方法,比較o1和o2的大小:如果方法返回正整數,則表
示o1大於o2;如果返回0,表示相等;返回負整數,表示o1小於o2。
-
要實現定制排序,需要將實現Comparator接口的實例作為形參傳遞給TreeSet的構
造器
-
仍然只能向TreeSet中添加類型相同的對象。否則發生
ClassCastException
異常
-
//方式一:自然排序
@Test
public void test1(){
TreeSet set = new TreeSet();
//失敗:不能添加不同類的對象
// set.add(123);
// set.add(456);
// set.add("AA");
// set.add(new User("Tom",12));
//舉例一:
// set.add(34);
// set.add(-34);
// set.add(43);
// set.add(11);
// set.add(8);
//舉例二:
set.add(new User("Tom",12));
set.add(new User("Jerry",32));
set.add(new User("Jim",2));
set.add(new User("Mike",65));
set.add(new User("Jack",33));
set.add(new User("Jack",56));
Iterator iterator = set.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
//方式二:定制排序
@Test
public void test2(){
Comparator com = new Comparator() {
//照年齡從小到大排列
@Override
public int compare(Object o1, Object o2) {
if(o1 instanceof User && o2 instanceof User){
User u1 = (User)o1;
User u2 = (User)o2;
return Integer.compare(u1.getAge(),u2.getAge());
}else{
throw new RuntimeException("輸入的數據類型不匹配");
}
}
};
TreeSet set = new TreeSet(com);
set.add(new User("Tom",12));
set.add(new User("Jerry",32));
set.add(new User("Jim",2));
set.add(new User("Mike",65));
set.add(new User("Mary",33));
set.add(new User("Jack",33));
set.add(new User("Jack",56));
Iterator iterator = set.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
遍歷Collection的兩種方式
Iterator迭代器
- java.utils包下定義的迭代器接口:Iterator
- Iterator對象稱為迭代器(設計模式的一種),主要用於遍歷 Collection 集合中的元素。
- GOF給迭代器模式的定義為:提供一種方法訪問一個容器(container)對象中各個元素,而又不需暴露該對象的內部細節。迭代器模式,就是為容器而生。
Iterator iter = coll.iterator();//回到起點
//hasNext():判斷是否還有下一個元素
while(iter.hasNext()){
//next():①指針下移 ②獲取指針所指位置的元素
Object obj = iter.next();
if(obj.equals("Tom")){
iter.remove();
}
}
Iterator接口中的remove()方法
- 可在遍歷的時候刪除集合中的元素
- 不同於集合直接調用remove(),collection返回的是boolean型,即是否刪除成功
- 如果未調用next()或已經調用過remove(),再調用remove()則會報錯IllegalStateException
foreach增強for循環
- Java 5.0 提供了 foreach 循環迭代訪問 Collection和數組。
- 遍歷操作不需獲取Collection或數組的長度,無需使用索引訪問元素。
- 遍歷集合的底層調用Iterator完成操作。
- foreach還可以用來遍歷數組。
面試題
@Test
public void testListRemove() {
List list = new ArrayList();
list.add(1);
list.add(2);
list.add(3);
updateList(list);
System.out.println(list);// [1,2]
}
private static void updateList(List list) {
list.remove(2); //基本類型不會自動封裝,當成索引
list.romove(new Integer(2)); //[1,3]
}
//練習:在List內去除重復數字值,要求盡量簡單
public static List duplicateList(List list) {
HashSet set = new HashSet();
set.addAll(list);
return new ArrayList(set);
}
public static void main(String[] args) {
List list = new ArrayList();
list.add(new Integer(1));
list.add(new Integer(2));
list.add(new Integer(2));
list.add(new Integer(4));
list.add(new Integer(4));
List list2 = duplicateList(list);
for (Object integer : list2) {
System.out.println(integer);
}
}