欢迎光临
我们一直在努力

JAVA基础-集合篇

1.Java容器都有哪些?

Colleacion集合(包含List,Set)

List集合:有序的,可重复的,可以插入多个NULL元素,元素有索引。

        Vector:(Object数组)线程安全,有Synchronized对其方法进行修饰,数组结构。

        arrayList:(Object数组)线程不安全,数组结构。

        copyOnWriterArrayList: 线程安全,ReentrantLock(可重入锁)。

        LinkedList:(双向循环链表)链表结构,线程不安全。

Set集合:无序,且唯一,只允许传入一个NULL。

Set:

        hashSet:无序,唯一。基于hashMap实现的,底层采用HashMap来保存元素。

        treeSet:有序,唯一。 基于红黑树(自平衡的排序二叉树)

        LinkedHashSet: LinkedHashSet继承与hashSet实现set方法,并且其内部就是通过LinkedHashMap进行实现的。

        Set集合方法去重List集合数据

                例:
                Set<对象> s = new TreeSet<>(Comparator.comparing(对象::get比较的字段));
                s.addAll(list集合);
                List<对象> list = s.stream().collect(Collectors.toList());
                    //list为去重成功的数据

Map:

        HashMap:JDK1.8之前使用的数据加链表的形式组成的,数组是hashMap的主体,链表主要是为了解决hash冲突而存在的(拉链法解决冲突:哈希冲突时,新元素通过“头插法”插入对应链表尾部),JDK1.8之后,在解决hash冲突进行了变化,当链表长度大于阈值(模式为8)时,并且数组长度大于等于64时,链表转化为红黑树,减少搜索的时间;若后续链表长度缩短至6以下,会重新转为链表。

        hash冲突:向hash表中进行存储数据的时候,通过hash函数计算出的数据存放的地址,但是这个地址中已经存在了数据,所以称为hash冲突。
        红黑树:是一种平衡二叉查找树,平衡的属性性能很好,任意节点左右子树高度相差不大于1,节点有红色和黑色两种,
        1.根节点是黑色的 每个叶子节点是不存储数据的黑色空节点  
        2.任何相邻的两个节点不能同时为红色 
        3.任何节点到其可到达的叶子节点之间包含性相同数量的黑色节点,数的高度稳定趋近与logn  时间复杂度也为logn  不超过2log(n+1)。
        treeMap:红黑树。
        hashtable:线程安全的,有synchronized对其方法进行修饰,数据+链表组成的,数组是hashmap的主体,链表则是主要为了解决 hash冲突存在的。
        linkedhashmap: LinkedHashMap继承了hashmap。所以底层还是数据链表红黑树,在基础上增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序,同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。
        ConcurrentHashMap:Java5中支持高并发、高吞吐量的 线程安全的hashmap实现的,它是由Segment数组结构和hashEntry数组结构组成。Segment数组结构在ConcurrentHashMap中扮演一个锁的模式,hashentry用于存储键值对数据,一个ConcurrenthashMap里包含一个Segment数组,和hashmap相似数组+链表,一个segment中包含一个hashentry数组,每个hashentry是一个链表结构的元素,每个segment守护着一个hashentry数组中的元素,对hashentry中的数据进行修改的时候,必须获得segment锁

2.Collection和Collections有什么区别?

Collection: 是一个集合接口,它提供了对集合对象进行基本操作的通用接口方法,所有的集合都是它的子类,比如List,Set等。

Collections:是一个包装类,包含了很多静态方法,不能被实例化,就想一个工具类。比如提供的排序方法。比如Collections.sort(list)。

3.List、set、map之间有什么区别?

List、Set、Map的区别主要体现在两个方面:元素是否有序,是否允许元素重复。

意思不同:

        List:有序,可重复。

        Set:无序,不可重复的集合。重复元素会覆盖掉。

        Map:键值对,键唯一,值不唯一。Map集合中存储的是键值对,建不能重复,值可以重复。

用途不同:

        List:集合中对象按照索引位置排序,可以有重复对象,允许按照对象在集合中的索引位置检索。

        map:每一个元素包含一个键和一个值,成对出现,键对象不可以重复,值对象可以重复。

        Set:集合中的对象不按照特定的方式排序,并且没有重复对象,但它的实现类能对集合中的对象按照特定的方式排序。

4.HashMap和HashTable的区别

存储:HashMap运行Key和Value为NULL,而HashTable不可以。

线程安全方面:HashTable是线程安全的,HashMap是线程不安全的。

推荐使用:在HashTable的类注释中可以看到,HashTable是保留类不建议使用,推荐在单线程环境下使用HashMap代替,如果需要对线程环境中使用就是用ConcurrentHashMap代替。

5.如何决定使用HashMap和TreeMap

对于在Map中插入,删除,定位一个元素这类操作,HashMap是最好的选择,因为相对于TreeMap来说HashMap的插入会更快,但如果需要对某个Key进行遍历的时候,就可以使用TreeMap。

6.说一下HashMap的实现原理

HashMap基于Hash算法实现,我们通过Put(Key,Value)的方式存储,Get(Key)来获取数据。当传入Key时,HashMap会根据key,hashCode()计算出hash值,根据Hash值将Value保存到bucket里面,当计算出的hash值相同时,我们称之为hash冲突,HashMap的做法是用链表+红黑树存储相同Hash值的Value。当Hash冲突的个数较少,使用链表否则使用红黑树。

解决Hash冲突的方法

        1.开放寻址法:就是当计算出的Hash值对应的地址重复时,继续往后找,寻找空的位置进行插入。

        2.链表法:就是在对应的地址中增加链表将值进行插入,值有很多并且超过最大的限度,也就是8位时,并且数组长度大于等于64时,使用红黑树的形式。

7.说一下HashSet的原理

HashSet是基于HashMap实现的,HashSet底层使用了HashMap来保存所有的元素,因此HashSet的实现比较简单,相关HashSet的操作,基本上都是直接调用底层HashMap的相关方法来完成的。HashSet不允许出现重复的值。

8.ArrayList与LinkedList的区别

数据结构实现:ArrayList是动态数组的数据结构实现,LinkedList是双向链表的数据结构进行实现的
随机访问效率方法:ArrayList比LinkedList的随机访问效率高,因为LinkedList是线性的数据存储结构,所以需要移动指针从前往后进行查询
增加和删除的效率:在非首尾的增加和删除操作,LInkedList的操作效率要比ArrayList的效率高,因为ArrayList增删的时候会影响数组内其他的下标。
综合来说,在进行查询,访问的情况下使用ArrayList,在进行增删改时使用LinkedList方法

9.如何实现数组与List之间的转换?

数组转List:使用Arrays.asList(array)进行转换

List转数组:使用List自带的Array()方法。

10.ArrayList与Vector的区别

线程安全:Vector使用Synchronized来实现线程同步,是线程安全的。而ArrayList是非线程安全的。
性能方面:ArrayList在性能方面要优于vector。
扩容方面:ArrayList和vector都会根据实际的需要动态的调整容量,只不过Vector扩容每次会增加1倍,而ArrayList只会增加50%。

11.Array和ArrayList的区别

Array可以存储基本数据类型和对象,ArrayList只能存储对象。

Array是指定固定的长度信息的,而ArrayList大小是自己进行扩容的,ArrayList的初始容量是10,当初始容量不能够满足的时候,进行扩容,扩容扩原来的50%,当需要扩容时,ArrayList会创建一个更大的内部数组,并将所有的元素从旧数组复制到新数组中。这涉及到创建新数组、复制元素以及销毁旧数组的操作。由于这些操作的开销较大,频繁的扩容可能会影响性能。

Array内置方法没有ArrayList多,比如addAll,removeAll,iteration等方法只有ArrayList有。

12.在Query(队列)中poll()和remove()有什么区别?

相同点:都是返回第一个元素,并且在队列中进行删除返回的对象
不同点:如果没有元素poll()时会返回null,而remove()会直接抛出NoSuchElementException异常。
Queue queue = new LinkedList();
queue. offer(“string”); // add
System. out. println(queue. poll());
System. out. println(queue. remove());
System. out. println(queue. size());

13.哪些集合类时线程安全的?

Vector,HashTable,Stack都是线程安全的,而向JDK1.5之后,Java.util.concurrect并发包的出现,ConcurrectHashMap也是线程安全的。

14.迭代器Iterator是什么?

Iterator 接口提供遍历任何Collection的接口,可以从一个Collection中使用迭代器方法来获取迭代器实例。迭代器取代了Java集合框架中的EnumerAtion,迭代器允许调用者在迭代过程中移除元素。

15.迭代器如何实现的?

//Iterator 使用代码如下:
List list = new ArrayList<>();
Iterator it = list. iterator();
while(it. hasNext()){
    String obj = it. next();
    System. out. println(obj);
    }
/*Iterator使用起来更加安全,因为它可以确保,在当前遍历的集合元素被更改的时候,就会抛出ConCurrectModificationException异常*/

16.Iterator和ListIterator的区别

Iterator 可以遍历set 和List集合,而ListIterator只能遍历List集合
Iterator 只能单向进行遍历 ,ListIterator 可以双向进行遍历
ListIterator 从 Iterator 接口继承,然后添加了一些额外的功能,比如添加一个元素,替换一个元素、获取前面或者后面的索引位置。

17.怎么确保一个集合不被修改?

可以使用Collection.unmodifiableCollection(Collection c) 方法来创建一个只读集合,这样改变集合的时候就会抛出异常Java. lang. UnsupportedOperationException 异常

//示例代码如下:
List list = new ArrayList<>();
list. add(“x”);
Collection clist = Collections. unmodifiableCollection(list);
clist. add(“y”); // 运行时此行报错
System. out. println(list. size());

18.ArraryList的初始容器是什么?扩容机制是什么?扩容过程是怎么样的?

初始容量:默认为10,也可以通过构造方法传入大小。

扩容机制:原数组长度+原数组长度/2(源码中是原数组右移一位,也就相当于除以2)

                注意:扩容后的ArrayList底层数组不是原来的数组。

扩容过程:因为ArrayList底层是数组,所以它的扩容机制和数组一样,首先新建一个新数组,长度是原数组的1.5倍,然后调用Arrays.copyof()复制原数组的值,然后复制给新数组。

19.什么是哈希表?

根据关键码值(Key  Value)而直接进行访问的数据结构,在一个表中,通过H(Key) 计算Key在表中的位置,H(Key)就是哈希函数,表就是Hash表。

20.什么是哈希冲突?

不同的Key通过哈希函数计算出相同的存储地址,这就是哈希冲突。

21.解决哈希冲突

1.开放地址法

如果发生了Hash冲突,就会以当前地址为基准,再去寻找计算另一个位置,知道不发生Hash冲突。

寻找的方法有: 线性探测     1,2,3,m

                          二次探测      1的平方,-1的平方,2的平方,-2的平方,K的平方,-k的平方,K<=m/2

                          随机探测         生成一个随机数,然后从随机地址+随机数++

2.链地址法
        冲突的哈希值,连到同一个链表上
3.再hash法(再散列法)
        多个哈希函数,发生冲突,就在用另一个计算,直到没有冲突
4.建立公共溢出区
        哈希表分为基本表和溢出表,与基本表发生冲突的都填入溢出表     

22.HashMap的hash()算法,为什么不是h = key.hashcode(),而是Key.hashcode()^(h>>>16)

得到哈希值然后右移16位,然后进行异或运算,这样使哈希值的低16位也具有了一部分高16位的特性,增加更多的变化性,减少了哈希冲突。

23.为什么hashMap的初始容量和扩容都是2的次幂

因为计算元素存储的下标是(n-1)的哈希值,数组初始容量-1,得到的二进制都是1,这样可以减少哈希冲突,可以更好的均匀插入

24.HashMap如果指定了不是2 的次幂的容量会发生什么?

会获得一个大于指定的初始值的最接近2的次幂的值作为初始容量

25.HashMap为什么线程不安全

jdk1.7中因为使用了头插法,再扩容的时候,可能会造成闭环和数据丢失。
jdk1.8中使用了尾插法,不会出现闭环和数据丢失,但是再多线程下,会发生数据覆盖,(put操作中,再putVal函数中)值的覆盖还有长度的覆盖

26.解决HashMap的线程安全问题

使用hashTable解决,再方法加同步关键字,所以效率低,已经被弃用
使用Collections.synchronizedMap(new HashMap<>()),不常用
ConcurrentHashMap(常用)

27.ConcurrentHashMap的原理

jdk1.7:采用分段锁,是由Segment(继承ReentranLock:可重入锁,默认是16,并发度是16)和HashEntry内部类组成,每一个Sement(锁)对应1个HashEntry(Key,Value)数组,数组之间互不影响,实现了并发访问
JDK1.8:抛弃了分段锁,采用CAS(乐观锁)+synchronized实现更加细粒度的锁,Node数组+链表+红黑树构成,只要锁住链表的头节点(树的根节点),就不会影响其他数组的读写,提高了并发度

28.为什么用synchronized代替ReentranLock

节省内存开销,ReentranLock基于AQS来获取同步支持,但不能每个节点都需要同步支持,只有链表头节点或树的根节点需要同步,所以使用ReentranLock会带来很大的内存开销。
获得jvm支持,可重入锁只是api级别,而synchronized是jvm直接支持的,能够jvm运行时做出响应的优化。
再jdk1.6之后,对synchronized做了大量的优化,而且有多种锁状态,会从无锁->偏向锁->轻量级锁->重量级锁 步步转换。

AQS(Abstract Queued Synchronizer):一个抽象的队列同步器,通过维护一个共享资源状态(Volatile int State) 和一个先进先出(FIFO)的线程等待队列来实现一个多线程访问资源的同步框架

29.HashMap为什么使用链表

为了减少和解决哈希冲突,把冲突的值放在同一列表下

30.HashMap为什么使用红黑树

当数据过多,链表遍历较慢,所以引入红黑树

31.HashMap为什么一上来就不使用红黑树

维护的成本较大,红黑树再插入新的数据后,可能会变色,左旋,右旋来保持平衡,所以当数据少时,就不需要利用红黑树

32.为什么链表长度大于8,并且表的长度大于64的时候,链表会转换成红黑树

因为链表的长度越长,哈希冲突概率就越小,当链表等于8时,哈希冲突就非常低了,是千万分之一,我们的map也不会存那么多的数据,如果真要存那么多的数据,那就转为红黑树,提高查询和插入的效率。

33.为什么转成红黑树是8 呢?而重新转为链表的阈值为6呢?

因为如果都是8的话,那么会频繁的进行转换,会浪费资源

34.为什么负载因子是0.75?

加载因子越大,填满的元素越多,空间利用率越高,但发生冲突的机会变大了;
加载因子越小,填满的元素越少,冲突发生的机会减小,但空间浪费了更多了,而且还会提高扩容rehash操作的次数。
“冲突的机会”与“空间利用率”之间,寻找一种平衡与折中。
又因为根据泊松分布,当负载因子是0.75时,平均值时0.5,带入可得,当链表为8时,哈希冲突发生概率就很低了。

35.什么时候扩容?

元素个数 > 数组长度 * 负载因子 例如 16 * 0.75 = 12,当元素超过12个时就会扩容。
链表长度大于8并且表长小于64,也会扩容

36.为什么不是满了扩容?

因为元素越多,空间利用率是高了,但是发生哈希冲突的几率也增加了。

37.扩容机制

jdk1.7: 会生成一个新table,重新计算每个节点放进新table,因为是头插法,在线程不安全的时候,可能会出现闭环和数据丢失。
jdk1.8: 会生成一个新table,新位置只需要看(e.hash & oldCap)结果是0还是1,0就放在旧下标,1就是旧下标+旧数组长度。避免了对每个节点进行hash计算,大大提高了效率。e.hash是数组的hash值,oldCap是旧数组的长度。

38.集合为什么要用迭代器(Iterator)

更加安全,因为它可以确保,在当前遍历的集合元素被更改的时候,就会抛出 ConcurrentModificationException 异常。
如果不用迭代器,只能for循环,还必须知道集合的数据结构,复用性不强。

赞(0)
未经允许不得转载:171主机测评 » JAVA基础-集合篇
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址