对java集合的汇总

集合知识点

什么是集合

  • 集合就是一个放数据的容器,准确的说是放数据对象引用的容器
  • 集合类存放的都是对象的引用,而不是对象本身
  • 集合的类型主要有三种:list,set,map

集合的特点

  • 集合用于存储对象的容器,对象是来封装数据的,对象多了也需要存储集中式管理
  • 和数组相比较,集合的长度是可变的,大小不确定

集合和数组的区别

  1. 集合容量是自增的,数组是固定的
  2. 集合底层是高性能的数据结构和算法,提高了程序速度和质量
  3. 集合方便扩展,提高代码的复用性
  4. 集合存储是引用数据类型,而数组可以为基本数据类型和引用数据类型

常用的集合类有哪些

Map和Collection接口是所有集合框架的父接口

  • collection接口的子接口是List和set
  • Map接口的实现类主要有HashMap.TreeMap,HashTable,ConcurrentHashMap
  • set接口的实现类主要有TreeSet,HashSet,LinkedHashSet
  • List接口的实现类主要有ArrayList,LinkedList,vector

List,Map,Set三者区别

在这里插入图片描述

集合底层的数据结构

  • collection接口
    • List
      1. ArrayList:object数组
      2. LinkedList:双向链表
      3. Vector:Object数组
    • Set
      1. HashSet(无序,唯一):基于hashmap实现,底层采用hashMap保存元素
      2. linkedHashSet:LinkedHashSet继承于HashSet,并且内部是通过LinkedHashMap来实现的
      3. TreeSet(有序,唯一):红黑树(自平衡的二叉搜索树)
  • Map
    • HashMap:JDK1.8之前HashMap有数组和链表组成,数组是HashMap的主体,链表则是为解决hash冲突而存在的(拉链法),JDK1.8之后在解决hash冲突时有了比较大的变化,当链表长度大于阈值(默认为8),将链表转化为红黑树,以减少搜索时间
    • LinkedHashMap:继承Hashmap,所以底层也是拉链式散列结构
    • HashTable:数组+链表实现
    • TreeMap:红黑树实现

哪些集合是线程安全的

  • vector:就比ArrayList多了一个synchronize(线程安全)
  • hashTable:就比HashMap多了个synchronize(线程安全)
  • concurrentHashMap:高并发,高吞吐量的线程线程安全.由segment数组结构和hashEntry数组结构组成.segment在concurrentHashMap中扮演锁的角色,HashEntry则用于存储键值对数据.一个concurrentHashMap里面包含一个segment数组,segment结构和hashmap类似,是一种数组+链表结构;一个segment里面包含一个HashEntry数组,每一个HashEntry有链表组成;每个segment守护hashentry数组里面的元素,当HashEntry数组里面的数据进行修改时,必须先获得对应的segment锁

快速失败机制Fail-Fast

fail-fast是java集合的一种错误检测机制,当多个线程对集合进行结构上的改变的操作时,有可能会产生fail-fast

案例:假设有两个线程(线程1,线程2),线程1通过Iterator在遍历集合A中的元素,在某个时候线程2修改了集合A的结构(是结构上的修改,不是简单的修改集合元素的内容),那么这时候就会抛出ConcurrentModificationException异常,从而产生fail-fast机制

​ 原因:迭代器在遍历集合时直接访问集合中的内容,并且遍历过程中使用一个modCount变量.集合在被遍历期间如果内容发生改变,就会改变MODCount的值.每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测MODCount是否改变

​ 解决办法:在遍历过程中,所有涉及到改变MODCount值的地方全部加上synchronized

使用copyonwriteArrayList来替换ArrayList

怎么保证一个集合不被改变

可以使用Collections.unmodifiableCollection(Collection c)创建一个可读集合

ArrayList<String> arrayList =  new ArrayList<>();
Collection<String> strings = Collections.unmodifiableCollection(arrayList);
strings.add("lsy");
System.out.println(strings);

运行结果:

Exception in thread "main" java.lang.UnsupportedOperationException
	at java.util.Collections$UnmodifiableCollection.add(Collections.java:1057)
	at com.lsy.collection.List.main(List.java:14)

在多线程场景下如何使用ArrayList

使用Collections.synchronizedList()方法将其转换成线程安全后在使用

ArrayList<String> arrayList = new ArrayList<>();
java.util.List<String> strings = Collections.synchronizedList(arrayList);
strings.add("aaa");

Iterator

Iterator是单向遍历集合,但是相对于更加安全,因为他可以确保集合元素被更改的时候,就会抛出ConcurrentModificationException异常

ListIterator和Iterator区别

  • Iterator可以遍历set和list集合,但是ListIterator只可以遍历list
  • ListIterator可以双向遍历,Iterator只可以单向遍历
  • ListIterator里面的方法更多

Iterator的使用

 ArrayList<String> arrayList = new ArrayList<>();
 for (int i = 0; i < 10; i++) {
     arrayList.add(""+i);
 }
 arrayList.toArray();
 Iterator<String> iterator = arrayList.iterator();
while (iterator.hasNext()){
    System.out.println(iterator.next());
}

HashSet原理

  • HashSet其实就是HashMap用来实现的,HashSet的值放到HashMap的key上,HashMap的值为Present
  • HashSet的相关操作基本上都是调用HashMap的相关方法来实现

hashSet构造方法

public HashSet() {
    map = new HashMap<>();
}


public HashSet(Collection<? extends E> c) {
    map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
    addAll(c);
}


public HashSet(int initialCapacity, float loadFactor) {
    map = new HashMap<>(initialCapacity, loadFactor);
}


public HashSet(int initialCapacity) {
    map = new HashMap<>(initialCapacity);
}


HashSet(int initialCapacity, float loadFactor, boolean dummy) {
    map = new LinkedHashMap<>(initialCapacity, loadFactor);
}

HashSet如何检查重复?如何保证数据不重复

  • 向HashSet中add()元素时,首先会判断元素是否存在,不仅要比较hash值,同时还要结合equals方法比较
  • HashSet中的add方法会使用HashMap的put()方法(HashMap比较Key是否相等是先比较hashcode再比较)
public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}

什么是hash算法

哈希算法是指把任意长度的二进制映射为固定长度的较小的二进制值,这个较小的二进制值叫做哈希值

HashMap的实现原理

HashMap是基于哈希表的map接口的非同步实现,此实现提供所有可选的映射操作,并允许使用null值和null键

底层数据结构:在jdk1.7底层结构为数组+链表,数组为主体,链表为解决hash冲突,也就是说一个数组的每一格就是一个链表,若遇到hash冲突,就将冲突的值添加到链表中

在这里插入图片描述

在jdk1.8之后,为解决hash冲突有了比较大的变化,当链表大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间

在这里插入图片描述

不同jdk1.7jdk1.8
存储结构数组+链表数组+链表+红黑树
初始化方式单独函数:inflateTable继承到resize函数中
hash值计算方法扰动处理:9次扰动=4次位运算+5次异或运算扰动处理:2次扰动=1次位运算+1次异或运算
存放规则无冲突时存放数组;冲突时,存放到链表无冲突时,存放数组;冲突时:链表长度<8存放单链表,链表长度>8存放红黑树
插入数据方式头插法(先将原位置的元素后移一位,再将数据插入该位置)尾插法
扩容后存储位置的计算方式全部按照原来方法进行计算按照扩容后的规律计算(扩容后的位置=原位置or(原位置+就容量))

HashMap的长度为什么是2的幂次方

  • 为了能让HashMap存取高效,尽量减少碰撞,也就是把数据均匀分配,每个链表/红黑树长度大致一样

这个算法如何设计:首先我们想到的是取余操作,但是取余%操作如果除数是2的幂次方则等价于其除数减一的与(&)操作(hash%length==hash&(length-1))

为什么是两次扰动:这样加大哈希值低位的随机性,使得分布更均匀,从而提高对应数组存储下标位置的随机性和均匀性

HashMap与HashTable有什么区别

区别HashMapHashTable
线程安全非线性安全线程安全(内部方法都经过synchronize修饰)
效率
对null的支持null可以作为键,但是只可以有一个null键,可以有多个null值只有put一个null值,就会报错
初始化容量初始化大小16,之后每次扩容,容量变为2倍初始化大小11,容量变为原来的2n+1

TreeMap简介

  • Tree是一个有序的Key-value集合,通过红黑树实现
  • 线程同步

如何决定使用HashMap还是TreeMap

在Map中插入,删除,查找元素这类操作时,HashMap最好,但是如果需要对一个有序的key集合进行遍历,可以选择TreeMap

说一下ConcurrentHashMap

ConcurrentHashMap用分段锁实现,HashTable用一把锁实现

JDK1.7:

  • 首先将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个数据时,其他段的数据也可能被其他线程访问在这里插入图片描述

  • 采用segment和HashEntry的方式进行实现,一个concurrentHashMap包括一个segment数组,一个segment数组包括一个HashEntry数组

在这里插入图片描述

JDK1.8

放弃了臃肿的Segment设计,采取的是采用Node+CAS+Synchronized来保证并发安全进行实现,synchronize只锁定当前链表或者红黑树的首节点,这样只要hash不冲突,就不会产生并发
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值