java集合

java集合

list有序可重复

set无序不可重复

map键值对

List

List是一个有序队列,在JAVA中有两种实现方式:
ArrayList 使用数组实现,是容量可变的非线程安全列表,随机访问快,集合扩容时会创建更大的数组,把原有数组复制到新数组。
LinkedList 本质是双向链表,与 ArrayList 相比插入和删除速度更快,但随机访问元素很慢。

ArrayList与LinkedList:

  • 都不保证线程安全
  • arraylist底层Object数组;likedlist底层为双向链表(1.6之前为循环链表,1.7取消了循环)
  • arraylist用数组存储,插入和删除元素的时间复杂度受元素位置的影响。linkedlist用链表存储,对于插入和删除元素不敏感,
  • arraylist支持高效的随机元素访问,即通过元素的序号快速获取元素对象(get(index))
  • ArrayList 的空 间浪费主要体现在在 list 列表的结尾会预留⼀定的容量空 间,⽽ LinkedList 的空间花费则体现在它的每⼀个元素都需要消耗⽐ ArrayList 更多的空间 (因为要存放直接后继和直接前驱以及数据)。

双向链表:包含两个指针,一个prev指向前一个节点,一个next指向后一个节点

双向循环链表:最后一个节点的next指向head,head的prev指向最后一个节点,构成一个环

ArrayList、Vector和LinkedList有什么共同点与区别?

ArrayList初始化时,初始大小10,当插入新元素的时候,会判断是否需要扩容,扩容的步长是0.5倍原容量。扩容方式是利用数组的复制。因此有一定的开销。

ArrayList进行元素的插入的时候,需要移动插入位置之后的所有元素,位置越靠前,需要位移的元素越多,开销越大,相反,插入位置越靠后,开销就越小。如果在最后面进行插入,就不需要位移。

**LinkedList:**内部使用双向链表的结构实现存储。其有一个内部类作为存放元素的单元,里面有三个属性,用来存放元素本身以及前后2个单元的引用。LinkedList内部有一个Header属性,用来标识起始位置,linkedlist的第一个单元和最后一个单元都会指向leader,因此形成了一个双向的链表结构。

ArrayList、Vector和LinkedList都是可伸缩的数组,即可以动态改变长度的数组。
ArrayList和Vector都是基于存储元素的Object[] array来实现的,它们会在内存中开辟一块连续的空间来存储,支持下标、索引访问。但在涉及插入元素时可能需要移动容器中的元素,插入效率较低。当存储元素超过容器的初始化容量大小,ArrayList与Vector均会进行扩容。
Vector是线程安全的,其大部分方法是直接或间接同步的。ArrayList不是线程安全的,其方法不具有同步性质。LinkedList也不是线程安全的。
LinkedList采用双向列表实现,对数据索引需要从头开始遍历,因此随机访问效率较低,但在插入元素的时候不需要对数据进行移动,插入效率较高。

vector是list的古老实现类,底层使用object数组,线程安全。

arraylist扩容

ArrayList是基于数组实现的,是一个动态数组,其容量能自动增长。
ArrayList不是线程安全的,只能用在单线程环境下。
实现了Serializable接口,因此它支持序列化,能够通过序列化传输;
实现了RandomAccess接口,支持快速随机访问,实际上就是通过下标序号进行快速访问;
实现了Cloneable接口,能被克隆。

每次扩容都是通过Arrays.copyOf(elementData, newCapacity)这样的方式实现的。

ArrayList动态扩容的全过程。如果通过无参构造的话,初始数组容量为0,当真正对数组进行添加时,才真正分配容量。每次按照1.5倍(位运算)的比率通过copeOf的方式扩容。 在JKD1.6中实现是,如果通过无参构造的话,初始数组容量为10,每次通过copeOf的方式扩容后容量为原来的1.5倍

Set

Set 即集合,该数据结构不允许元素重复且无序。JAVA对Set有三种实现方式:
HashSet 通过 HashMap 实现,HashMap 的 Key 即 HashSet 存储的元素,Value系统自定义一个名为 PRESENT 的 Object 类型常量。判断元素是否相同时,先比较hashCode,相同后再利用equals比较,查询O(1)
LinkedHashSet 继承自 HashSet,通过 LinkedHashMap 实现,使用双向链表维护元素插入顺序。
TreeSet 通过 TreeMap 实现的,底层数据结构是红黑树,添加元素到集合时按照比较规则将其插入合适的位置,保证插入后的集合仍然有序。查询O(logn)

hashset如何检查重复

当你把对象加⼊ HashSet 时, HashSet 会先计算对象的hashcode 值来判断对象加⼊的位置,同 时也会与其他加⼊的对象的 hashcode 值作比较,如果没有相符的 hashcode , HashSet 会假设 对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调⽤ equals() ⽅法来检查 hashcode 相等的对象是否真的相同。如果两者相同, HashSet 就不会让加⼊操作成功。

hashcode()和equals()的相关规定:

  1. 如果两个对象相等,则hashcode一点相同
  2. 两个对象相等,对两个equals()返回true
  3. 两个对象有相同的hashcode值,他们也不一定是相等的。
  4. equals()方法被覆盖,则hashcode()也必须被覆盖
  5. hashcode()的默认行为是对堆上的对象产生独特值,如果没有重写hashcode,则该class的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)

==与equals:

1.对于基本类型来说,== ⽐᫾的是值是否相等;

2.对于引⽤类型来说,== ⽐᫾的是两个引⽤是否指向同⼀个对象地址(两者在内存中存放的地址 (堆内存地址)是否指向同⼀个地⽅);

  1. 对于引⽤类型(包括包装类型)来说,equals 如果没有被重写,对⽐它们的地址是否相等;如果 equals()⽅法被重写(例如 String),则⽐᫾的是地址⾥的内容

hashset中,equals和hashcode之间的关系?

equals和hashCode这两个方法都是从object类中继承过来的,equals主要用于判断对象的内存地址引用是否是同一个地址;hashCode根据定义的哈希规则将对象的内存地址转换为一个哈希码。HashSet中存储的元素是不能重复的,主要通过hashCode与equals两个方法来判断存储的对象是否相同:

  1. 如果两个对象的hashCode值不同,说明两个对象不相同。
  2. 如果两个对象的hashCode值相同,接着会调用对象的equals方法,如果equlas方法的返回结果为true,那么说明两个对象相同,否则不相同。

hashmap

hashmap数据结构:

1.7使用数组+链表

1.8及之后使用:数组+链表+红黑树,当阈值默认是0.75,链表的深度大于等于8,数组容量大于等于64时,扩容的时候会把链表转为红黑树。时间复杂度从n变为o(logn);当红黑树的节点深度小于或者等于6时候,红黑树退化成链表结构:

简述hashmap工作原理:

hashmap通过get和put方法存储和获取

put()存储对象:

1.调用哈希方法计算Key的hash值(其中jdk1.7利用了9次扰动处理=4次位运算+5次异或;jdk1.8只用了2次扰动处理=1次位运算+1次异或),然后和(数组长度-1)做异或运算,得出数组下标;

2.当table中的元素个数大于阈值(capacity*loadfactor)时,进行扩容,将table数组的大小扩充为2倍;

3.如果key的hash值对应的table下标元素为空,说明还没有元素,则直接插入,若不为空,则说明存在元素,发生了hash冲突,接下来要遍历链表或红黑树进行查找。如果遍历的过程中,==或equals返回true,说明找到了对应的对象,直接更新该对象的value,返回旧value;如果遍历结束,==或者equals还是返回false,说明没有找到对应的对象,则需要在该链表或者红黑树中插入(1.7使用头插,1.8使用尾插)

get()方法获取对象:

1.和put一样,通过hash(key.hashCode())方法,然后和(数组长度-1)做异或运算,得出数组下标。

2.顺序遍历链表或红黑树,==或equals返回true,则返回对应的value值,否则返回null;

如何解决hash冲突

开放定址法、再哈希法、链地址法、建立公共溢出区

hashmap种处理hash冲突的方法就是链地址法:思想就是将所有哈希地址为i的元素构成一个称为同义词链的单链表,并将单链表的头指针存在哈希表的第i个单元种,因而查找、插入和删除主要在同义词链中进行,链地址法适用于经常插入和删除的情况。

为什么hashmap中的table数组用transient修饰?

put和remove过程中,红黑树要通过左旋、右旋、变色操作来保持平衡,并且构造红黑树比构造链表复杂。
HashMap频繁的扩容,会造成底部红黑树不断的进行拆分和重组,这是非常耗时的。因此,也就是链表长度比较长的时候转变成红黑树才会显著提高效率。

hashmap和hashtable

  • hashmap非线程安全,hashtable线程安全,因为hashtable内部的方法都经过synchronized修饰(如果想要保证线程安全就使用concurrenthashmap)
  • hashmap比hashtable效率高
  • hashmap可以存储为Null的key和value,但是null作为键只能有一个,null作为值可以有多个;hashtable不允许有null键和null值,否则抛出nullpointerException
  • 创建时如果不指定容量,hashtable默认的初始大小为11,每次扩容,容量变为原来的2n+1;hashmap默认的初始化大小为16,之后每次扩容,容量变为原来的 2倍; 创建时如果给定了容量初始值,那么 Hashtable 会直接使⽤你给定的⼤⼩,⽽ HashMap 会将其扩充为 2 的幂次⽅⼤⼩ ( HashMap 中的 tableSizeFor()⽅法保证,下⾯给出了源代码)。也就是说 HashMap 总 是使⽤ 2 的幂作为哈希表的⼤⼩,后⾯会介绍到为什么是 2 的幂次⽅
  • jdk1.8以后的hashmap在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8),(将链表转换成红黑树前会判断,如果当前数组的长度小于64,那么会选择先对数组进行扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间,hashtable没有这样的机制。
红黑树

由于二叉查找树BST存在数据倾斜的问题(极端情况下会形成一个链表),所以平衡二叉树产生了,平衡树在插入和删除的时候,通过旋转将高度保持在logn。平衡性分别为AVL树和红黑树,AVL树由于实现比较复杂,而且插入和删除性能差,在实际环境中我们更多的是应用红黑树。

红黑树的规则:

① 每个节点都是红色或者黑色;
② 根节点必须始终是黑色;
③ 没有两个相邻的红色节点;
④对每个结点,从该结点到其子孙节点的所有路径上包含相同数目的黑结点。

AVL树和红黑树的区别:
AVL更加严格平衡,可以提供更快的查找速度,读取查找密集型任务,适用AVL树;红黑树更适合插入修改密集型任务;AVL的旋转比红黑树的旋转更加难以平衡和调试。

在 Java 中,被transient关键字修饰的变量不会被默认的序列化机制序列化

hashmap并没有使用默认的序列化机制,而是通过实现readObject和writeObject两个方法自定义了序列化的内容。

为什么链表长度大于8才转变成红黑树?

JDK8 之前底层实现是数组 + 链表,JDK8 改为数组 + 链表/红黑树。主要成员变量包括存储数据的 table 数组、元素数量 size、加载因子 loadFactor。 HashMap 中数据以键值对的形式存在,键对应的 hash 值用来计算数组下标,如果两个元素 key 的 hash 值一样,就会发生哈希冲突,被放到同一个链表上。
table 数组记录 HashMap 的数据,每个下标对应一条链表,所有哈希冲突的数据都会被存放到同一条链表,Node/Entry 节点包含四个成员变量:key、value、next 指针和 hash 值。在JDK8后链表超过8会转化为红黑树。
若当前数据/总数据容量>负载因子,Hashmap将执行扩容操作。 默认初始化容量为 16,扩容容量必须是 2 的幂次方、最大容量为 1<< 30 、默认加载因子为 0.75。

为什么使用链表+数组?
因为数组的值是限制好的,对key值进行散列取到下标以后,放入数组中时,难免出现两个key值不同,但是却放到下标相同的格子中。此时可以使用链表来对其进行链式存放。

用LinkedList代表数组结构可以吗?
可以。

为什么使用数组?
因为使用数组的效率高:在hashmap中,定位节点的位置是利用元素的key的哈希值对数组长度取模得到。此时,我们已经得到节点的位置,数组的查找效率比LinkedList大(底层是链表结构)

ArrayList底层也是数组,为什么不用ArrayList呢?
因为采用基本数组结构,扩容机制可以自己定义,hashmap中数组扩容刚好是2的次幂,再做取模运算的效率高,而ArrayList的扩容机制是1.5倍扩容。

hash冲突:得到下标值。

为什么hashmap线程不安全

为何HashMap线程不安全
在JDK1.7中,HashMap采用头插法插入元素,因此并发情况下会导致环形链表,产生死循环。
虽然JDK1.8采用了尾插法解决了这个问题,但是并发下的put操作也会使前一个key被后一个key覆盖。
由于HashMap有扩容机制存在,也存在A线程进行扩容后,B线程执行get方法出现失误的情况。

hashmap和hashtable

1.HashMap是Hashtable的轻量级实现,HashMap允许key和value为null,但最多允许一条记录的key为null.而HashTable不允许。
2.HashTable中的方法是线程安全的,而HashMap不是。在多线程访问HashMap需要提供额外的同步机制。
3.Hashtable使用Enumeration进行遍历,HashMap使用Iterator进行遍历。

concurrenthashmap和hashtable的区别

ConcurrentHashMap 和 Hashtable 的区别主要体现在实现线程安全的⽅式上不同。

  • 底层数据结构: JDK1.7 的 ConcurrentHashMap 底层采⽤ 分段的数组+链表 实现,JDK1.8 采⽤的数据结构跟 HashMap1.8 的结构⼀样,数组+链表/红⿊⼆叉树。 Hashtable 和 JDK1.8 之前的 HashMap 的底层数据结构类似都是采⽤ 数组+链表 的形式,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突⽽存在的;
  • 实现线程安全的⽅式(重要): ① 在 JDK1.7 的时候,` ConcurrentHashMap (分段锁) 对整个桶数组进⾏了分割分段( Segment ),每⼀把锁只锁容器其中⼀部分数据,多线程访问 容器⾥不同数据段的数据,就不会存在锁竞争,提⾼并发访问率。 到了 JDK1.8 的时候已经 摒弃了 Segment 的概念,⽽是直接⽤ Node 数组+链表+红⿊树的数据结构来实现,并发 控制使⽤ synchronized 和 CAS 来操作。(JDK1.6 以后 对 synchronized 锁做了很多优 化) 整个看起来就像是优化过且线程安全的 HashMap ,虽然在 JDK1.8 中还能看到 Segment 的数据结构,但是已经简化了属性,只是为了兼容旧版本;② **Hashtable (同⼀把 锁) :使⽤ synchronized 来保证线程安全,效率⾮常低下。**当⼀个线程访问同步⽅法时,其 他线程也访问同步⽅法,可能会进⼊阻塞或轮询状态,如使⽤ put 添加元素,另⼀个线程不 能使⽤ put 添加元素,也不能使⽤ get,竞争会越来越激烈效率越低。

TreeMap

TreeMap是底层利用红黑树实现的Map结构,底层实现是一棵平衡的排序二叉树,由于红黑树的插入、删除、遍历时间复杂度都为O(logN),所以性能上低于哈希表。但是哈希表无法提供键值对的有序输出,红黑树可以按照键的值的大小有序输出。

如何决定使用hashmap还是treemap?

如果对Map进行插入、删除或定位一个元素的操作更频繁,HashMap是更好的选择。如果需要对key集合进行有序的遍历,TreeMap是更好的选择。

concurrenthashmap

减小锁粒度(缩小锁定对象的范围,从而减小锁冲突的可能性,从而提高系统的并发能力)

其内部分为若干小的hashmap,称之为段segment,默认情况下,被细分为16个段。即锁的并发度就是16.

如果需要添加一个新的表项,并不是将整个hashmap加锁,而是首先根据hashcode得到该表项应该存放在哪个段中,然后对该段加锁,并完成put操作。在多线程环境下,如果多个线程同时进行put操作,只要被加入的表项不存在同一个段中,则线程间就做到了真正的并行。

ConcurrentHashmap是由Segment数组结构和HashEntry数组结构构成。segment是一种可重入锁reentlock,扮演锁的角色。hashentry用于存储键值对数据。segement的结构和hashmap类似,是一种数据+链表的结构。每个hashentry是一个链表结构的元素。每个segment守护一个hashentry数组里的元素。每当对hashentry中的数据进行修改,必须首先获得它对应的segmeny锁。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值