Java集合的面试点


注:题目抄的,理解和代码测试是自己写的

List,Set和Map差别

  1. List的存储对象不唯一,可以有多个相同的对象存储进去
  2. Set的存储对象唯一,不能存储已经有的一样的对象
  3. Map有key(键)和value(值),键会映射到值上面。key是不能重复的,但是value可以重复。一个键只能映射一个值。但是一个值可以有多个映射。

ArrayList和LinkedList区别

ArrayList

  1. 线程安全方面:都是不同步的,都是线程不安全的。
  2. 底层的结构:ArrayList的底层是Object数组,LinkedList的底层是双向链表。
  3. 增删改查的时间复杂度:对于ArrayList,增加为O(1),删除和插入都为O(n-i),i为插入或者删除的位置。因为要先到第i个位置,然后再
    移动后面的n-i个对象。对于LinkedList,增加为O(1),删除或者插入都为O(n),因为每次都只要移动到第i个位置就可以了。
    4.是否支持快速访问:ArrayList底层为数组,所以支持get(index),快速访问。但是LinkedList不支持下标快速访问。有RandomAccess接口声明的类就支持快速访问
    5.占内存的原因:ArrayList因为底层是数组,所以每次结尾都会有多余空间空出。LinkedList链表的话,每个对象要有first,next等,所以对象相对ArrayList占空间。
    6.遍历方式的选择
    ArrayList因为被RadomAccess接口声明,它是支持快速访问的。所以优先选择for循环遍历。
    LinkedList不支持快速访问,所以可以用iterator或者foreach循环(foreach也是用iterator实现的)。
    public static void main(String[] args) throws IOException {
        List<Integer> list = new LinkedList<>();
        list.add(8);
        list.add(7);
        list.add(6);
        list.add(4);
        Iterator iterator = list.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }
    }

ArrayList和Vector

ArrayList
线程不同步的,所以效率搞。单线程适用。
Vector
线程同步,所以效率低。多线程适用。

ArrayList的扩容机制

ArrayList底层是数组,这个数组初始值为10。size用来表示该list存储对象的个数。如果len用来表示数组的大小。每次调用add方法就会调用扩容方法。扩容方法会判断size+1是否>len,如果大于就会扩容,一般是将数组大小扩容至原来的1.5倍大小。

HashMap和HashTable区别

1.HashMap线程不安全,HashTable线程安全
2.HashMap运行效率高于HashTable
3.HashMap的key支持一个null值,HashTable不支持。HashTable基本淘汰
4.HashMap的对于的链表长度大于阈值8时就会转变为红黑树。
注意:HashMap的哈希数组大小总是为2的幂-1,这样可以最大程度减少hash值的碰撞

HashMap的线程安全化

        Map map = new HashMap<String,String>();
        List list = new ArrayList();
        Set set = new HashSet();
        Collections.synchronizedMap(map);
        Collections.synchronizedList(list);
        Collections.synchronizedSet(set);

HashMap和HashSet的区别

1.接口
HashMap实现了Map接口,HashSet实现了Set接口。HashSet是HashMap实现的。代码简单。
2.存储方式
HashMap采用键值对存储,键唯一。HashSet采用唯一对象存储数据。
3.hashcode值计算
HashMap的hashCode是通过key值计算的。HashSet是通过传入的对象计算的。当对象的hash值一样时使用equals判断是否为同一个对象。
4.添加元素区别
HashMap使用put(key,value)。HashSet使用add(value)添加对象。

HashSet如何检查

当对象加入HashSet的时候,先会判断这hashcode值有没有已经存在,若是不存在则认为没有数据重复,可以添加进去。当hashcode值有重复,则会调用equals方法,如果还是返回true就会报错,添加不进去。

public class Test2 {


    public static void main(String[] args) throws IOException {
        //接下来测试
        //因为重写了equals和hashcode方法,才可以实现。若是没有重写hashcode方法就不能实现
        //因为系统的hashcode方法只要引用不一致,hash值就不可能相等
        Set<Demo> set = new HashSet();
        Demo demo1 = new Demo("sb?",50);
        Demo demo2 = new Demo("sb?",50);
        Demo demo3 = new Demo("sb?",66);
        set.add(demo1);
        set.add(demo2);
        set.add(demo3);
        System.out.println(set.size());//大小只有2
    }

}

class Demo{
    public String name;
    public int age;
    public Demo(){

    }
    public Demo(String name,int age){
        this.name = name;
        this.age = age;
    }

    //重写equals方法
    @Override
    public boolean equals(Object obj) {
        return (this.name.equals(((Demo)obj).name)) && (this.age == ((Demo)obj).age);
    }
    //重写equals后不重写hashCode方法是没意义的
    //当hashCode方法重写后,内容一样,指向不一的对象放入Set才会报错

    @Override
    public int hashCode() {
        //随便写的
        return (this.age+this.name.length())*68;
    }
}

HashMap底层实现

jdk1.8以前
HashMap底层使用的是数组加链表,也就是链表散列。这里是如何得到hash值的呢。通过key来获取hashcode值,然后得到的hashcode通过扰动函数后得到hash值.hash值&(n-1)得到的数值就是数组下标的位置了。得到下标后就会到对应的链表,然后判断有没有key一样的map,如果有则会覆盖,没有则会在链表最后面添加。
在这里插入图片描述

jdk1.8即以后
再这之后当链表的长度大于阈值8的时候,链表就会为红黑书,这样可以加快搜索的时间。
在这里插入图片描述

HashMap的数组长度为2的幂-1

原因
这样可以减少hash的碰撞,防止一个下标的链表存放过多数据。

扫描二维码关注公众号,回复: 11644604 查看本文章

多线程下的HashMap死循环问题和解决

问题
在多线程下使用HashMap的话容易导致链表变为循环链表,从而出现死循环问题。jdk1.8以后解决了这个问题,但是多线程下不推荐使用HashCode
解决
不使用HashMap,使用ConcurrentHashMap。

ConcurrentHashMap,HashTable,HashMap

1.ConcurrentHashMap和HashTable相对HashMap来说,都实现了线程同步。而HashMap线程不同步。
2.ConcurrentHashMap使用的是散列数组,所以锁也是针对散列数组的散列锁。将一个数组分为了多个散列数组,然后分开加锁,可以提高多线程的运行效率。而HashTable没有散列数组,虽然也是线程安全的,但是一个锁将整个数组全部锁起来,导致了运行效率低下问题。
在这里插入图片描述

ConcurrentHashMap的底层实现

jdk1.7:(使用散列数组,Segment可重入锁数组和HashEntry链表)
首先底层是一个散列的Segment数组,Segment基础了ReentrantLock,是可重入锁。
一个ConcurrentHashMap里面只有一个Segment数组,每个Segment元素下面又存放着HashEntry,用于链表存放。
在这里插入图片描述

jdk1.8(采用CAS比较并交换算法和syncronized实现)
1.8即以后取消了Segment可重入锁,而是使用CAS和syncronized实现线程同步。同样但阈值超过8也会将链表转换为红黑树。而且syncronized只会锁住每个节点的头结点。这样效率就会高上N倍。这样只要hash不冲突就不会有线程阻塞。
在这里插入图片描述

Comparable和Comparator的区别和使用

注意:Comparable的compareTo方法默认只有一个参数,所以不能用匿名内部类方式。Comparator的有两个参数,所以哟个匿名内部类.
Comparable:要排序的对象实现这个接口,并重写compareTo方法

public class Test2 {

//运行结果是从小到大排序
    public static void main(String[] args) throws IOException {
        Demo demo1 = new Demo("sdf",656);
        Demo demo2 = new Demo("gwe",515);
        Demo demo3 = new Demo("hrw",8871);
        List<Demo> list = new ArrayList<>();
        list.add(demo1);
        list.add(demo2);
        list.add(demo3);
        Collections.sort(list);
        for(int i = 0;i < list.size();i++){
            System.out.println(list.get(i).age);
        }
    }

}

class Demo implements Comparable<Demo>{
    public String name;
    public int age;
    //重写compareTo方法
    @Override
    public int compareTo(Demo o) {
        return this.age - o.age;
    }

    public Demo(){

    }
    public Demo(String name,int age){
        this.name = name;
        this.age = age;
    }
}

Comparator

public class Test2 {

运行结果是从小到大排序
    public static void main(String[] args) throws IOException {
        Demo demo1 = new Demo("sdf",656);
        Demo demo2 = new Demo("gwe",515);
        Demo demo3 = new Demo("hrw",8871);
        List<Demo> list = new ArrayList<>();
        list.add(demo1);
        list.add(demo2);
        list.add(demo3);
        Collections.sort(list, new Comparator<Demo>() {
            @Override
            public int compare(Demo o1, Demo o2) {
                return o1.age - o2.age;
            }
        });
        for(int i = 0;i < list.size();i++){
            System.out.println(list.get(i).age);
        }
    }

}

class Demo{
    public String name;
    public int age;
    public Demo(){

    }
    public Demo(String name,int age){
        this.name = name;
        this.age = age;
    }
}

集合底层数据结构组成和使用总结

数据结构组成

Collection

List

  • ArrayList:底层为Object数组
  • Vector:底层为Object数组
  • LinkedList:底层为双向链表
    Set
  • HashSet:底层是基于HashMap实现的
  • LinkedHashSet:底层基于LinkedHashMap
  • TreeSet:底层是红黑书(有序,唯一)

Map

  • HashMap:数组+链表,jdk1.8以后,链表长度大于阈值8后会自动转换为红黑树
  • LinkedHashMap:在原来HashMap的基础上又加了一个双向链表
  • HashTable:数组+链表
  • TreeMap:底层是红黑树

使用场合

  1. 当需要用到键值对的时候就要用Map结构。需要排序用TreeMpa,不需要排序用HashMap
  2. 当不需要用到键值对,但是要有唯一值的时候用Set。TreeSet排序,HashSet不排序
  3. 当不需要键值对也不需要值唯一的时候就用List。常用ArrayList,因为可以实现快速查询。

注意:TreeSet和TreeMap实现对象的自动排序都要让对象继承Comparab接口,重写compareTo方法

public class Test2 {


    public static void main(String[] args) throws IOException {
        Demo demo1 = new Demo("sdf",656);
        Demo demo2 = new Demo("gwe",515);
        Demo demo3 = new Demo("hrw",8871);
        Set<Demo> set = new TreeSet();
        set.add(demo1);
        set.add(demo2);
        set.add(demo3);
        Iterator iterator = set.iterator();
        while(iterator.hasNext()){
            //输出结果为 515 , 656 , 8871
            System.out.println(((Demo)iterator.next()).age);
        }
    }

}

class Demo implements Comparable<Demo>{
    public String name;
    public int age;

    @Override
    public int compareTo(Demo o) {
        return this.age - o.age;
    }

    public Demo(){

    }
    public Demo(String name,int age){
        this.name = name;
        this.age = age;
    }
}

猜你喜欢

转载自blog.csdn.net/qq_44771337/article/details/108456769
今日推荐