4、ArrayList 集合ArrayList 集合是最常用的集合,是用存儲數據結構,元素增刪慢,查找快。
一、List 集合
List 是一個接口,是有序的 collection,此接口的用戶可以對列表中每個元素的插入位置進行精確的控制,用戶可以根據元素的整數索引訪問元素,並搜索列表中的元素。List 接口允許存放重復的元素,並且元素都是有序的(Set 接口不允許存放重復元素,元素是無序的)
1、List 接口特點
- 它是一個有序的集合
- 他是一個帶索引的集合,通過索引就可以精確地操作集合中的元素(與數組的索引是一個道理)
- 集合中可以有重復的元素,可以通過 equals 方法來比較是否為重復的元素
- List 接口常用的子類有:ArrayList 集合、LinkedList 集合
2、List 接口中常用的方法
- boolean add(Object e):向集合末尾添加指定元素
- void add(int index,Object e):向集合指定索引處添加指定元素,原有元素依次后移
- remove(Object e):將指定元素對象從集合中刪除,返回被刪除的元素
- remove(int index):將指定索引處的元素從集合中刪除,返回被刪除的元素
- set(int index,Object e):將指定索引處的元素替換成指定的元素,返回替換前的元素
- get(int index):獲取指定索引處的元素,返回該元素
-
public static void main(String[] args)
-
{
-
List<String> L = new ArrayList<>();
-
//末尾添加元素
-
L.add( "abc");
-
L.add( "bcd");
-
L.add( "cde");
-
L.add( "def");
-
L.add( "efg");
-
//指定位置添加元素
-
L.add( 2,"fgh");
-
//刪除指定元素
-
L.remove( "abc");
-
//刪除指定索引元素
-
L.remove( 1);
-
//將指定索引處的元素替換成指定元素
-
L.set( 1,"hello");
-
//獲取指定索引處的元素
-
L.get( 1);
-
//使用迭代器獲取出集合中的元素,最好使用listIterator進行迭代
-
Iterator<String> it = L.listIterator();
-
while(it.hasNext())
-
{
-
System.out.println(it.next());
-
}
-
//由於List集合是有索引的,還可以使用索引進行迭代
-
for(int i = 0;i < L.size();i++)
-
{
-
System.out.println(L.get(i));
-
}
-
}
注:
- List 集合是帶索引的有序集合,因此除了使用迭代器進行獲取元素外,還可以使用索引下表進行元素獲取
- Iterator 迭代 List 異常問題:在迭代過程中,如果要添加一個新的元素,使用集合的方法對元素進行操作,會導致迭代器不知道集合中的變化,容易發生數據的不確定性。因此,通過 listIterator 迭代能避免這個異常。
3、List 集合存儲數據結構
List 接口下有多個集合,它們存儲元素所采用的數據結構方式有所不同,這就導致了不同集合有其不同特點,供程序員在不同的環境下使用。
數據存儲的常用結構有:堆棧、隊列、數組、鏈表
- 堆棧
(1) 先進后出
(2) 棧的入口、出口都是棧的頂端位置
(3) 壓棧:即存元素
(4) 出棧:即取元素 - 隊列
(1) 先進先出
(2) 隊列的入口、出口為兩端 - 數組
(1) 查找元素快:通過索引快速訪問指定元素位置
(2) 增刪元素慢:指定位置增加、刪除元素都需要創建一個新的數組,將指定新元素存儲在指定索引位置,再把原數組索引根據索引復制到新數組對應索引位置 - 鏈表
(1) 多個節點之間,通過地址進行連接
(2) 查找元素慢:想要查找某個元素,需要通過連接的節點,依次向后查找指定元素
(3) 增刪元素快:只需修改連接下個元素的地址即可
4、ArrayList 集合
ArrayList 集合是最常用的集合,是用存儲數據結構,元素增刪慢,查找快。
在我的另一篇博客:Java中ArrayList集合有其的介紹,這里不多說
5、LinkedList 集合
LinkedList 集合數據存儲的結構是鏈表結構,對元素的增刪很方便,實際開發中對一個集合元素的增刪經常涉及到首位操作,而 LinkedList 集合提供了大量的首位操作方法。
- void addFirst(E e):將指定元素插入鏈表的開頭
- void addLast(E e):將指定元素插入鏈表的結尾
- E getFirst():返回鏈表的第一個元素
- E getLast():返回鏈表的最后一個元素
- E removeFirst():移除並返回鏈表的第一個元素
- E removeLast():移除並返回鏈表的最后一個元素
- E pop(E e):取出鏈表棧頂元素
- void push(E e):將元素推入此鏈表所示的堆棧
- boolean isEmpty():判斷鏈表中是否有元素
邏輯實例:
-
public static void main(String[] args)
-
{
-
//創建鏈表
-
LinkedList<String> link = new LinkedList<>();
-
//添加元素
-
link.add( "abc");
-
link.add( "bcd");
-
link.add( "cde");
-
link.add( "def");
-
//獲取元素
-
System.out.println(link.getFirst());
-
System.out.println(link.getLast());
-
//刪除元素
-
System.out.println(link.remove( "abc"));
-
System.out.println(link.removeFirst());
-
-
while (link.isEmpty()) //判斷集合是否有元素
-
{
-
System.out.println(link.pop()); //取出棧頂元素
-
}
-
}
6、Vector 集合
Vector 集合數據存儲結構是數組結構,與 ArrayList 不同之處在於提供了一個獨特的取出方法:枚舉Enumeration,與 Iterator 接口功能類似。Vector 集合已被 ArrayList 集合替代,枚舉Enumeration 已被迭代器 Iterator 替代(這里盜用一張圖)
二、Set 接口
Set 集合里面存儲的是無序的不重復元素,沒有索引,可以采用迭代器和增強for來獲取元素,Set 常用的子類有 HashSet、LinkedHashSet 集合,可以通過 equals 方法來判斷是否為重復元素。
1、HashSet 集合
HashSet 類實現 Set 接口,由哈希表支持(實際上是一個 HashMap 集合),HashSet 集合不能保證迭代順序與元素存儲順序相同,采用哈希表結構存儲數據結構,保證元素唯一性的方式依賴於:hashCode() 於 equals() 方法。
-
特點:無序集合,存儲和取出的順序不同,沒有索引,不存儲重復元素
-
在代碼編寫上和 ArrayList 完全一致
-
存儲、取出數據都比較快
-
線程不安全,運行速度快
-
底層數據結構為哈希表(鏈表數組結合體)
-
public static void main(String[] args)
-
{
-
//使用多態創建哈希表
-
Set<String> S = new HashSet<>();
-
S.add( "abc");
-
S.add( "bcd");
-
S.add( "cde");
-
//使用迭代器獲取元素
-
Iterator<String> it = S.iterator();
-
while (it.hasNext())
-
{
-
System.out.println(it.next());
-
}
-
//使用增強獲取元素
-
for(String s : S)
-
{
-
System.out.println(s);
-
}
-
}
2、HashSet 集合存儲數據的結構(哈希表)
哈希表介紹:
哈希表底層使用的是數組機制,數組中也存放對象,而這些對象往數組中存放時的位置比較特殊,當需要把對象在這些數組中存放時,會根據這些對象特有的數據來結合相應的算法,計算出這個對象在數組中的位置,然后把這個對象存放在數組中。這樣的數組就稱為哈希數組,即哈希表。
在哈希表存儲數組時,會先記錄第一個元素的地址,繼續存儲時,會讓先來的元素記錄后來的地址,在這里有一個“桶”和“加載因子”的概念,桶:數組的初始容量,初始容量為16;加載因子:數組的長度百分比,默認為0.75。數組的長度:16*0.75 = 12;當存放的數據超出數組長度 12 時,數組就會進行擴容,即復制(這個過程很耗費資源),這個過程也稱為數據的再哈希 rehash。
當向哈希表存放元素時,會根據元素的特有數據結合響應的算法,這個算法就是 Object 類中的 hashCode 方法。由於任何對象都是 Object 類的子類,所以任何對象都有這個方法,即:在哈希表中存放對象時,會調用對象的 hashCode 方法,算出對象在表中的位置,需要注意的是,如果兩個對象 hashCode 方法算出結果一樣,稱為哈希沖突,這樣會調用對象 equals 方法來比較兩個對象是不是同一個對象,如果返回 true,則把第一個對象存放在哈希表中,如果返回 false,就會把這兩個值都存放在哈希表中。
總結:保證 HashSet 集合元素的唯一,其實就是根據對象 hashCode 和 equals 方法來決定的。如果往集合中存放自定義對象,為了保證唯一性,就必須重寫 hashCode 和 equals 方法建立屬於當前對象的比較方法。
3、String 類的哈希值
哈希值表示普通的十進制整數, 是父類 Object 方法 public int hashCode() 的計算結果,String 類繼承了Object,重寫了 hashCode 方法。String 類中 hashCode 源碼:
-
public int hashCode() {
-
int h = hash; //一開始變量 hash 為 0
-
if (h == 0 && value.length > 0) {
-
char val[] = value;
-
//返回字符串ASCII經過計算的和
-
for (int i = 0; i < value.length; i++) {
-
h = 31 * h + val[i];
-
}
-
hash = h;
-
}
-
return h;
-
}
因此,當使用String類定義兩個對象:String S1 = new String("abc"); String S2 = new String("abc"); 對象S1和S2哈希值是相同的,然后集合會讓后來的對象調用 equals 方法,如果返回 true,則集合判定元素重復,將其去除。
4、自定義對象重寫hashCode和equals
給HashSet中存放自定義類型元素時,需要重寫對象中的hashCode和equals方法,建立自己的比較方式,才能保證HashSet集合中的對象唯一。
創建Person類,在類中重寫 hashCode 方法和 equals 方法
-
public class Person {
-
private String name;
-
private int age;
-
public Person(String name,int age)
-
{
-
this.name = name;
-
this.age = age;
-
}
-
public void setName(String name)
-
{
-
this.name = name;
-
}
-
public void setAge(int age)
-
{
-
this.age = age;
-
}
-
public String getName()
-
{
-
return name;
-
}
-
public int getAge()
-
{
-
return age;
-
}
-
public String toString() {
-
return name + age;
-
}
-
//重寫hashCode方法
-
public int hashCode()
-
{
-
return name.hashCode() + age;
-
}
-
//重寫equals方法
-
public boolean equals(Object obj)
-
{
-
if(this == obj)
-
return true;
-
if(obj == null)
-
return false;
-
if(obj instanceof Person)
-
{
-
Person P = (Person)obj;
-
return name.equals(obj.name) && age == P.age;
-
}
-
return false;
-
}
-
}
在main中調用,由於重寫了 hashCode 和 equals 方法,所以相同類型元素將不會打印出
-
public static void main(String[] args)
-
{
-
//創建存儲Person類的哈希表
-
HashSet<Person> H = new HashSet<>();
-
H.add( new Person("a",18));
-
H.add( new Person("a",18));
-
H.add( new Person("b",19));
-
H.add( new Person("c",20));
-
System.out.println(H);
-
}
5、LinkedHashSet集合
LinkedHashSet 類是基於鏈表的哈希表的實現,繼承自 HashSet,是 Set 接口的實現,此實現與 HashSet 的不同之外在於,后者維護着一個運行於所有條目的雙重鏈接列表。此鏈接列表定義了迭代順序,即按照將元素插入到 set 中的順序(插入順序)進行迭代。
- LinkedHashSet 特點:具有順序,存儲和取出元素順序相同
-
public class LinkedHashSetDemo {
-
-
public static void main(String[] args) {
-
LinkedHashSet<Integer> link = new LinkedHashSet<Integer>();
-
link.add( 123);
-
link.add( 44);
-
link.add( 33);
-
link.add( 33);
-
link.add( 66);
-
link.add( 11);
-
System.out.println(link);
-
}
-
}
hashCode 和 equals 方法的面試題
(1) 兩個對象 Person P1 P2,如果兩個對象的哈希值相同,則兩個對象的 equals 一定返回 true 嗎?
不一定為 true
(2) 兩個對象 Person P1 P2,如果兩個對象的 equals 方法返回 true,則兩個對象的哈希值一定相同嗎?
一定相同