二、Java集合

Github地址

优快云地址

二、Java集合

Java中的集合,从上层接口看分为两类,MapCollection,而Collection又分为SetList

2.1 Java中常见的集合
  • Map接口和Collection接口是所有集合框架的父接口
  • Collection接口的子接口包括:Set接口和List接口
  • Map接口的实现类主要有:HashMap、TreeMap、Hashtable、LinkedHashMap、ConcurrentHashMap以及Properties等
  • Set接口的实现类主要有:HashSet、TreeSet、LinkedHashSet等
  • List接口的实现类主要有:ArrayList、LinkedList、Stack以及Vector等
2.2 HashMap有了解吗?
  • HashMap底层数据结构数组+链表。JDK8及其以后的版本是数组+链表+红黑树实现,红黑树解决了链表太长导致查询速度变慢的问题。
  • HashMap初始容量为16,加载因子为0.75,扩增容量为原容量的2倍。
  • HashMap储存和获取原理: HashMap的put方法传递键和值来存储时,先对键调用hashCode()方法,返回的hashCode用于找到bucket位置来存储Entry对象。也就是找到了该元素应该被存储的桶(数组)中,当hashCode相同时,bucket位置发生了冲突,也就是hash冲突,这个时候bucket后边接上一个链表(红黑树)来解决。当调用get方法获取键的值时,会先根据键的hashcode找到相应的bucket,再根据equals方法在链表和红黑树中找到对应的值。
  • HashMap扩容步骤:当触发加载因子0.75时,将会创建原来HashMap大小的两倍的bucket,并将原来的对象放入新的bucket数组中。这个过程叫rehashing,因为再次调用hashcode方法找到在新的bucket中的位置。

延伸1. JDK8中做了扩容优化,省去了重新计算hash值的时间。

延伸2. HashMap链表超过8转换为红黑树,CurrentHashMap为6

2.3 HashMap和Hashtable的区别有哪些?
  • HashMap没有考虑同步,是线程不安全的;HashTable 使用了synchronized关键字,是线程安全的。
  • HashMap允许null作为key,HashTable不允许null作为key,同时HashTable的value也不能为空。
  • HashTable的默认初始容量是11, 扩容是2n+1;HashMap的默认初始容量是16,扩容是2*n

延伸1. HashMap为什么不是线程安全的?
原因有二,①HashMap线程不安全主要是考虑到多线程环境下进行扩容可能会出现HashMap死循环。②HashTable线程安全的原因是其内部在put和remove等方法上做了synchronized处理,所以对单个方法的使用是线程安全的。但是多个方法进行复合操作,线程安全性也是无法保证的。

2.4 解决Hash冲突的方法有哪些?
  • 拉链法(现阶段HashMap使用)
  • 线性探测再散列法
  • 二次探测再散列法
  • 伪随机探测再散列法
2.5 ConcurrentHashMap

详细查看
答: ConcurrentHashMap结合了HashMap和Hashtable二者的优势。HashMap没有考虑同步,

Java7中 ConcruuentHashMap 使用的分段锁,也就是每一个 Segment 上同时只有一个线程可以操作,每一个 Segment 都是一个类似 HashMap 数组的结构,它可以扩容,它的冲突会转化为链表。但是 Segment 的个数一但初始化就不能改变。其分段锁实现方式为内部实现了MapEntrySegment,前者用存储键和值,后者实现了可重入的锁ReentrantLock守护每一个键值,当对HashEntry数组的数据进行修改时,必须首先获得对应的Segment锁。

Java8 中的 ConcruuentHashMap 使用的 Synchronized 锁加 CAS 的机制。结构也由 Java7 中的 Segment 数组 + HashEntry 数组 + 链表进化成了Node 数组 + 链表 / 红黑树,Node 是类似于一个 HashEntry 的结构。它的冲突再达到一定大小时会转化成红黑树,在冲突小于一定数量时又退回链表。

延伸1. 在实际的开发中,我们在单线程环境下可以使用HashMap,多线程环境下可以使用ConcurrentHashMap,至于Hashtable在每次同步执行时都要锁住整个结构。已经不被推荐使用了。

2.6 TreeMap有哪些特性?

答:TreeMap底层使用红黑树实现,TreeMap中存储的键值对按照键来排序。

  • 如果key存入的是字符串等类型,按照字典顺序。
  • 如果key为自定义引用类型,比如说User、Person之类,那么该对象必须实现Comparable接口,并覆盖compareTo方法;或者在创建TreeMap的时候,我们指定使用的比较器。两种方法如下所示
// 方式一:定义该类的时候,就指定比较规则
class Person implements Comparable{
    @Override
    public int compareTo(Object o) {
        // 在这里边定义其比较规则
        return 0;
    }
}
// 方式二:创建TreeMap的时候,可以指定比较规则
new TreeMap<Person, Integer>(new Comparator<Person>() {
    @Override
    public int compare(Person p1, Person p2) {
        // 在这里边定义其比较规则
        return 0;
    }
});
2.7 ArrayList 和LinkedList有哪些区别?
  • ArrayList 底层由动态数组实现,实质上是一个动态数组;
  • LinkedList 底层由双向链表实现,可当作链表、队列、双端队列使用。
  • ArrayList 在随机存取方面效率高于LinkedList;
  • LinkedList 在增删方面效率高于ArrayList。
  • ArrayList 会预留一部分空间,当空间不足时会进行扩容;
  • LInkedList 的开销是必须存储节点的信息以及节点的指针信息。

延伸1. 还有一个集合Vector,它是线程安全的ArrayList的,但现在已经弃用了,多线程下推荐使用CopyOnWriteArrayList替代ArrayList来保证线程安全。

2.8 HashSet和TreeSet有哪些不同?
  • HashSet底层使用Hash表实现:保证元素的唯一性,先判断hashcode,再equals;
  • TreeSet底层使用红黑树实现:保证元素唯一性通过实现Compator或者实现Comparable接口。

延伸1. HashSet的add方法底层使用HashMap的put方法将key=e,value=PRESENT构建成key-value键值对,当此e存在于HashMap的key中,则value将会覆盖原有value,但是key保持不变,所以如果将一个已经存在的e元素添加中HashSet中,新添加的元素是不会保存到HashMap中,所以这就满足了HashSet中元素不会重复的特性。

延伸2. HashSet的contains方法使用HashMap得containsKey方法实现

2.9 List和Set的区别?
  • List是有序的并且元素是可以重复的
  • Set是无序(LinkedHashSet除外)的,并且元素是不可以重复的
    (此处的有序和无序是指放入顺序和取出顺序是否保持一致)
2.10 Iterator和ListIterator的区别是什么?
  • Iterator可以遍历list和set集合;ListIterator只能用来遍历list集合
  • Iterator前者只能前向遍历集合;ListIterator可以前向和后向遍历集合
  • ListIterator其实就是实现了前者,并且增加了一些新的功能。
2.11 Collection和Collections有什么关系?

答:Collection是一个顶层集合接口,其子接口包括List和Set;而Collections是一个集合工具类,可以操作集合,比如说排序,二分查找,拷贝集合,寻找最大最小值等。 总而言之:带s的大都是工具类

2.13 LinkedHashMap和LinkedHashSet有了解吗?

答:LinkedHashMap可以记录下元素的插入顺序和访问顺序,具体实现如下:

  • LinkedHashMap内部的Entry继承于HashMap.Node,这两个类都实现了Map.Entry<K,V>
  • LinkedHashMap的Entry不光有value,next,还有before和after属性,这样通过一个双向链表,保证了各个元素的插入顺序。
  • 通过构造方法public LinkedHashMap(int initalCapacity, float loadFactor, boolean accessOrder),accessOrder传入true可以实现LRU缓存算法(访问顺序)
  • LinkedHashSet底层使用LinkedHashMap实现,两者的关系类似与HashMap与HashSet的关系。

扩展1. 什么是LRU算法?
LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。

扩展2. LinkedHashMap如何实现LRU算法?
由于LinkedHashMap可以记录下Map中元素的访问顺序,所以可以轻易的实现LRU算法。只需要将构造方法的accessOrder传入true,并且重写removeEldestEntry方法即可。具体实现参考如下:

import java.util.LinkedHashMap;
import java.util.Map;

public class LRUTest {

    private static int size = 5;

    public static void main(String[] args) {
        Map<String, String> map = new LinkedHashMap<String, String>(size, 0.75f, true) {
            @Override
            protected boolean removeEldestEntry(Map.Entry<String, String> eldest) {
                return size() > size;
            }
        };
        map.put("1", "1");
        map.put("2", "2");
        map.put("3", "3");
        map.put("4", "4");
        map.put("5", "5");
        System.out.println(map.toString());

        map.put("6", "6");
        System.out.println(map.toString());
        map.get("3");
        System.out.println(map.toString());
        map.put("7", "7");
        System.out.println(map.toString());
        map.get("5");
        System.out.println(map.toString());
    }
}
/*打印结果*/
{1=1, 2=2, 3=3, 4=4, 5=5}
{2=2, 3=3, 4=4, 5=5, 6=6}
{2=2, 4=4, 5=5, 6=6, 3=3}
{4=4, 5=5, 6=6, 3=3, 7=7}
{4=4, 6=6, 3=3, 7=7, 5=5}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Yexiaomola

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值