關於集合中元素的有序無序的易混淆點
最近在整理Java基礎知識的面試題,看到了一個題目的答案不夠準確,這裡跟大家分享一下。
一、面試題的小錯誤
對於TreeSet和TreeMap來說,元素應該是無序(指元素的存取)而不是有序的,而在表中它可能想表達的是可以排序,不夠嚴謹,嚴格來講
元素的有序≠可以排序。元素的存取有序和排序本質上應該是兩碼事,不應該混為一聽。
二、元素的有序和無序
下面具體來講講,集合中元素的存取有序的問題。
眾所周知,List的特點: ①元素可重複 ②元素是有序的
相對地,Set的特點是: ①元素唯一 ②元素是無序的
首先,我們來明確這裡的有序/無序的準確定義。
有序是指元素的存取順序是一致的,即你以什麼順序將元素存入List,比如1,3,4,13,那麼取(遍歷)出來的順序也是1,3,4,13。
無序是指元素的存取順序是不一致的(這裡注意,事實上會有小概率可能,你可能碰巧發現你的set中元素的存取順序很巧合地一致了,不符合這個定義了,那麼set中的元素究竟是不是一致的呢?我們下面會講解)
三、具體例子
①List
List中元素的存取是有序的,有序很好理解,比如一個數組arr(以ArrayList為例),該陣列的輸出順序是跟你存入(add)的順序是一致的,為 15 , 2, 3, 9。
import java.util.ArrayList; import java.util.List; public class Test { public static void main(String[] args) { List<Integer> arr=new ArrayList(); arr.add(15); arr.add(2); arr.add(3); arr.add(9); for(int a:arr) { System.out.println(a); } // 輸出結果 15 2 3 9 //arraylist的元素存取是有序的 } }
②Set
import java.util.HashSet; import java.util.Set; public class Test { public static void main(String[] args) { Set<Integer> set=new HashSet(); set.add(122); set.add(234); set.add(2); set.add(12);
for(Integer s:set) { System.out.println(s+" "+s.hashCode()); } // 輸出為 2 122 234 12 //HashSet是元素無序的 } }
Set中元素的存取是無序的。那麼為什麼會出現小概率的“有序”情況呢?
以HashSet來講,HashSet的底層是Hash表,我們每次add元素進去後,每個元素將呼叫hashcode方法(匹配1個Hash函式)得到一個對應的hashcode值,然後根據這個hashcode值在hash表中對應的位置存入元素。舉例來說,我們現在存的順序是123,22,12,3。而取出來的時候是按照這些元素在Hash表中的真正的儲存位置順序(或者逆序或者某種規則)遍歷,所以便會出現存取順序不一致的情況。也就是說,Hash表內部的排序是根據hash值而不是add的先後順序來排序的,而上面出現的特殊情況,也就是說正好你的元素的add順序和它對應的Hash值在Hash表中的排列順序剛好一致,讓你感覺上好像變成有序了。其實,從Hash表的角度上來講,HashSet中元素的存取並不是完全無序(隨機)的,只不過它是按照自己的規則來存入和輸出元素(Hash表中的Hash值的大小順序)。
同理,對於TreeMap來說,其底層為紅黑樹。add元素的先後順序並不重要,真正的儲存順序是按照二叉樹的規則儲存的,所以最後遍歷都是自然排序後的輸出。
四、總結
最後總結:從本質上講,List和Set內元素有序/無序的核心區別
List的元素有序是因為它的底層是線性結構的陣列(或者連結串列),元素的存取是和add的先後(時間)次序有關的——本質上與時間相關
Set的元素無序是因為它的底層是非線性結構的雜湊表/二叉樹,元素的存取是和add的先後(時間)順序無關的——你先add還是後add,都是存在Hash表中的同一個位置/在二叉樹中,無論你先add還是後add,最後都輸出的是排序(預設自然排序)後的陣列——本質上和時間無關