java容器

目录

List,Set,Map的区别和联系

一、List接口

二、Set接口

三、Map接口

四、总结

ArrayList和LinkedList的区别是什么?

Array和ArrayList的区别

List和Set的区别

遍历一个List集合有哪些方式

ArrayList的优缺点

数组和List之间的转换

说一下ArrayList的扩容机制

HashSet、TreeSet的区别

hashMap专栏

HashMap 存储键值对实现快速存取,键值允许为 null。

非同步,线程不安全。

底层是 hash 表,不保证有序(比如插入的顺序),

jdk8 后采用数组+链表+红黑树的数据结构。

扩容因子是0.75,为什么?

hashMap 中 put() 是如何实现的?

HashMap中get()是如何实现的?

HashMap的Key可以是任何类型,必须得重写了equals()、hashCode() 方法,且key的内容不可改变。


List,Set,Map的区别和联系

一、List接口

List是一个继承于Collection的接口,即List是集合中的一种。List是有序的队列,List中的每一个元素都有一个索引;第一个元素的索引值是0,往后的元素的索引值依次+1。和Set不同,List中允许有重复的元素。实现List接口的集合主要有:ArrayList、LinkedList、Vector、Stack。

ArrayList

ArrayList是一个动态数组,也是我们最常用的集合。每一个ArrayList都有一个初始容量:0

第一次添加元素会扩容到:private static final int DEFAULT_CAPACITY = 10;

随着容器中的元素不断增加,容器的大小也会随着增加。在每次向容器中增加元素的同时都会进行容量检查,当溢出时,就会进行扩容操作。所以如果我们明确所插入元素的多少,最好指定一个初始容量值,避免过多的进行扩容操作而浪费时间、效率。

size、isEmpty、get、set、iterator 和 listIterator 操作都以固定时间运行。add 操作以分摊的固定时间运行,也就是说,添加 n 个元素需要 O(n) 时间(由于要考虑到扩容,所以这不只是添加元素会带来分摊固定时间开销那样简单)。

ArrayList擅长于随机访问。ArrayList是非同步的。

ArrayList基于数组,需要连续内存根据数组下标访问,查询快,实现了随机访问接口 RandomAccess

如果是添加删除尾部元素,效率很快,操作其他地方的元素就会移动元素索引位置,所以效率慢。

ArayList如果调用的是无参构造的话,初始容量是0,然后add一个元素,就会扩容到10

如果调用的是有参构造,传了一个整数,那么初始容量就是你传的那个整数

如果add方法添加了10个元素,要继续add添加元素的话,就会扩容到原来的1.5倍,变成15。

addAll方法有所不同,扩容后的大小是原来扩容方式的大小和添加元素后的元素总个数大小,两者取一个最大值去扩容。比如说,list中本来有10个元素,addAll方法添加6个元素进去。这时候按原来的方式扩容会扩容成15,添加后的元素有10+6=16个,所以会直接扩容到16,只扩容一次就可以了,相比之前的提高了效率。

ArrayList可以利用cpu缓存原理,提高效率。ArrayList加载到内存中,然后cpu要去内存中去访问,直接访问太慢了,所以会有一个cpu缓存,把要访问的元素以及该元素相邻的元素都加载到cpu缓存中去。这样就可以提高效率,因为如果要访问他相邻的元素时候就不需要重新在内存中取了。

LinkedList

同样实现List接口的LinkedList与ArrayList不同,ArrayList是一个动态数组,而LinkedList是一个双向链表。所以它除了有ArrayList的基本操作方法外还额外提供了get,remove,insert方法在LinkedList的首部或尾部。

由于实现的方式不同,LinkedList不能随机访问,它所有的操作都是要按照双重链表的需要执行。在列表中索引的操作将从开头或结尾遍历列表(从靠近指定索引的一端,节约一半时间)。这样做的好处就是可以通过较低的代价在List中进行插入和删除操作。

与ArrayList一样,LinkedList也是非同步的。如果多个线程同时访问一个List,则必须自己实现访问同步。一种解决方法是在创建List时构造一个同步的List:

List list = Collections.synchronizedList(new LinkedList(…));

LinkedList占用内存比ArrayList要大。

Vector

与ArrayList相似,但是Vector是同步的。所以说Vector是线程安全的动态数组。它的操作与ArrayList几乎一样。

二、Set接口

Set是一个继承于Collection的接口,Set是一种不包括重复元素的Collection。它维持它自己的内部排序,所以随机访问没有任何意义。与List一样,它同样运行null的存在但是仅有一个。由于Set接口的特殊性,所有传入Set集合中的元素都必须不同,关于API方面。Set的API和Collection完全一样。实现了Set接口的集合有:HashSet、TreeSet、LinkedHashSet、EnumSet。

HashSet

HashSet堪称查询速度最快的集合,因为其内部是以HashCode来实现的。集合元素可以是null,但只能放入一个null。它内部元素的顺序是由哈希码来决定的,所以它不保证set的迭代顺序;特别是它不保证该顺序恒久不变。

TreeSet

TreeSet是二叉树实现的,基于TreeMap,生成一个总是处于排序状态的set,内部以TreeMap来实现,不允许放入null值。它是使用元素的自然顺序对元素进行排序,或者根据创建Set时提供的 Comparator 进行排序,具体取决于使用的构造方法。

LinkedHashSet

LinkedHashSet集合同样是根据元素的hashCode值来决定元素的存储位置,但是它同时使用链表维护元素的次序。这样使得元素看起 来像是以插入顺序保存的,也就是说,当遍历该集合时候,LinkedHashSet将会以元素的添加顺序访问集合的元素。LinkedHashSet在迭代访问Set中的全部元素时,性能比HashSet好,但是插入时性能稍微逊色于HashSet。

三、Map接口

Map与List、Set接口不同,它是由一系列键值对组成的集合,提供了key到Value的映射。在Map中它保证了key与value之间的一一对应关系。也就是说一个key对应一个value,所以它不能存在相同的key值,当然value值可以相同。实现map的集合有:HashMap、HashTable、TreeMap、WeakHashMap。

HashMap

以哈希表数据结构实现,查找对象时通过哈希函数计算其位置,它是为快速查询而设计的,其内部定义了一个hash表数组(Entry[] table),元素会通过哈希转换函数将元素的哈希地址转换成数组中存放的索引,如果有冲突,则使用散列链表的形式将所有相同哈希地址的元素串起来,可能通过查看HashMap.Entry的源码它是一个单链表结构。

HashTable

也是以哈希表数据结构实现的,解决冲突时与HashMap也一样也是采用了散列链表的形式。HashTable继承Dictionary类,实现Map接口。其中Dictionary类是任何可将键映射到相应值的类(如 Hashtable)的抽象父类。每个键和每个值都是一个对象。在任何一个 Dictionary 对象中,每个键至多与一个值相关联。Map是”key-value键值对”接口。 HashTable采用”拉链法”实现哈希表不过性能比HashMap要低。

TreeMap

有序散列表,实现SortedMap接口,底层通过红黑树实现。

WeakHashMap

谈WeakHashMap前先看一下Java中的引用(强度依次递减)

  1. 强引用:普遍对象声明的引用,存在便不会GC
  2. 软引用:有用但并非必须,发生内存溢出前,二次回收
  3. 弱引用:只能生存到下次GC之前,无论是否内存足够
  4. 虚引用:唯一目的是在这个对象被GC时能收到一个系统通知

以弱键实现的基于哈希表的Map。在 WeakHashMap 中,当某个键不再正常使用时,将自动移除其条目。更精确地说,对于一个给定的键,其映射的存在并不阻止垃圾回收器对该键的丢弃,这就使该键成为可终止的,被终止,然后被回收。丢弃某个键时,其条目从映射中有效地移除,因此,该类的行为与其他的 Map 实现有所不同。null值和null键都被支持。该类具有与HashMap类相似的性能特征,并具有相同的效能参数初始容量和加载因子。像大多数集合类一样,该类是不同步的。

四、总结

1、List、Set都是继承自Collection接口,Map则不是

2、List特点:元素有放入顺序,元素可重复 ,Set特点:元素无放入顺序,元素不可重复,重复元素会覆盖掉,(注意:元素虽然无放入顺序,但是元素在set中的位置是有该元素的HashCode决定的,其位置其实是固定的,加入Set 的Object必须定义equals()方法 ,另外list支持for循环,也就是通过下标来遍历,也可以用迭代器,但是set只能用迭代,因为他无序,无法用下标来取得想要的值。) 

3、Set和List对比: 

Set:检索元素效率低下,删除和插入效率高,插入和删除不会引起元素位置改变。 

List:和数组类似,List可以动态增长,查找元素效率高,插入删除元素效率低,因为会引起其他元素位置改变。 但是如果从最后面插入元素的话,效率很快。

4、Map适合储存键值对的数据

5、线程安全集合类与非线程安全集合类 :

  • LinkedList、ArrayList、HashSet是非线程安全的,Vector是线程安全的;
  • HashMap是非线程安全的,HashTable是线程安全的;
  • StringBuilder是非线程安全的,StringBuffer是线程安全的。

ArrayList和LinkedList的区别是什么?

1,数据结构实现上:ArrayList底层是动态数组实现,LinkedList底层是双向链表数据结构实现

2,随机访问效率:ArrayList实现了RandomAccess接口,所以在随机访问(查询)的时候效率较高,LinkedList是线性的数据存储方式,所以需要移动指针从前往后依次查找,效率较低。

3,增加和删除效率:在非首尾的增加和删除操作,LinkedList要比ArrayList效率要高

4,内存空间占用:LinkedList要比ArrayList更占内存,LinkedList还存储两个引用,一个指向前一个元素,一个指向后一个元素

5,数据量大,修改操作频繁用LinkedList,数据量大,查询操作频繁用ArrayList。

 

Array和ArrayList的区别

1,存储内容比较:Array可以包含基本类型和对象类型,ArrayList却只能包含对象类型,Array数组在存放的时候一定是同种类型的元素,ArrayList就不一定了

2,空间大小比较:Array数组的空间大小是固定了,所以需要提前确定合适的空间大小,ArrayList的空间是动态增长的,而且每次添加新的元素的时候都会检查内部数组的空间是否足够

3,方法上的比较:ArrayList方法比Array更多样化,支持迭代器操作

 

List和Set的区别

List、Set都是继承Collection接口 

List特点:一个有序(元素存入集合的顺序和取出的顺序一致)容器,元素可以重复,可以插入多个null元素,元素都有索引
Set特点:一个无序(存入和取出顺序有可能不一致)容器,不可以存储重复元素,只允许存入一个null元素,必须保证元素唯一性。
List因为是有序的,所以List支持for循环,也就是通过下标来遍历,也可以用迭代器,但是Set只能用迭代,因为他无序,无法用下标来取得想要的值。
 

遍历一个List集合有哪些方式

1.for循环

2.迭代器Iterator遍历

3.foreach循环遍历

当我们遍历时使用哪种依照具体情景来讨论:

如果一个数据集合实现了Random Access接口,就意味着它支持随机访问,使用for循环会比其他两种更快
如果一个数据集合没有实现Random Access接口,那么使用Iterator或者foreach会更快
 

ArrayList的优缺点

ArrayList优点:

ArrayList底层以数组实现,支持随机访问(因为ArrayList实现了RandomAccess接口)
ArrayList在顺序添加一个元素的时候非常方便


ArrayList的缺点:

在删除或者插入元素的时候,需要做一次复制操作,如果操作的元素很多的话,会比较耗费性能

 

数组和List之间的转换

数组转List:使用 Arrays.asList(array) 进行转换
List转换数组:使用List自带的 toArray() 方法
 

说一下ArrayList的扩容机制

1、ArrayList的默认大小是0,默认初始容量是10;

2、未指定集合容量,默认是0,如果已经指定大小则集合大小为指定大小。

3、当集合new出来时,容量为0,当第一次添加元素时,集合扩容为10

4、当集合元素为10,当再次添加元素时,扩容为原来的1.5倍。

 

HashSet、TreeSet的区别

HashSet

底层实现的就是HashMap,所以是根据HashCode来判断是否是重复元素。如果HashCode不存在,则直接插入,如果HashCode已存在,就调用对象的equals()方法,如果返回false则插入,如果返回true说明是重复元素,就不插入。
因为TreeSet要是额外使用红黑树来保证元素的有序性,所以性能相对来说是HashSet的性能是要比TreeSet的性能要好。
初始化容量是:16, 这是因为底层实现的是HashMap。加载因子是0.75。
HashSet是无序的,也就是说插入的顺序和取出的顺序是不一样的。
因为HashSet不能根据索引去数据,所以不能用普通的for循环来取出数据,应该用增强for循环。这也进一步说明了HashSet的查询性能肯定是没有ArrayList的性能高的。


TreeSet

底层是实现的TreeMap。
元素不能够重复,可以有一个null值,并且这个null值一直在第一个位置上。
默认容量:16,加载因子是0.75
TreeMap是有序的,这个有序不是存入的和取出的顺序是一样的,而是根据自然规律拍的序。
 

 

hashMap专栏

HashMap 存储键值对实现快速存取,键值允许为 null。

key 值不可重复,若 key 值重复则覆盖。键值不允许为基本数据类型。在Java 中是使用泛型来约束 HashMap 中的 key 和 value 的类型的, HashMap<K, V>, 泛型在 Java 的规定中必须是对象 Object 类型的,基本数据类型不是 Object 类型, 不能作为键值。

非同步,线程不安全。

HashTable线程安全,所有的读写等操作都进行了同步锁(synchronized)保护,但是线程1使用put进行元素添加,线程2不但不能使用put方法添加元素,也不能使用get方法来获取元素,效率太低,所以使用较少。而ConcurrentHashMap:是一种高效但是线程安全的集合,ConcurrentHashMap在JDK 1.8后采用了Node + CAS + Synchronized来保证并发安全进行实现,对每个数组元素加锁(Node),当一个线程操作一个数组时,只有那一个数组被锁住,其他数组不受影。

底层是 hash 表,不保证有序(比如插入的顺序),

要进行增删查操作,就使用HashMap,要进行排序就使用TreeMap。

jdk8 后采用数组+链表+红黑树的数据结构。

当数组长度大于64并且链表的节点大于 8 个时就会将链表变为红黑树,为什么是8呢,因为当链表长度很小的时候,即使遍历,速度也非常快,但是当链表长度不断变长,肯定会对查询性能有一定的影响,所以才需要转成树。至于为什么阈值是8,源码中只是写了是8,但并没有解释为什么是8,HashMap给定的默认容量为 16,负载因子为 0.75。Map 在使用过程中不断的往里面存放数据,当数量达到了 16 * 0.75 = 12 ,开始第一次存储第 13 个键值对时就需要将当前的容量进行扩容。长度和最大容量都扩容为原来的2倍。底层一切的扩容设计,都是为了增加了随机性, 减少了哈希碰撞的次数。

红黑树主要是为了避免DOS攻击,防止有人恶意用大量的相同hash值的特殊字符来使链表长度过长而影响到性能,链表转化为红黑树应该是偶然现象,我们平时是几乎不会遇到这种情况的。

扩容因子是0.75,为什么?

大于这个值,链表长度就会比较长影响性能

小于这个值,扩容就会更加频繁。

0.75是比较均衡的一个值。

hashMap 中 put() 是如何实现的?

1,计算关于 key 的 hashcode 值(结果除以16取余),然后进行二次哈希,得到最终哈希值,目的是为了让哈希值足够随机,插入时足够均匀。

2,如果散列表为空时, 调用 resize() 初始化 散列表;

3,如果没有发生碰撞, 直接添加元素到散列表中去;

4,如果发生了碰撞(hashCode 值相同), 进行三种判断;

若 key 地址相同或者 equals 后内容相同, 说明是同样的数据,则替换旧值 。
如果是红黑树结构, 就调用树的插入方法 。
链表结构,循环遍历知道链表中的某个节点为空,尾插法进行插入。插入之后判断链表的个数是否达到红黑树的阈值8,也可以遍历到有节点与插入元素相同的内容进行覆盖。
5,如果超过了阀值,则resize进行扩容。

HashMap中get()是如何实现的?

对 key 的 hashCode 值进行 计算,计算得到在数组中的位置,先判断第一个数据的key是否与参数key相等,不等就遍历后面的链表找到相同的key值返回对应的Value值即可。

HashMap的Key可以是任何类型,必须得重写了equals()、hashCode() 方法,且key的内容不可改变。

比如说用一个自定义对象当作key,然后修改了对象得属性,然后再调用map.get(key)方法去拿value,这样就找不到了。key最好是Intager,String这种类型,因为都是final类型,即不可变性,保证key的不可更改性,不会存在获取hash值不同的情况;内部已重写了equals()、hashCode() 等方法,遵守了HashMap内部的规范,不容易出现Hash值计算错误的情况;

如果使用Object类型做Key,则需要重写hashCode()是因为需要计算存储数据的存储位置,需要注意不要试图从散列码计算中排除掉一个对象的关键部分来提高性能,这样虽然能更快但可能会导致更多的Hash碰撞;还需要重写equals()方法,需要遵守自反性、对称性、传递性、一致性以及对于任何非null的引用值xx.equals(null)必须返回false的这几个特性,目的是为了保证key在哈希表中的唯一性。
 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值