Java 数据结构相关

一、数据结构

List,Set,Map他们的子类实现都有差别,比如有些是同步的有些是非同的,而非同步可在new创建的时候加上Collections.synchronizedList()来封装成同步的

List:

	1、Vector :底层是数组,同步的
		是将所有的方法统统加上synchronized实现的。
		扩容:每次扩容是之前容量的2倍
	2、ArrayList :底层是数组,非同步的。
		默认数组容量是0,只有真正对数据进行添加add时,才分配默认10的初始容量。
		扩容:每次扩容是之前容量的1.5倍
			长度为10的数组,现在我们要新增一个元素,发现已经满了。
			1、会重新定义一个长度为10+10/2的数组也就是新增一个长度为15的数组。
			2、把原数组的数据,原封不动的复制到新数组中,这个时候再把指向原数的地址换到新数组,ArrayList就这样完成了一次改头换面。
		查询快,增删慢
			1、有指定index新增,也有直接新增的,在这之前他会有一步校验长度的判断ensureCapacityInternal,就是说如果长度不够,是需要扩容的。
			2、在扩容的时候,老版本的jdk和8以后的版本是有区别的,8之后的效率更高了,采用了位运算,右移一位,其实就是除以2这个操作。
			3、指定位置新增的时候,在校验之后的操作很简单,就是数组的copy,效率慢。
			4、删除和新增是一样的,也是对数组进行copy。
	3、LinkedList :底层是双向链表,非同步的
		增删快,查询慢
			1、支持头插入、尾插入
			2、查询是从头开始一个一个进行比较,数据量较大时效率慢

Set: 结构其实就是维护一个Map来存储数据的,利用Map的key值唯一性。

	1、HashSet是通过HashMap实现的,TreeSet是通过TreeMap实现的,只不过Set用的只是Map的key

数组是查询效率最快的数据结构

Map:

	1、HashMap实现结构是将里面的key、value封装成一个Entry对象(Java7叫Entry,Java8叫Node),然后在放进Entry数组里,而他的位置是由key的哈希值与数组长度(默认16----经验值、仅认为该容量符合常用)计算而来,数组+链表(散列数组,数组索引就是key的hash值)

HashMap:

1. 是一个最常用的Map,它根据键的HashCode值存储数据,根据键可以直接获取它的值,具有很快的访问速度。遍历时,取得数据的顺序是完全随机的。最多只允许一条记录的key为Null;允许多条记录的value为 Null。
2. 非线程安全的(即任一时刻可以有多个线程同时写HashMap),可能会导致数据的不一致。
迭代器:HashMap 中的 Iterator 迭代器是 fail-fast 的
扩容机制:根据LoadFactor(负载因子,默认值0.75f),当容量达到该值时会进行扩容,创建一个新的Entry空数组长度是原数组2倍,遍历原数组把所有的Entry重新Hash到新数组。
插入机制:
	1.7是头插法:考虑到了一个所谓的热点数据的点(新插入的数据可能会更早用到),因扩容转移会导致循环列表。
	1.8是尾插法:扩容转移后前后链表顺序不变,保持之前节点的引用关系。
链表存储:
	1.8采用了红黑树:链表存储将7作为一个分水岭,等于7的时候不转换,大于等于8的时候才进行转换,小于等于6的时候就化为链表。
线程安全:可以用 Collections的synchronizedMap方法使HashMap具有同步的能力,其内部维护了一个普通对象Map,还有排斥锁mutex(创建时可使用维护的Map、也可用传入的Map),当操作Map时,就会对方法上锁。
或者使用ConcurrentHashMap。在单线程中速度快

Hashtable:

1. HashTable的key和value都不允许为Null值,put空值时会抛空指针异常,因为使用到了安全失败机制(fail-safe)会使你此次读到的数据不一定是最新的数据。如果使用null值,就会使得其无法判断对应的key是不存在还是为空。
2. 线程安全的,在对数据操作的时候都会上锁,所以效率比较低下。
扩容机制:默认的初始大小为11,之后每次扩充为原来的2n+1。

ConcurrentHashMap:

1. ConcurrentHashMap的key和value都不允许为Null值,put空值时会抛空指针异常,因为使用到了安全失败机制(fail-safe)会使你此次读到的数据不一定是最新的数据。如果使用null值,就会使得其无法判断对应的key是不存在还是为空。
2. 底层是基于 数组 + 链表 组成。
	1.7:Segment 数组、HashEntry 组成。
		采用了分段锁技术,其中Segment继承于ReentrantLock,理论上ConcurrentHashMap支持CurrencyLevel(Segment数组数量)的线程并发。
		put:先定位到Segment然后在进行Put操作,源码中:首先第一步的时候会尝试获取锁,如果获取失败肯定就有其他线程存在竞争,则利用 scanAndLockForPut() 自旋获取锁。
		get:只需要将 Key 通过 Hash 之后定位到具体的 Segment ,再通过一次Hash定位到具体的元素上。由于HashEntry中的value属性是用volatile关键词修饰的,保证了内存可见性,所以每次获取时都是最新值。(整个过程不加锁)
	1.8:
		抛弃了原有的Segment分段锁,而采用了 CAS + synchronized 来保证并发安全性。引入了红黑树,在链表大于一定值的时候会转换(默认是8)。
		put:
			1、根据 key 计算出 hashcode 。
			2、判断是否需要进行初始化。
			3、即为当前 key 定位出的 Node,如果为空表示当前位置可以写入数据,利用 CAS 尝试写入,失败则自旋保证成功。
			4、如果当前位置的 hashcode == MOVED == -1,则需要进行扩容。
			5、如果都不满足,则利用 synchronized 锁写入数据。
			6、如果数量大于 TREEIFY_THRESHOLD 则要转换为红黑树。
		get:
			1、根据计算出来的 hashcode 寻址,如果就在桶上那么直接返回值。
			2、如果是红黑树那就按照树的方式获取值。
			3、就不满足那就按照链表的方式遍历获取值。

LinkedHashMap:

1. LinkedHashMap保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的。也可以在构造时带参数,按照应用次数排序。
2. 在遍历的时候会比HashMap慢,不过有种情况例外:当HashMap容量很大,实际数据较少时,遍历起来可能会比LinkedHashMap慢。因为LinkedHashMap的遍历速度只和实际数据有关,和容量无关,而HashMap的遍历速度和他的容量有关。

TreeMap:

1. TreeMap实现SortMap接口,能够把它保存的记录根据键排序。
2. 默认是按键值的升序排序,也可以指定排序的比较器,当用Iterator 遍历TreeMap时,得到的记录是排过序的。

使用情况:
1、用的最多的是HashMap。HashMap里面存入的键值对在取出的时候是随机的,它根据键的HashCode值存储数据,根据键可以直接获取它的值,具有很快的访问速度。在Map中插入、删除和定位元素,HashMap 是最好的选择。
  2、TreeMap取出来的是排序后的键值对。但如果您要按自然顺序或自定义顺序遍历键,那么TreeMap会更好。
  3、LinkedHashMap是HashMap的一个子类,如果需要输出的顺序和输入的相同,那么用LinkedHashMap可以实现,它还可以按读取顺序来排列,像连接池中可以应用。

二、快速失败(fail-fast):

是java集合中的一种机制,在用迭代器遍历一个集合对象时,如果遍历过程中其他线程对集合对象的内容进行了修改(增加、删除、修改,改变了modCount的值),则会抛出Concurrent Modification Exception。
java.util包下的集合类都是快速失败的,不能在多线程下发生并发修改(迭代过程中被修改)。
原理:
1、迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。
2、集合在被遍历期间如果内容发生变化,就会改变modCount的值。
3、每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历。

三、安全失败(fail-safe):

迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发ConcurrentModificationException。
因此缺点就是,迭代器并不能访问到修改后的内容,即:迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在遍历期间原集合发生的修改迭代器是不知道的。
java.util.concurent下都是安全失败。

四、ABA问题:

来了一个线程把值A改成了B,又来了一个线程把值又改回了A,对于这个时候判断的线程,就发现他的值还是A,所以他就不知道这个值到底有没有被人改过,其实很多场景如果只追求最后结果正确,这是没关系的。

五、CAS:

是乐观锁一种实现方式,每次修改的时候带上查询出来的值做比较。适用于并发量不高的情况。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值