java中List、Set、Map集合遍歷的幾種方式小結和比較
阿新 • • 發佈:2019-02-13
一、集合類的通用遍歷方式, 用迭代器迭代:
1.迭代遍歷while
Iterator it = list.iterator();
while(it.hasNext()){
Object obj = it.next();
System.out.println(it.next());
}
2.迭代遍歷for
for(Iterator it = it.iterator(); it.hasNext();){
System.out.printin(it.next());}
二、List遍歷方式:
1.迭代遍歷while:
Iterator iterator = list.iterator();
while(iterator.hasNext()){
int i = (Integer) iterator.next();
System.out.println(i);
}
2.迭代遍歷for:
for(Iterator iterator = list.iterator();iterator.hasNext();){
int i = (Integer) iterator.next();
System.out.println(i);
}
3.增強for迴圈遍歷:
for (Object object : list) {
System.out.println(object);
}
4.類似陣列形式遍歷:
for(int i = 0 ;i<list.size();i++) {
int j= (Integer) list.get(i);
System.out.println(j);
}
三、Set遍歷方式:
1.迭代遍歷while:
Set<String> set = new HashSet<String>();
Iterator<String> it = set.iterator();
while (it.hasNext()) {
String str = it.next();
System.out.println(str);
}
2.迭代遍歷for:
for(Iterator iter = set.iterator();iter.hasNext();){
Integer v = (Integer)iter.next();
System.out.println(v);
}
3.增強for迴圈遍歷:
for (String str : set) {
System.out.println(str);
}
4.增強for迴圈遍歷的補充
優點還體現在泛型 假如 set中存放的是Object
Set<Object> set = new HashSet<Object>();
for迴圈遍歷:
for (Object obj: set) {
if(obj instanceof Integer){
int aa= (Integer)obj;
}else if(obj instanceof String){
String aa = (String)obj
}
……
}
四、Map遍歷方式:
先初始化一個map
public class TestMap {
Map<String,String> map = new HashMap<String, String>();
}
1.keySet()方式
(效率低,但是使用普遍。通過鍵找值遍歷,鍵和值都可以遍歷出來。)
原理:將Map轉成Set集合(keySet()),通過Set的迭代器取出Set集合中的每一個元素(Iterator)就是Map集合中的所有的鍵,再通過get方法獲取鍵對應的值。
(1)
Set<String> keySet = map.keySet();
Iterator<String> it = keySet.iterator();
while(it.hasNext()){
String key = it.next();
String value = map.get(key);
System.out.println(key + value);
}
(2)它的增強for迴圈寫法:
for(String key:map.keySet()){
System.out.println(key + map.get(key));
}
2.entrySet()方式
(推薦,尤其是容量大時。這是最常見的並且在大多數情況下也是最可取的遍歷方式。在鍵值都需要時使用。)
原理:通過Map中的entrySet()方法獲取存放Map.Entry<K,V>物件的Set集合。面向物件的思想將map集合中的鍵和值對映關係打包為一個物件,就是Map.Entry,將該物件存入Set集合,Map.Entry是一個物件,那麼該物件具備的getKey,getValue獲得鍵和值。
(1)
Set<Map.Entry<String, String>> entrySet = map.entrySet();
Iterator<Map.Entry<String, String>> it2 = entrySet.iterator();
while(it2.hasNext()){
Map.Entry<String, String> me = it2.next();
String key2 = me.getKey();
String value2 = me.getValue();
System.out.println(key2 + value2);
}
(2)它的增強for迴圈寫法:
for(Map.Entry<String, String> c : map.entrySet()){
System.out.println(c.getKey() + c.getValue());
}
補充說明:
其中:程式中:Set<String> entrySet = map.entrySet();
Iterator<String> it = entrySet.iterator();
相當於: Iterator<String> it = map.entrySet().iterator();
3. 僅需要鍵(keys) keySet()
(1)
Set set = map.keySet();
Iterator iter = set.iterator();
while(iter.hasNext()){
System.out.println(iter.next());
}
(2)它的增強for迴圈寫法:
//遍歷map中的鍵
for (String key : map.keySet()) {
System.out.println("Key = " + key);
}
4.僅需要值(values) values() 不能獲取到key物件
(1)
Collection<String> vs = map.values();
Iterator<String> it = vs.iterator();
while (it.hasNext()) {
String value = it.next();
System.out.println(" value=" + value);
}
(2)它的增強for迴圈寫法:
//遍歷map中的值
for (String value : map.values()) {
System.out.println("Value = " + value);
}
5.基於3.和4.的聯合使用方式,即在for-each迴圈中遍歷keys或values。
都採用增強for迴圈寫法:
Map<String, String> map = new HashMap<String, String>();
//遍歷map中的鍵
for (Integer key : map.keySet()) {
System.out.println("Key = " + key);
}
//遍歷map中的值
for (Integer value : map.values()) {
System.out.println("Value = " + value);
}
綜上所述:Map遍歷方式有9種可以採用,可根據實際業務需要選擇其中合適的一種。
五、資料元素是怎樣在記憶體中存放的?
主要有2種儲存方式:
1、順序儲存,Random Access(Direct Access):
這種方式,相鄰的資料元素存放於相鄰的記憶體地址中,整塊記憶體地址是連續的。可以根據元素的位置直接計算出記憶體地址,直接進行讀取。讀取一個特定位置元素的平均時間複雜度為O(1)。正常來說,只有基於陣列實現的集合,才有這種特性。Java中以ArrayList為代表。
2、鏈式儲存,Sequential Access:
這種方式,每一個數據元素,在記憶體中都不要求處於相鄰的位置,每個資料元素包含它下一個元素的記憶體地址。不可以根據元素的位置直接計算出記憶體地址,只能按順序讀取元素。讀取一個特定位置元素的平均時間複雜度為O(n)。主要以連結串列為代表。Java中以LinkedList為代表。
六、每個遍歷方法的實現原理是什麼?
1、傳統的for迴圈遍歷,基於計數器的:
遍歷者自己在集合外部維護一個計數器,然後依次讀取每一個位置的元素,當讀取到最後一個元素後,停止。主要就是需要按元素的位置來讀取元素。
2、迭代器遍歷,Iterator:
每一個具體實現的資料集合,一般都需要提供相應的Iterator。相比於傳統for迴圈,Iterator取締了顯式的遍歷計數器。所以基於順序儲存集合的Iterator可以直接按位置訪問資料。而基於鏈式儲存集合的Iterator,正常的實現,都是需要儲存當前遍歷的位置。然後根據當前位置來向前或者向後移動指標。
3、foreach迴圈遍歷:
根據反編譯的位元組碼可以發現,foreach內部也是採用了Iterator的方式實現,只不過Java編譯器幫我們生成了這些程式碼。
七、各遍歷方式對於不同的儲存方式,效能如何?
1、傳統的for迴圈遍歷,基於計數器的:
因為是基於元素的位置,按位置讀取。所以我們可以知道,對於順序儲存,因為讀取特定位置元素的平均時間複雜度是O(1),所以遍歷整個集合的平均時間複雜度為O(n)。而對於鏈式儲存,因為讀取特定位置元素的平均時間複雜度是O(n),所以遍歷整個集合的平均時間複雜度為O(n2)(n的平方)。
ArrayList按位置讀取的程式碼:直接按元素位置讀取。
LinkedList按位置讀取的程式碼:每次都需要從第0個元素開始向後讀取。其實它內部也做了小小的優化。
2、迭代器遍歷,Iterator:
那麼對於RandomAccess型別的集合來說,沒有太多意義,反而因為一些額外的操作,還會增加額外的執行時間。但是對於Sequential Access的集合來說,就有很重大的意義了,因為Iterator內部維護了當前遍歷的位置,所以每次遍歷,讀取下一個位置並不需要從集合的第一個元素開始查詢,只要把指標向後移一位就行了,這樣一來,遍歷整個集合的時間複雜度就降低為O(n);
(這裡只用LinkedList做例子)LinkedList的迭代器,內部實現,就是維護當前遍歷的位置,然後操作指標移動就可以了。
3、foreach迴圈遍歷:
分析Java位元組碼可知,foreach內部實現原理,也是通過Iterator實現的,只不過這個Iterator是Java編譯器幫我們生成的,所以我們不需要再手動去編寫。但是因為每次都要做型別轉換檢查,所以花費的時間比Iterator略長。時間複雜度和Iterator一樣。
八、各遍歷方式的適用於什麼場合?
1、傳統的for迴圈遍歷,基於計數器的:
順序儲存:讀取效能比較高。適用於遍歷順序儲存集合。
鏈式儲存:時間複雜度太大,不適用於遍歷鏈式儲存的集合。
2、迭代器遍歷,Iterator:
順序儲存:如果不是太在意時間,推薦選擇此方式,畢竟程式碼更加簡潔,也防止了Off-By-One的問題。
鏈式儲存:意義就重大了,平均時間複雜度降為O(n),還是挺誘人的,所以推薦此種遍歷方式。
3、foreach迴圈遍歷:
foreach只是讓程式碼更加簡潔了,但是他有一些缺點,就是遍歷過程中不能操作資料集合(刪除等),所以有些場合不使用。而且它本身就是基於Iterator實現的,但是由於型別轉換的問題,所以會比直接使用Iterator慢一點,但是還好,時間複雜度都是一樣的。所以怎麼選擇,參考上面兩種方式,做一個折中的選擇。
九、Java的最佳實踐是什麼?
Java資料集合框架中,提供了一個RandomAccess介面,該介面沒有方法,只是一個標記。通常被List介面的實現使用,用來標記該List的實現是否支援Random Access。
一個數據集合實現了該介面,就意味著它支援Random Access,按位置讀取元素的平均時間複雜度為O(1)。比如ArrayList。
而沒有實現該介面的,就表示不支援Random Access。比如LinkedList。
所以看來JDK開發者也是注意到這個問題的,那麼推薦的做法就是,如果想要遍歷一個List,那麼先判斷是否支援Random Access,也就是 list instanceof RandomAccess。
比如:
if (list instanceof RandomAccess) {
//使用傳統的for迴圈遍歷。
} else {
//使用Iterator或者foreach。
}
本篇博文是小白我在大神的肩膀上,吸收整理出來的。衷心感謝大神們的無私奉獻、厚薄積發的精神,這種品質給我們小白在技術上的成長帶來了很大的幫助。
參考:
https://www.cnblogs.com/leskang/p/6031282.html
https://www.cnblogs.com/fqfanqi/p/6187085.html
1.迭代遍歷while
Iterator it = list.iterator();
while(it.hasNext()){
Object obj = it.next();
System.out.println(it.next());
}
2.迭代遍歷for
for(Iterator it = it.iterator(); it.hasNext();){
System.out.printin(it.next());}
二、List遍歷方式:
1.迭代遍歷while:
Iterator iterator = list.iterator();
while(iterator.hasNext()){
int i = (Integer) iterator.next();
System.out.println(i);
}
2.迭代遍歷for:
for(Iterator iterator = list.iterator();iterator.hasNext();){
int i = (Integer) iterator.next();
System.out.println(i);
}
3.增強for迴圈遍歷:
for (Object object : list) {
System.out.println(object);
}
4.類似陣列形式遍歷:
for(int i = 0 ;i<list.size();i++) {
int j= (Integer) list.get(i);
System.out.println(j);
}
三、Set遍歷方式:
1.迭代遍歷while:
Set<String> set = new HashSet<String>();
Iterator<String> it = set.iterator();
while (it.hasNext()) {
String str = it.next();
System.out.println(str);
}
2.迭代遍歷for:
for(Iterator iter = set.iterator();iter.hasNext();){
Integer v = (Integer)iter.next();
System.out.println(v);
}
3.增強for迴圈遍歷:
for (String str : set) {
System.out.println(str);
}
4.增強for迴圈遍歷的補充
優點還體現在泛型 假如 set中存放的是Object
Set<Object> set = new HashSet<Object>();
for迴圈遍歷:
for (Object obj: set) {
if(obj instanceof Integer){
int aa= (Integer)obj;
}else if(obj instanceof String){
String aa = (String)obj
}
……
}
四、Map遍歷方式:
先初始化一個map
public class TestMap {
Map<String,String> map = new HashMap<String, String>();
}
1.keySet()方式
(效率低,但是使用普遍。通過鍵找值遍歷,鍵和值都可以遍歷出來。)
原理:將Map轉成Set集合(keySet()),通過Set的迭代器取出Set集合中的每一個元素(Iterator)就是Map集合中的所有的鍵,再通過get方法獲取鍵對應的值。
(1)
Set<String> keySet = map.keySet();
Iterator<String> it = keySet.iterator();
while(it.hasNext()){
String key = it.next();
String value = map.get(key);
System.out.println(key + value);
}
(2)它的增強for迴圈寫法:
for(String key:map.keySet()){
System.out.println(key + map.get(key));
}
2.entrySet()方式
(推薦,尤其是容量大時。這是最常見的並且在大多數情況下也是最可取的遍歷方式。在鍵值都需要時使用。)
原理:通過Map中的entrySet()方法獲取存放Map.Entry<K,V>物件的Set集合。面向物件的思想將map集合中的鍵和值對映關係打包為一個物件,就是Map.Entry,將該物件存入Set集合,Map.Entry是一個物件,那麼該物件具備的getKey,getValue獲得鍵和值。
(1)
Set<Map.Entry<String, String>> entrySet = map.entrySet();
Iterator<Map.Entry<String, String>> it2 = entrySet.iterator();
while(it2.hasNext()){
Map.Entry<String, String> me = it2.next();
String key2 = me.getKey();
String value2 = me.getValue();
System.out.println(key2 + value2);
}
(2)它的增強for迴圈寫法:
for(Map.Entry<String, String> c : map.entrySet()){
System.out.println(c.getKey() + c.getValue());
}
補充說明:
其中:程式中:Set<String> entrySet = map.entrySet();
Iterator<String> it = entrySet.iterator();
相當於: Iterator<String> it = map.entrySet().iterator();
3. 僅需要鍵(keys) keySet()
(1)
Set set = map.keySet();
Iterator iter = set.iterator();
while(iter.hasNext()){
System.out.println(iter.next());
}
(2)它的增強for迴圈寫法:
//遍歷map中的鍵
for (String key : map.keySet()) {
System.out.println("Key = " + key);
}
4.僅需要值(values) values() 不能獲取到key物件
(1)
Collection<String> vs = map.values();
Iterator<String> it = vs.iterator();
while (it.hasNext()) {
String value = it.next();
System.out.println(" value=" + value);
}
(2)它的增強for迴圈寫法:
//遍歷map中的值
for (String value : map.values()) {
System.out.println("Value = " + value);
}
5.基於3.和4.的聯合使用方式,即在for-each迴圈中遍歷keys或values。
都採用增強for迴圈寫法:
Map<String, String> map = new HashMap<String, String>();
//遍歷map中的鍵
for (Integer key : map.keySet()) {
System.out.println("Key = " + key);
}
//遍歷map中的值
for (Integer value : map.values()) {
System.out.println("Value = " + value);
}
綜上所述:Map遍歷方式有9種可以採用,可根據實際業務需要選擇其中合適的一種。
五、資料元素是怎樣在記憶體中存放的?
主要有2種儲存方式:
1、順序儲存,Random Access(Direct Access):
這種方式,相鄰的資料元素存放於相鄰的記憶體地址中,整塊記憶體地址是連續的。可以根據元素的位置直接計算出記憶體地址,直接進行讀取。讀取一個特定位置元素的平均時間複雜度為O(1)。正常來說,只有基於陣列實現的集合,才有這種特性。Java中以ArrayList為代表。
2、鏈式儲存,Sequential Access:
這種方式,每一個數據元素,在記憶體中都不要求處於相鄰的位置,每個資料元素包含它下一個元素的記憶體地址。不可以根據元素的位置直接計算出記憶體地址,只能按順序讀取元素。讀取一個特定位置元素的平均時間複雜度為O(n)。主要以連結串列為代表。Java中以LinkedList為代表。
六、每個遍歷方法的實現原理是什麼?
1、傳統的for迴圈遍歷,基於計數器的:
遍歷者自己在集合外部維護一個計數器,然後依次讀取每一個位置的元素,當讀取到最後一個元素後,停止。主要就是需要按元素的位置來讀取元素。
2、迭代器遍歷,Iterator:
每一個具體實現的資料集合,一般都需要提供相應的Iterator。相比於傳統for迴圈,Iterator取締了顯式的遍歷計數器。所以基於順序儲存集合的Iterator可以直接按位置訪問資料。而基於鏈式儲存集合的Iterator,正常的實現,都是需要儲存當前遍歷的位置。然後根據當前位置來向前或者向後移動指標。
3、foreach迴圈遍歷:
根據反編譯的位元組碼可以發現,foreach內部也是採用了Iterator的方式實現,只不過Java編譯器幫我們生成了這些程式碼。
七、各遍歷方式對於不同的儲存方式,效能如何?
1、傳統的for迴圈遍歷,基於計數器的:
因為是基於元素的位置,按位置讀取。所以我們可以知道,對於順序儲存,因為讀取特定位置元素的平均時間複雜度是O(1),所以遍歷整個集合的平均時間複雜度為O(n)。而對於鏈式儲存,因為讀取特定位置元素的平均時間複雜度是O(n),所以遍歷整個集合的平均時間複雜度為O(n2)(n的平方)。
ArrayList按位置讀取的程式碼:直接按元素位置讀取。
LinkedList按位置讀取的程式碼:每次都需要從第0個元素開始向後讀取。其實它內部也做了小小的優化。
2、迭代器遍歷,Iterator:
那麼對於RandomAccess型別的集合來說,沒有太多意義,反而因為一些額外的操作,還會增加額外的執行時間。但是對於Sequential Access的集合來說,就有很重大的意義了,因為Iterator內部維護了當前遍歷的位置,所以每次遍歷,讀取下一個位置並不需要從集合的第一個元素開始查詢,只要把指標向後移一位就行了,這樣一來,遍歷整個集合的時間複雜度就降低為O(n);
(這裡只用LinkedList做例子)LinkedList的迭代器,內部實現,就是維護當前遍歷的位置,然後操作指標移動就可以了。
3、foreach迴圈遍歷:
分析Java位元組碼可知,foreach內部實現原理,也是通過Iterator實現的,只不過這個Iterator是Java編譯器幫我們生成的,所以我們不需要再手動去編寫。但是因為每次都要做型別轉換檢查,所以花費的時間比Iterator略長。時間複雜度和Iterator一樣。
八、各遍歷方式的適用於什麼場合?
1、傳統的for迴圈遍歷,基於計數器的:
順序儲存:讀取效能比較高。適用於遍歷順序儲存集合。
鏈式儲存:時間複雜度太大,不適用於遍歷鏈式儲存的集合。
2、迭代器遍歷,Iterator:
順序儲存:如果不是太在意時間,推薦選擇此方式,畢竟程式碼更加簡潔,也防止了Off-By-One的問題。
鏈式儲存:意義就重大了,平均時間複雜度降為O(n),還是挺誘人的,所以推薦此種遍歷方式。
3、foreach迴圈遍歷:
foreach只是讓程式碼更加簡潔了,但是他有一些缺點,就是遍歷過程中不能操作資料集合(刪除等),所以有些場合不使用。而且它本身就是基於Iterator實現的,但是由於型別轉換的問題,所以會比直接使用Iterator慢一點,但是還好,時間複雜度都是一樣的。所以怎麼選擇,參考上面兩種方式,做一個折中的選擇。
九、Java的最佳實踐是什麼?
Java資料集合框架中,提供了一個RandomAccess介面,該介面沒有方法,只是一個標記。通常被List介面的實現使用,用來標記該List的實現是否支援Random Access。
一個數據集合實現了該介面,就意味著它支援Random Access,按位置讀取元素的平均時間複雜度為O(1)。比如ArrayList。
而沒有實現該介面的,就表示不支援Random Access。比如LinkedList。
所以看來JDK開發者也是注意到這個問題的,那麼推薦的做法就是,如果想要遍歷一個List,那麼先判斷是否支援Random Access,也就是 list instanceof RandomAccess。
比如:
if (list instanceof RandomAccess) {
//使用傳統的for迴圈遍歷。
} else {
//使用Iterator或者foreach。
}
本篇博文是小白我在大神的肩膀上,吸收整理出來的。衷心感謝大神們的無私奉獻、厚薄積發的精神,這種品質給我們小白在技術上的成長帶來了很大的幫助。
參考:
https://www.cnblogs.com/leskang/p/6031282.html
https://www.cnblogs.com/fqfanqi/p/6187085.html