日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

【腾讯面试题】Java集合:List、Set以及Map

發布時間:2024/10/8 java 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【腾讯面试题】Java集合:List、Set以及Map 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

Java集合:List、Set以及Map

  • 概述
  • Collection接口
    • List:有序,可重復
      • ArraysList
      • Vector
      • LinkedList
    • Set:無序,唯一
      • HashSet
      • LinkedHashSet
      • Tree Set
  • Map接口
    • TreeSet,HashSet,LinkedHashSet的區別
      • 1.介紹
      • 2.相同點
      • 3.不同點
      • 4.代碼比較
    • TreeSet的兩種排序方式比較
      • 1.排序的引入(以基本數據類型的排序為例)
      • 2.如果是引用數據類型呢,比如自定義對象,又該如何排序呢?
        • (1).自然排序
        • (2).比較器排序
        • (三) 性能測試
  • Java常見集合的默認大小及擴容機制
    • List:元素有序的、可重復的
      • ArrayList、Vector默認初始容量為10
      • Vector:線程安全,速度慢,擴容10-->20
      • ArrayList:線程不安全,速度快,擴容10-->16
    • Set:元素無序、不可重復
      • HashSet:線程不安全,存取速度快,擴容16-->32
    • Map:雙列集合
      • HashMap:默認初始容量為16,一次擴容為32
    • HashMap數組長度為什么是2的次冪?
    • 為什么HashMap的數組初始化是2的次方時效率最高?
  • 參考資料

概述

List , Set, Map都是接口,前兩個繼承至Collection接口,Map為獨立接口

List下有ArrayList,Vector,LinkedList

Set下有HashSet,LinkedHashSet,TreeSet

Map下有Hashtable,LinkedHashMap,HashMap,TreeMap

Collection接口下還有個Queue接口,有PriorityQueue類


Queue接口與List、Set同一級別,都是繼承了Collection接口。看圖你會發現,LinkedList既可以實現Queue接口,也可以實現List接口。只不過呢,LinkedList實現了Queue接口。Queue接口窄化了對LinkedList的方法的訪問權限(即在方法中的參數類型如果是Queue時,就完全只能訪問Queue接口所定義的方法了,而不能直接訪問 LinkedList的非Queue的方法),以使得只有恰當的方法才可以使用。

SortedSet是個接口,它里面的(只有TreeSet這一個實現可用)中的元素一定是有序的。

Collection接口

List:有序,可重復

ArraysList

優點: 底層數據結構是數組,查詢快,增刪慢。
缺點: 線程不安全,效率高

Vector

優點: 底層數據結構是數組,查詢快,增刪慢。
缺點: 線程安全,效率低

LinkedList

優點: 底層數據結構是鏈表,查詢慢,增刪快。
缺點: 線程不安全,效率高

Set:無序,唯一

HashSet

底層數據結構是哈希表。(無序,唯一)
如何來保證元素唯一性?
依賴兩個方法:hashCode()和equals()

LinkedHashSet

底層數據結構是鏈表和哈希表。(FIFO插入有序,唯一)
1.由鏈表保證元素有序
2.由哈希表保證元素唯一

Tree Set

底層數據結構是紅黑樹。(有序,唯一)
1.如何保證元素排序的呢? 自然排序和比較器排序
2.如何保證元素唯一性的呢? 根據比較的返回值是否是0來決定

Map接口


Map接口有三個比較重要的實現類,分別是HashMap、TreeMap和HashTable。

  • HashMap和HashTable是無序的,TreeMap是有序的
  • Hashtable的方法是同步的,HashMap的方法不是同步的。這是兩者最主要的區別。

這就意味著:

  • Hashtable是線程安全的,HashMap不是線程安全的。
  • Hashtable效率較低,HashMap效率較高。
  • 如果對同步性或與遺留代碼的兼容性沒有任何要求,建議使用HashMap。
  • 查看Hashtable的源代碼就可以發現,除構造函數外,Hashtable的所有 public 方法聲明中都有 synchronized關鍵字,而HashMap的源碼中則沒有。
  • Hashtable不允許null值,HashMap允許null值(key和value都允許)
  • 父類不同:Hashtable的父類是Dictionary,HashMap的父類是AbstractMap

TreeSet,HashSet,LinkedHashSet的區別

1.介紹

TreeSet, LinkedHashSet and HashSet 在java中都是實現Set的數據結構

  • TreeSet的主要功能用于排序
  • LinkedHashSet的主要功能用于保證FIFO即有序的集合(先進先出)
  • HashSet只是通用的存儲數據的集合

2.相同點

  • Duplicates elements: 因為三者都實現Set interface,所以三者都不包含相同元素
  • Thread safety: 三者都不是線程安全的,如果要使用線程安全可以Collections.synchronizedSet()

3.不同點

  • Performance and Speed: HashSet插入數據最快,其次LinkHashSet,最慢的是TreeSet,因為內部實現排序
  • Ordering: HashSet不保證有序,LinkHashSet保證FIFO即按插入順序排序,TreeSet安裝內部實現排序,也可以自定義排序規則
  • null:HashSet和LinkHashSet允許存在null數據,但是TreeSet中插入null數據時會報NullPointerException

4.代碼比較

public static void main(String args[]) {HashSet<String> hashSet = new HashSet<>();LinkedHashSet<String> linkedHashSet = new LinkedHashSet<>();TreeSet<String> treeSet = new TreeSet<>();for (String data : Arrays.asList("B", "E", "D", "C", "A")) {hashSet.add(data);linkedHashSet.add(data);treeSet.add(data);}//不保證有序System.out.println("Ordering in HashSet :" + hashSet);//FIFO保證安裝插入順序排序System.out.println("Order of element in LinkedHashSet :" + linkedHashSet);//內部實現排序System.out.println("Order of objects in TreeSet :" + treeSet);} 運行結果: Ordering in HashSet :[A, B, C, D, E] (無順序) Order of element in LinkedHashSet :[B, E, D, C, A] (FIFO插入有序) Order of objects in TreeSet :[A, B, C, D, E] (排序)

TreeSet的兩種排序方式比較

1.排序的引入(以基本數據類型的排序為例)

由于TreeSet可以實現對元素按照某種規則進行排序,例如下面的例子

public class MyClass {public static void main(String[] args) {// 創建集合對象// 自然順序進行排序TreeSet<Integer> ts = new TreeSet<Integer>();// 創建元素并添加// 20,18,23,22,17,24,19,18,24ts.add(20);ts.add(18);ts.add(23);ts.add(22);ts.add(17);ts.add(24);ts.add(19);ts.add(18);ts.add(24);// 遍歷for (Integer i : ts) {System.out.println(i);}} } 運行結果: 17 18 19 20 22 23 24

2.如果是引用數據類型呢,比如自定義對象,又該如何排序呢?

public class MyClass {public static void main(String[] args) {TreeSet<Student> ts=new TreeSet<Student>();//創建元素對象Student s1=new Student("zhangsan",20);Student s2=new Student("lis",22);Student s3=new Student("wangwu",24);Student s4=new Student("chenliu",26);Student s5=new Student("zhangsan",22);Student s6=new Student("qianqi",24);//將元素對象添加到集合對象中ts.add(s1);ts.add(s2);ts.add(s3);ts.add(s4);ts.add(s5);ts.add(s6);//遍歷for(Student s:ts){System.out.println(s.getName()+"-----------"+s.getAge());}} } public class Student {private String name;private int age;public Student() {super();// TODO Auto-generated constructor stub}public Student(String name, int age) {super();this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;} }

原因分析:
由于不知道該安照那一中排序方式排序,所以會報錯。

解決方法:
1.自然排序
2.比較器排序

(1).自然排序

自然排序要進行一下操作:
1.Student類中實現 Comparable接口
2.重寫Comparable接口中的Compareto方法

compareTo(T o) 比較此對象與指定對象的順序。 public class Student implements Comparable<Student>{private String name;private int age;public Student() {super();// TODO Auto-generated constructor stub}public Student(String name, int age) {super();this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic int compareTo(Student s) {//return -1; //-1表示放在紅黑樹的左邊,即逆序輸出//return 1; //1表示放在紅黑樹的右邊,即順序輸出//return o; //表示元素相同,僅存放第一個元素//主要條件 姓名的長度,如果姓名長度小的就放在左子樹,否則放在右子樹int num=this.name.length()-s.name.length();//姓名的長度相同,不代表內容相同,如果按字典順序此 String 對象位于參數字符串之前,則比較結果為一個負整數。//如果按字典順序此 String 對象位于參數字符串之后,則比較結果為一個正整數。//如果這兩個字符串相等,則結果為 0int num1=num==0?this.name.compareTo(s.name):num;//姓名的長度和內容相同,不代表年齡相同,所以還要判斷年齡int num2=num1==0?this.age-s.age:num1;return num2;} }

運行結果:

lis-----------22 qianqi-----------24 wangwu-----------24 chenliu-----------26 zhangsan-----------20 zhangsan-----------22

(2).比較器排序

比較器排序步驟:
1.單獨創建一個比較類,這里以MyComparator為例,并且要讓其繼承Comparator接口
2.重寫Comparator接口中的Compare方法

compare(T o1,T o2) 比較用來排序的兩個參數。

3.在主類中使用下面的 構造方法

TreeSet(Comparator<? superE> comparator) //構造一個新的空 TreeSet,它根據指定比較器進行排序。 public class MyClass {public static void main(String[] args) {//創建集合對象//TreeSet(Comparator<? super E> comparator) 構造一個新的空 TreeSet,它根據指定比較器進行排序。TreeSet<Student> ts=new TreeSet<Student>(new MyComparator());//創建元素對象Student s1=new Student("zhangsan",20);Student s2=new Student("lis",22);Student s3=new Student("wangwu",24);Student s4=new Student("chenliu",26);Student s5=new Student("zhangsan",22);Student s6=new Student("qianqi",24);//將元素對象添加到集合對象中ts.add(s1);ts.add(s2);ts.add(s3);ts.add(s4);ts.add(s5);ts.add(s6);//遍歷for(Student s:ts){System.out.println(s.getName()+"-----------"+s.getAge());}} } public class Student {private String name;private int age;public Student() {super();// TODO Auto-generated constructor stub}public Student(String name, int age) {super();this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}} public class MyComparator implements Comparator<Student> {@Overridepublic int compare(Student s1,Student s2) {// 姓名長度int num = s1.getName().length() - s2.getName().length();// 姓名內容int num2 = num == 0 ? s1.getName().compareTo(s2.getName()) : num;// 年齡int num3 = num2 == 0 ? s1.getAge() - s2.getAge() : num2;return num3;}}

運行結果:

lis-----------22 qianqi-----------24 wangwu-----------24 chenliu-----------26 zhangsan-----------20 zhangsan-----------22

(三) 性能測試

class Dog implements Comparable<Dog> {int size;public Dog(int s) {size = s;}public String toString() {return size + "";}@Overridepublic int compareTo(Dog o) {//數值大小比較return size - o.size;} } public class MyClass {public static void main(String[] args) {Random r = new Random();HashSet<Dog> hashSet = new HashSet<Dog>();TreeSet<Dog> treeSet = new TreeSet<Dog>();LinkedHashSet<Dog> linkedSet = new LinkedHashSet<Dog>();// start timelong startTime = System.nanoTime();for (int i = 0; i < 1000; i++) {int x = r.nextInt(1000 - 10) + 10;hashSet.add(new Dog(x));}// end timelong endTime = System.nanoTime();long duration = endTime - startTime;System.out.println("HashSet: " + duration);// start timestartTime = System.nanoTime();for (int i = 0; i < 1000; i++) {int x = r.nextInt(1000 - 10) + 10;treeSet.add(new Dog(x));}// end timeendTime = System.nanoTime();duration = endTime - startTime;System.out.println("TreeSet: " + duration);// start timestartTime = System.nanoTime();for (int i = 0; i < 1000; i++) {int x = r.nextInt(1000 - 10) + 10;linkedSet.add(new Dog(x));}// end timeendTime = System.nanoTime();duration = endTime - startTime;System.out.println("LinkedHashSet: " + duration);}}

Java常見集合的默認大小及擴容機制

在面試后臺開發的過程中,集合是面試的熱話題,不僅要知道各集合的區別用法,還要知道集合的擴容機制,今天我們就來談下ArrayList 和 HashMap的默認大小以及擴容機制。

在 Java 8 中,查看源碼可以知道:ArrayList 的默認大小是 10 個元素,HashMap 的默認大小是16個元素(必須是2的冪,為什么呢???下文有解釋)。這就是 Java 8 中 ArrayList 和 HashMap 類 的代碼片段:

// from ArrayList.java JDK 1.7 private static final int DEFAULT_CAPACITY = 10;//from HashMap.java JDK 7 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

這里要討論這些常用的默認初始容量和擴容的原因是:

當底層實現涉及到擴容時,容器需要重新分配一段更大的連續內存(如果是離散分配,則不需要重新分配,離散分配都是插入新元素時,動態分配內存),要將容器原來的數據全部復制到新的內存上,這無疑使效率大大降低。加載因子的系數小于等于1,意指即當元素個數超過容量長度*加載因子的系數時,進行擴容。另外,擴容也是有默認的倍數的,不同的容器擴容情況不同。

List:元素有序的、可重復的

ArrayList、Vector默認初始容量為10

Vector:線程安全,速度慢,擴容10–>20

底層數據結構是數組結構

加載因子為1:即當 元素個數 超過 容量長度 時,進行擴容

擴容增量:原容量的 1倍

如 Vector的容量為10,一次擴容后是容量為20

ArrayList:線程不安全,速度快,擴容10–>16

底層數據結構是數組結構

擴容增量:原容量的 0.5倍+1

如 ArrayList的容量為10,一次擴容后是容量為16

Set:元素無序、不可重復

HashSet:線程不安全,存取速度快,擴容16–>32

底層實現是一個HashMap(保存數據),實現Set接口

默認初始容量為16(為何是16,見下方對HashMap的描述)

加載因子為0.75:即當 元素個數 超過 容量長度的0.75倍 時,進行擴容

擴容增量:原容量的 1 倍

如 HashSet的容量為16,一次擴容后是容量為32

Map:雙列集合

HashMap:默認初始容量為16,一次擴容為32

(為何是16:16是2^4,可以提高查詢效率,另外,32=16<<1)

加載因子為0.75:即當 元素個數 超過 容量長度的0.75倍 時,進行擴容

擴容增量:原容量的 1 倍

如 HashSet的容量為16,一次擴容后是容量為32

HashMap數組長度為什么是2的次冪?

hashMap的數組長度一定保持2的次冪,比如16的二進制表示為 10000,那么length-1就是15,二進制為01111,同理擴容后的數組長度為32,二進制表示為100000,length-1為31,二進制表示為011111。

這樣會保證低位全為1,而擴容后只有一位差異,也就是多出了最左位的1,這樣在通過 h&(length-1)的時候,只要h對應的最左邊的那一個差異位為0,就能保證得到的新的數組索引和老數組索引一致(大大減少了之前已經散列良好的老數組的數據位置重新調換),還有,數組長度保持2的次冪,length-1的低位都為1,會使得獲得的數組索引index更加均勻。

static int indexFor(int h, int length) { return h & (length-1); }

首先計算得到key的hashcode值,然后跟數組的長度-1做一次“與”運算(&)。看上去很簡單,其實比較有玄機。比如數組的長度是2的4次方,那么hashcode就會和2的4次方-1做“與”運算。

為什么HashMap的數組初始化是2的次方時效率最高?

很多人都有這個疑問,為什么hashmap的數組初始化大小都是2的次方大小時,hashmap的效率最高,我以2的4次方舉例,來解釋一下為什么數組大小為2的冪時hashmap訪問的性能最高。

看下圖,左邊兩組是數組長度為16(2的4次方),右邊兩組是數組長度為15。兩組的hashcode均為8和9,但是很明顯,當它們和1110“與”的時候,產生了相同的結果,也就是說它們會定位到數組中的同一個位置上去,這就產生了碰撞,8和9會被放到同一個鏈表上,那么查詢的時候就需要遍歷這個鏈表,得到8或者9,這樣就降低了查詢的效率。

同時,我們也可以發現,當數組長度為15的時候,hashcode的值會與14(1110)進行“與”,那么最后一位永遠是0,而0001,0011,0101,1001,1011,0111,1101這幾個位置永遠都不能存放元素了,空間浪費相當大,更糟的是這種情況中,數組可以使用的位置比數組長度小了很多,這意味著進一步增加了碰撞的幾率,減慢了查詢的效率!


所以說,當數組長度為2的n次冪的時候,不同的key計算得到的index相同的幾率較小,那么數據在數組上分布就比較均勻,也就是說碰撞的幾率小,相對的,查詢的時候就不用遍歷某個位置上的鏈表,這樣查詢效率也就較高了。

說到這里,我們再回頭看一下hashmap中默認的數組大小是多少,查看源代碼可以得知是16,為什么是16,而不是15,也不是20呢,看到上面的解釋之后我們就清楚了吧,顯然是因為16是2的整數次冪的原因,

在小數據量的情況下16比15和20更能減少key之間的碰撞,而加快查詢的效率。

參考資料

  • https://blog.csdn.net/zhangqunshuai/article/details/80660974
  • https://www.cnblogs.com/whu-2017/p/9677212.html
  • 總結

    以上是生活随笔為你收集整理的【腾讯面试题】Java集合:List、Set以及Map的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。