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之间的碰撞,而加快查询的效率。

参考资料

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

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

  1. Java集合Set,List和Map等

    Java集合Set,List和Map等 1 Java集合框架 因为Java是面向对象的语言,对事物的体现都是以对象的形式,为了方便对多个对象的操作,就要对对象进行存储.另一方面,使用Array存储对象 ...

  2. Java集合:Set、Map、List

    文章目录 Java集合:Set.Map.List 一.List 常用方法 1.增加功能 2.删除功能 3.修改功能 4.获取功能 5.判断功能 6.长度功能 7.把集合转换成数组 二.Set 常用方法 ...

  3. 有容乃大 ——Java集合(List/Set/Map)

    有容乃大 --Java集合(List/Set/Map) 数据结构的概念 数据结构定义: 数据结构是计算机对数据存储的一种安排. 就是计算机组织.存储数据的方式. 数据结构有哪些? 堆.栈.数组.队列. ...

  4. Java集合框架List,Map,Set等全面介绍

    Java Collections Framework是Java提供的对集合进行定义,操作,和管理的包含一组接口,类的体系结构. Java集合框架的基本接口/类层次结构: java.util.Colle ...

  5. 【Java 集合】Java 集合主要脉络 ( Collection | Map | List | Set )

    文章目录 I 集合脉络 II List 接口简介 III Set 接口简介 IV Map 接口简介 V Collection 接口定义的方法 I 集合脉络 集合主要脉络 : Java 集合大的分类为两 ...

  6. Java集合:Collection和Map

    Collection是Java集合的一个接口,其实现类有Set.List等: Collections是集合的工具类,提供了一系列的静态方法,如排序.搜索等: 说说collection里面有什么子类. ...

  7. Java 集合框架 : Collection、Map

    1. Collection接口是Java集合框架的基本接口,所所有集合都继承该接口. 1.1 方法 : public interface Collection<E> extends Ite ...

  8. Java 集合系列14之 Map总结(HashMap, Hashtable, TreeMap, WeakHashMap等使用场景)

    概要 学完了Map的全部内容,我们再回头开开Map的框架图. 本章内容包括: 第1部分 Map概括 第2部分 HashMap和Hashtable异同 第3部分 HashMap和WeakHashMap异 ...

  9. Java基础学习——Java集合(九)Map接口、HashMap、LinkedHashMap实现类、TreeMap实现类

    一.Map接口 二.HashMap实现类.LinkedHashMap实现类 1.HashMap的特点 1)无序,唯一(key唯一,因为底层key按照哈希表(数组+链表)的结构) 2)放入集合的数据的类 ...

  10. JAVA集合Connection接口和Map接口常用的类及区别

    JAVA集合详解 文章目录 JAVA集合详解 前言 一.集合是什么? 1. 集合类 2.集合和数组的区别: 二.使用步骤 一.Connection接口(单列集合) 1.List和Set的优缺点 2.总 ...

最新文章

  1. 20种看asp源码的方法及工具
  2. Codeforces Gym 100418K Cards 暴力打表
  3. 右键菜单添加程序,指定图标, Notepad2、Sublime Text 2
  4. 罚函数法求解约束问题最优解
  5. 27、jdbc操作数据库(4)
  6. ionic 组件之二维码扫描
  7. 使用前端node.js 提供的服务器live-server
  8. python3 数据结构_Python3数据结构
  9. 创建Maven项目时提示web.xml is missing and failOnMissingWebXml is set to true错误解决方案
  10. 读《你的灯亮这么》---走出问题的乌托邦
  11. python aes加密每次密码不一样_记AES加密在linux系统每次都不一样的问题
  12. comsol 超声声场模拟_[转载]Comsol Multiphysics 声场仿真模块整体介绍
  13. 「Web全栈工程师的自我修养」读后感
  14. 体验极好的临时邮箱,10分钟邮箱,极美观,速度特别快
  15. Yunxion资产监测设备中DCDC芯片选择
  16. 配置RHEL7的vnc
  17. 0X00000000指令引用的0x00000000内存该内存不能为read或written
  18. dedecms织梦后台系统用户管理授权栏目BUG修正
  19. 推荐几个阿里,百度大佬的订阅号给大家
  20. 变量的生存期与存储类型

热门文章

  1. [SDOI2009]HH去散步(矩阵)
  2. UBUNTU安装 Rabbitvsc可视化版本控制客户端软件
  3. FinanceJson
  4. Android系统进程Zygote启动过程的源代码分析
  5. iOS内存暴增问题追查与使用陷阱
  6. [分享]Windows Phone 7 For Dummies
  7. java多线程编程_Java多线程编程实战指南+设计模式篇.pdf
  8. android传输注册数据异常,android数据传值再获取的问题
  9. java五子棋用到的类_JAVA五子棋用到的知识点以及方法类有哪些?
  10. 前段react技术架构图_基于 React 的可视化编辑平台实践