常用的集合类有哪些?
Map接口和Collection接口是所有集合框架的父接口:
Collection接口的子接口包括:Set接口和List接口
Map接口的实现类主要有:HashMap、TreeMap、Hashtable、ConcurrentHashMap以及Properties等
Set接口的实现类主要有:HashSet、TreeSet、LinkedHashSet等
List接口的实现类主要有:ArrayList、LinkedList、Stack以及Vector等
集合和数组的区别
-
数组是固定长度的;集合可变长度的。
-
数组可以存储基本数据类型,也可以存储引用数据类型;集合只能存储引用数据类型。
-
数组存储的元素必须是同一个数据类型;集合存储的对象可以是不同数据类型。
为什么要使用集合?
存储数据的时候, 数据多时变量就不方便了, 不是一个明智的选择,那么数组呢?数组也不是个好的选择,因为数组在创建的时候需要指定长度,在使用的过程中长度不变, 所有我们要寻求一个拥有可变长度的容器来存储这些数据。
还好Java提供了各种各样的容器,每个容器的长度都是可变的,我们只管往里面存,空间如果不够,它自己会调整。
所以有再多的数据我们也不怕,也不用看着下标去存,容器也提供了大量的对应方法,当看到提供的方法的时候就知道是什么操作了....比如:add,remove,size,isEmpty.....
为什么要使用集合?
答:因为方便和高效
哪些集合类是线程安全的?
vector:就比arraylist多了个同步化机制(线程安全),因为效率较低,现在已经不太建议使用。在web应用中,特别是前台页面,往往效率(页面响应速度)是优先考虑的。
statck:堆栈类,先进后出。
hashtable:就比hashmap多了个线程安全。
enumeration:枚举,相当于迭代器。
Java集合的快速失败机制 “fail-fast”?
是java集合的一种错误检测机制,当多个线程对集合进行结构上的改变的操作时,有可能会产生 fail-fast 机制。
例如:假设存在两个线程(线程1、线程2),线程1通过Iterator在遍历集合A中的元素,在某个时候线程2修改了集合A的结构(是结构上面的修改,而不是简单的修改集合元素的内容),那么这个时候程序就会抛出 ConcurrentModificationException 异常,从而产生fail-fast机制。
原因:迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历。
解决办法:
在遍历过程中,所有涉及到改变modCount值得地方全部加上synchronized。
使用CopyOnWriteArrayList来替换ArrayList
怎么确保一个集合不能被修改?
可以使用 Collections. unmodifiableCollection(Collection c) 方法来创建一个只读集合,这样改变集合的任何操作都会抛出 Java. lang. UnsupportedOperationException 异常。
示例代码如下:
List<String> list = new ArrayList<>();
list. add("x");
Collection<String> clist = Collections. unmodifiableCollection(list);
clist. add("y"); // 运行时此行报错
System. out. println(list. size());
---------------------------------------------------------------------------------------------------------------------------------
集合框架中两大体系 Collection和 Map
Collection 集合体系
Map 键值对 集合体系
---------------------------------------------------------------------------------------------------------------------------------
Collection做为集合框架的顶层设计,它里面抽象出了17个公共方法;
这些方法分三类:1.修改操作; 2.查询操作; 3.批量操作;
add(E e) | 向集合中添加单个数据,成功返回true,失败返回false |
addAll(Collection<? extends E> c) | 向集合中添加批量数据,成功返回true,失败返回false |
所添加的数据类型跟定义的数据类型保持一致(常见的是一个对象,或者子对象,除此以外其他类型都无法存入)
批量数据一般来自另一个集合,平时开发的时候用的比较少
---------------------------------------------------------------------------------------------------------------------------------
集合删除数据的方法有几种?
一共有四种:
1.删除单个数据remove方法
2.删除批量数据removeAll方法
3.删除符合条件的数据 removeIf方法(jdk1.8)
4.删除所有数据clear方法直接清空集合
remove:数据是通过迭代器来删除的,它根据要删除的数据是否为null,分为两种情况,Remove方法每调用一次,只删除一个匹配项;当要删除的数据不为null的时候,匹配删除对应的项;
--------------------------------------------------------------------------------------------------------------------------------
removeAll:删除指定数组的数据
retainAll: 指定要保留的数据的集合,跟removeAll是相反的原理
removeIf: 遍历集合,依次检查迭代器的返回数据,以查看它是否满足规则,满足规则用remove方法删除;
clear:遍历集合依次调用迭代器的remove方法,删除集合中的每一项数据;
---------------------------------------------------------------------------------------------------------------------------------
ArrayList:
array数组的意思,list列表的意思;数组列表
存入的顺序和取出的顺序一致:
优点:查询快,只要知道数据的下标查询速度非常快,arraylist是所有容器中查询速度最快的一个;
缺点:增删慢,因为数组满了后要扩容,扩容要新创建一个是原来1.5倍的新数组,然后把之前的元素copy到新数组中来;这样一来数据越多越慢;
大小Size是元素的个数;容量(capacity)就是数组的长度;
三个构造方法:
无惨构造方法的初始容量是10;第二个构造方法可以指定容量;第三个可以将另一个集合数组中的元素插入到ArrayList集合中数组的初始容量就是集合的大小;
常用方法:
ArrayList不仅是list下的一员,又做为collection旗下的一员,所以不但拥有collection里面的所有方法,还拥有list里面的所有的方法;
ArrayList常用的方法有10个
---------------------------------------------------------------------------------------------------------------------------------
LinkedList:
linked链式的意思,list列表;链式列表,基于链表实现的,而且是双向的,所以又称“双向链表”
前后没有节点的时候
前后有节点的,可以从尾部和头部添加节点
- 有序,元素存入和去除的顺序一致
- 可重复,里面可以存储重复的元素
- 可为null,它里面可以存储null元素
优点:增删快,增加的时候只需要记住前后节点就可以了
缺点:查询慢,每次查询都要从头部或者尾部按顺序查找
既实现了list接口,可实现了Deque接口,而list和queue又集成了collection,所以拥有的方法特别多
它常用的方法有9个:
Linkedlist删除数据:
删除数据一般是找到匹配项以后,使用迭代器的remove方法进行删除
ArrayList和LinkedList 区别:
综上所述:ArrayList多用于查询较多的应用场景种,linkedlist多用于频繁增删的场景中
---------------------------------------------------------------------------------------------------------------------------------
HashMap: hash散列,哈希,map映射;哈希map
key-velue的形式存储数据的,这些数据保存在数组中,默认初始容量是16
数组是有序的,但是hashmap中的数据并不是按照顺序存放的,而是先根据key计算出hash值,再余数组容量-1
hash值&(数组容量-1),等同于数组容量取余得到的余数就是元素的位置
如此依赖势必会有多个元素被分配到同一个位置上
hashmap用链表的形式将多个元素链接起来解决了多个元素位置冲突的问题
1.无序
存入顺序,取出顺序
2.key可以为null,并且分配的hash值為0
3.key不可以重复,重复的key新值会覆盖旧值;
4.hash冲突:它是两个不同的key产生了相同的hash值,发生hash冲突的原因是取值范围的问题,例如一个二进制位取值范围是:0-1,要在这个范围内取值要么是0要么是1,发生hash碰撞的概率是1/2
如果取值范围是16位的时候,发生碰撞的概率会越小
在hashmap中hash值是int类型4个自字节长度是32个二进制位
碰撞的概率是:
更长的hash值意味着碰撞的概率越低,但也需要更大的空间和计算,我们需要在性能和成本之间做好权衡
5.链化<==>树化
树化:当链表长度大于8,并且容量大于等于64的时候会将链表转化为红黑树
转化为红黑树的目的是为了提高查询速度,因为链表一旦长了以后查询就会变得很慢,
链化:另外红黑树也会变回链表,当红黑树种的节点小于等于6的时候,红黑树将转化为链表,这个过程称之为链化,此时的节点数很少,链表与红黑树的查询速度不差上下,而且在新增元素的时候链表不用计算节点的位置,直接插在尾部,但红黑树还要计算节点的位置,因此他们两个相互转换可以形成很好的互补;
6.扩容:当元素个数超出临界值的时候,hashmap就需要扩容
临界值=容量*负载因子
扩容后是之前的两倍,频繁扩容很影响hashmap的性能,所以合适的容量与负载因子至关重要
7.hashmap实现了map接口,拥有map里面的所有方法,其中8个方法是最常用的
鼎鼎大名的HashMap一定要整体总结理解下:
先介绍一下hashmap?
答:HashMap嘛,在jdk7是数组+链表,jdk8数据结构做了升级,变成了数组+链表+红黑树了;
那你说说为什么这么设计,以及jdk8为什么要做出这种升级呢?
答:Map这种集合容器,最主要的应用就是想通过一个key最快的时间找到对应的value,事实上这个时间复杂度接近为O(1),那么怎么样才能实现这么快的速度呢?于是就引入了数组,数组可以理解为内存中一块连续的内存空间,且每一小块空间都有自己的索引,通过这个索引就能直接找到对应空间的值。可以直接把key和数组中的某个索引对应上,我放也放到这个索引里面,取也直接取这个索引去取,是不是依赖数组的特性,我就可以用O(1)的时间复杂度快速定位到我想要的值了;
那链表和红黑树又是怎么回事?
答:在把key映射成数组索引值这件事情上,期望的不同的key映射到不同的数组索引值中;但是天不遂人愿,总有可能两个key好巧不巧就映射成了同一个数组索引值,这就是哈希碰撞;碰撞之后就有两种情况:
一是同一个key,我就要把原有的key的值覆盖掉;
二就是两个key算出来的值是一样的,那就只能把两个key和value放到同一个数组索引的内存里面,也就形成了链表,当然,碰撞的越来越多,这个链表就会越来越长;
链表越来越长,那么为什么红黑树能就解决呢?
答:要从链表和红黑树的查找效率说起,链表这种数据结构不需要连续的内存空间,内部通常是持有了下一个节点的引用,所以,链表要查询出某一个元素,就要从头节点开始查,直到next为null才能确定整条链表查询结束,在最坏情况下,链表的长度是n,就是遍历n次才能找到元素,所以,链表的时间复杂度为O(n)。
这种的查询速度如果数据多了是不可以容忍的,怎么解决呢?
答:如果链表的长度大于了8,达到了9,就变成红黑树,当然还要满足整个hash表中的元素达到了64,之所以条件比较苛刻,是因为链表转数组本身就很耗性能,不到万不得已,万万使不得啊。红黑树这种数据结构首先是二叉搜索树,二叉搜索树就满足了查找的时间复杂度是O(lgn),是一种折半查找,一次排除一半的数据,就算你有100W数据,查找固定的数,也只需要20次,就是这么拽,而红黑树在有二叉搜索树的查询效率的前提下,又保证了树的平衡。所以链表在上述情况下会进化成红黑树,当然,又进化也有退化,退化的节点就是同一个哈希桶中的元素数小于6,就会从红黑树变回链表。之所以中间留了个7,就是为了防止频繁的树化、链表化、树化、链表化。
那么key是怎么转换成数组索引的呢?
答:public native int hashCode(); 这个方法可以返回一个32位的数字,当然,这个32位的数字是不能直接去当数组的索引的,因为一般情况下不会有那么大的数组。所以这个hashcode肯定是要经过某种转换,如果数组的长度是16的话,应该转换成为的值在0到15之间,才能保证落在数组的某一个索引值里面。这种的实现方式,常规的能想到的是取模。但是我们都知道,在计算机中,位运算的效率是最高的,于是,这个公式是这个样子的 (n - 1) & hash ,这种运算的结果和取模不一定相同(有很多博客说相同是错误的)。至于这么做为什么就能达到和取模一样的效果的呢?我们还是拿16举例:
再加上位运算的速度相当快,所以HashMap是采用这种方式寻找数组索引的。曾经对相同的100W样本做过取模和位运算,大概快了几十倍把。
公式 (n - 1) & hash 中的hash不是直接用到hashcode,这个jdk7和jdk8还是不同的:
答:先取得key的hashcode,然后扰动函数高位低位特性融合,最后算出在数组中的索引
jdk1.7是采用的头插法会造成链表成环,jdk8是尾插法,就不会了。
那jdk8之后HashMap是不是就线程安全了?
答:不是的,jdk8只是不会发生链表成环的情况,但是在put操作的时候,会出现元素被覆盖的情况,并且size++也是有线程安全问题的,如果要考虑多线程的情况下使用,建议使用ConcurrentHashMap,里面有分段锁,所谓分段锁,jdk7中ConcurrentHashMap外面有一个Segment数组,这个Segment继承了ReentrantLock,我们就可以对每一个Segment单独上锁,既能保证线程安全,锁的粒度又不会太大,性能又不会太差;
数组什么时候会扩容?
答:有一个东西叫负载因子,默认是0.75,但是科学家前辈算出来的0.69几才是最完美的值。这个值是用来在空间和时间上取得平衡,比如原来数组长度是16,那么达到 16 *0.75=12 就会扩容了,一般扩容是原有数组的两倍。
--------------------------------------------------------------------------------------------------------------------------------
LinkedHashMap 链式哈希映射
1.介绍linkedHashMap linked,链式的意思;hashmap,Hash映射
2.添加顺序
3.访问顺序
4.常用方法
用双向链表来保证了元素的顺序
来记录的头结点和尾结点
2.添加顺序和取出顺序是一致的
3.被访问的元素会排在最后,通俗的讲就是被get过的元素会排在最后
可以设置排序方式:
有5个构造方法:4个构造方法有默认值,只有一个构造方法是可以指定排序顺序的
4.linkedhashmap的常用方法有那些:它既继承了hashmap又实现了map接口
当时常用的只有8个方法:
------------------------------------------------------------------------------------------------------------------------------
TreeMap:二叉树映射
介绍treemap,
1.排序:根据key进行排序
遍历方式:中序遍历
2.无序
3.key不可以为null:会抛空指针,而且无法参与比较(null.compareTo("E"))
4. key唯一 :当两个key相同的时候,走else分支,新值会覆盖旧值
treemap 的常用方法
是以二叉树的形式存储数据的,二叉树是红黑树,红黑树拥有自平衡的特点
红黑树的5个性质:
常用的方法有9个:
------------------------------------------------------------------------------------------------------------------------------
HashSet 散列集合,内部是hashmap存储数据
它的四个构造方法都是在创建hashmap对象,所以,我们创建hashset就是在创建一个hashmap;
存数据的时候只需要把key当velue,值统一用一个“present”值填充
添加顺序和取出顺序:存入的顺序和取出的顺序不一致,其他的跟hashmap一致;
hashmap和hashset的区别:
------------------------------------------------------------------------------------------------------------------------------
LinkedHashSet
元素新增和取出的顺序一致,其他特点和优缺点跟linkedHashMap一样;
LinkedHashSet常用方法有那些?常用的有7个
LinkedHashSet和LinkedHashMap的区别:LinkedHashSet只有一种顺序,那就是添加顺序,也是默认的取出顺序