最新公告
  • 欢迎您光临极客文库,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入我们

  • 1. 你了解哪些集合类型?

    你应该知道以下几个最重要的类型:

    • ArrayList
    • LinkedList
    • HashMap
    • HashSet

    之后,你可能会被问到这样一些问题,比如应该何时使用此种特定类型,它比其他的好在哪里,它是怎么存储数据的以及隐匿在背后的数据结构是什么。最好的方法是尽可能多地了解这些集合类型,因为这类问题几乎是无穷尽的。
    2. HashMap 有什么特点?

    HashMap 基于Map接口实现,存储键值对时,可以接收 null 为键值。HashMap 是非同步的。
    3. HashMap 的工作原理是怎样的?

    HashMap 在 Map.Entry 静态内部类实现中存储键值对,使用哈希算法。在 put 和 get 方法中,使用 hashCode() 和 equals() 方法

    • 调用 put 方法时,使用键值对中的 Key hashCode() 和哈希算法找出存储键值对索引。键值对 Entry 存储在 LinkedList 中,如果存在 Entry,使用 equals() 方法来检查 Key 是否已经存在:如果存在,则覆盖 value;如果不存在,会创建一个新的 Entry 然后保存。
    • 调用 get 方法时,HashMap 使用键值 Key hashCode() 来找到数组中的索引,然后使用 equals() 方法找出正确的 Entry,返回 Entry 中的 Value。

    HashMap 中容量、负荷系数和阀值是重要的参数。HashMap 默认的初始容量是32,负荷系数是0.75阀值 = 负荷系数 x 容量。添加 Entry时,如果 Map 的大小 > 阀值,HashMap 会对 Map 的内容重新哈希,使用更大的容量(容量总是2的幂)。关于 JDK 中的 hash 算法实现以及由此引发的哈希碰撞现象(DDos攻击)都可能是面试的延伸问题。
    4. 能否使用任何类作为 Map 的 key?

    可以使用任何类作为 Map 的 key,然而在使用之前,需要考虑以下几点:
    • 如果类重写了 equals() 方法,也应该重写 hashCode() 方法。
    • 类的所有实例需要遵循与 equals() 和 hashCode() 相关的规则。
    • 如果一个类没有使用 equals(),不应该在 hashCode() 中使用它。
    • 用户自定义 Key 类最佳实践是使之为不可变的,这样 hashCode() 值可以被缓存起来,拥有更好的性能。不可变的类也可以确保 hashCode() 和 equals() 在未来不会改变,这样就会解决与可变相关的问题了。

    如果有一个类 MyKey,在 HashMap 中使用它:

    HashMap<MyKey, String> myHashMap = new HashMap<MyKey, String>();

    //传递给 MyKey 的 name 参数被用于 equals() 和 hashCode() 中

    MyKey key = new MyKey(“Pankaj”); // 假设 hashCode=1234
    myHashMap.put(key, “Value”);


    // 以下的代码会改变 key 的 hashCode() 和 equals() 值
    key.setName(“Amit”); // 假设新的 hashCode=7890

    //下面会返回 null,因为 HashMap 会尝试查找存储同样索引的 key,而 key 已被改变了,匹配失败,返回 null
    System.out.println(myHashMap.get(new MyKey(“Pankaj”)));


    这就是为什么 String 通常会用作 HashMap 的 Key,因为 String 的设计是不可变的(immutable)。

    5. 插入数据时,ArrayList、LinkedList、Vector谁速度较快?

    ArrayList、LinkedList、Vector 底层的实现都是使用数组方式存储数据。数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入数据慢。

    • Vector 中的方法由于加了 synchronized 修饰,因此 Vector 是线程安全容器,但性能上较ArrayList差
    • LinkedList 使用双向链表实现存储,按序号索引数据需要进行前向或后向遍历,但插入数据时只需要记录当前项的前后项即可,所以 LinkedList 插入速度较快

    6. 多线程场景下如何使用 ArrayList?

    ArrayList 不是线程安全的,如果遇到多线程场景,可以通过 Collections 的 synchronizedList 方法将其转换成线程安全的容器后再使用。例如像下面这样:

    List<String> synchronizedList = Collections.synchronizedList(list);

    synchronizedList.add(“aaa”);

    synchronizedList.add(“bbb”);

    for (int i = 0; i < synchronizedList.size(); i++)

    {

       System.out.println(synchronizedList.get(i));

    }


    7. 说一下 ArrayList 的优缺点

    ArrayList的优点如下:

    1. ArrayList 底层以数组实现,是一种随机访问模式。ArrayList 实现了 RandomAccess 接口,因此查找的时候非常快。
    2. ArrayList 在顺序添加一个元素的时候非常方便。

    ArrayList 的缺点如下:

    1. 删除元素的时候,需要做一次元素复制操作。如果要复制的元素很多,那么就会比较耗费性能。
    2. 插入元素的时候,也需要做一次元素复制操作,缺点同上。

    ArrayList 比较适合顺序添加、随机访问的场景。

    8. 为什么 ArrayList 的 elementData 加上 transient 修饰?

    ArrayList 中的数组定义如下:
    privatetransient Object[] elementData;


    再看一下 ArrayList 的定义:
    publicclassArrayList<E> extendsAbstractList<E>
           implementsList<E>, RandomAccess, Cloneable, java.io.Serializable


    可以看到 ArrayList 实现了 Serializable 接口,这意味着 ArrayList 支持序列化。transient 的作用是说不希望 elementData 数组被序列化,重写了 writeObject 实现:
    privatevoidwriteObject(java.io.ObjectOutputStream s)throws java.io.IOException{
       // Write out element count, and any hidden stuff
       int expectedModCount = modCount;
       s.defaultWriteObject();
           // Write out array length
       s.writeInt(elementData.length);
           // Write out all elements in the proper order.
       for (int i=0; i<size; i++)
           s.writeObject(elementData[i]);
       if (modCount != expectedModCount) {
           thrownew ConcurrentModificationException();
       }


    每次序列化时,先调用 defaultWriteObject() 方法序列化 ArrayList 中的非 transient 元素,然后遍历 elementData,只序列化已存入的元素,这样既加快了序列化的速度,又减小了序列化之后的文件大小。

    9. 遍历一个 List 有哪些不同的方式?每种方法的实现原理是什么?Java 中 List 遍历的最佳实践是什么?

    遍历方式有以下几种:

    1. for 循环遍历,基于计数器。在集合外部维护一个计数器,然后依次读取每一个位置的元素,当读取到最后一个元素后停止。
    2. 迭代器遍历,Iterator。Iterator 是面向对象的一个设计模式,目的是屏蔽不同数据集合的特点,统一遍历集合的接口。Java 在 Collections 中支持了 Iterator 模式。
    3. foreach 循环遍历。foreach 内部也是采用了 Iterator 的方式实现,使用时不需要显式声明 Iterator 或计数器。优点是代码简洁,不易出错;缺点是只能做简单的遍历,不能在遍历过程中操作数据集合,例如删除、替换。

    最佳实践:Java Collections 框架中提供了一个 RandomAccess 接口,用来标记 List 实现是否支持 Random Access。
    • 如果一个数据集合实现了该接口,就意味着它支持 Random Access,按位置读取元素的平均时间复杂度为 O(1),如ArrayList。
    • 如果没有实现该接口,表示不支持 Random Access,如LinkedList。

    推荐的做法就是,支持 Random Access 的列表可用 for 循环遍历,否则建议用 Iterator 或 foreach 遍历。
    10. 如何边遍历边移除 Collection 中的元素?

    边遍历边修改 Collection 的唯一正确方式是使用 Iterator.remove() 方法,如下:
    Iterator<Integer> it = list.iterator();
    while(it.hasNext()){
       // do something
       it.remove();

    }


    一种最常见的错误代码如下:
    for(Integer i : list){
       list.remove(i)
    }


    运行以上错误代码会报 ConcurrentModificationException 异常。这是因为当使用 foreach(for(Integer i : list)) 语句时,会自动生成一个iterator 来遍历该 list,但同时该 list 正在被 Iterator.remove() 修改。Java 一般不允许一个线程在遍历 Collection 时另一个线程修改它。

    本站所有文章均由网友分享,仅用于参考学习用,请勿直接转载,如有侵权,请联系网站客服删除相关文章。若由于商用引起版权纠纷,一切责任均由使用者承担
    极客文库 » 10 道 Java 面试题:集合类

    常见问题FAQ

    如果资源链接失效了怎么办?
    本站用户分享的所有资源都有自动备份机制,如果资源链接失效,请联系本站客服QQ:2580505920更新资源地址。
    如果用户分享的资源与描述不符怎么办?
    可以联系客服QQ:2580505920,如果要求合理可以安排退款或者退赞助积分。
    如何分享个人资源获取赞助积分或其他奖励?
    本站用户可以分享自己的资源,但是必须保证资源没有侵权行为。点击个人中心,根据操作填写并上传即可。资源所获收益完全归属上传者,每周可申请提现一次。
    如果您发现了本资源有侵权行为怎么办?
    及时联系客服QQ:2580505920,核实予以删除。

    Leave a Reply

    Hi, 如果你对这款资源有疑问,可以跟我联系哦!

    联系发布者

    Leave a Reply

    Hi, 如果你对这款资源有疑问,可以跟我联系哦!

    联系发布者
    • 101会员总数(位)
    • 3672资源总数(个)
    • 0本周发布(个)
    • 0 今日发布(个)
    • 124稳定运行(天)

    欢迎加入「极客文库」,成为原创作者从这里开始!

    立即加入 了解更多
    成为赞助用户享有更多特权立即升级