JAVA 学习 面试(八)集合类

集合类

JAVA 学习 面试(八)集合类_第1张图片

集合(Collection)

1、 List列表 : 有序 可重复

1、ArrayList : 数组列表 ,内部是通过Array实现,对数据列表进行插入、删除操作时都需要对数组进行拷贝并重排序,因此在知道存储数据量时,尽量初始化初始容量,提升性能 。
2、LinkedList : 双向链表  每个元素都有指向前后元素的指针,顺序读取的效率较高,随机读取的效率较低 
3、Vector : 向量 , 线程安全的列表,与ArrayList 一样也是通过数组实现的
4、Stack : 栈 , 后进先出 LIFO , 继承自Vector,也是用数组,线程安全的栈
类型 底层实现 线程安全 扩容方式 特点
ArrayList 数组 初始容量是 10,扩容因子是 0.5 查询快,增删慢
LinkedList 链表 没有扩容的机制 查询慢,增删快
Vector 数组 默认初始容量为10,扩容因子是 1 查询快,增删慢

2、Queue队列:有序 可重复

1、ArrayDeque :  数组实现的的双端队列, 可以在队列两端插入和删除元素 
2、LinkedList : 也属于双端队列 
3、PriorityQueue : 优先队列  ,数组实现的二叉树, 完全二叉树实现的小顶堆(可改变比较方法)

3、Set集合: 无序 不重复

1、HashSet 基于哈希实现的集合, 链表形式
2、LinkedHashSet 
3、TreeSet 红黑树结构 
类型 底层实现 线程安全 扩容方式 特点
HashSet 基于HashMap实现 添加元素时,table数组扩容为16,加载因子为0.75 无序
LinkedHashSet 基于LinkedHashMap 初始容量为16,临界值为12,以后再次扩容,扩容2倍 有序
TreeSet 基于TreeMap实现 容量翻倍 有序
集合(Collection)和数组的区别
  • 2、数组的长度是固定的,集合长度是可以改变的,提供很多成员方法。
  • 3、数组的存放的类型只能是一种(基本类型/引用类型),集合存放的类型可以不是一种(不加泛型时添加的类型是Object)。(当将元素放入集合时,它们会被转换成Object类型,之后在访问集合中的元素时需要进行强制类型转换。这种设计虽然方便,但也带来了类型不安全的隐患,以及类型转换的性能损失。)
  • 4、数组是java语言中内置的数据类型,是线性排列的,执行效率或者类型检查都是最快的。
工具
  • 遍历集合:Iterator 和 Enumeration
  • 操作集合:Arrays 和 Collections
Map
1、HashMap 哈希映射 无序 , key 和 value 都可以为null 
2、TreeMap 红黑树实现的, 可排序, 红黑树是一种自平衡二叉查找树
3、LinkedHashMap  链表映射 ,继承于HashMap,又实现了双向链表的特性 ,保留了元素插入顺序 
类型 底层实现 线程安全 扩容方式 特点
HashMap 数组+红黑树 初始容量是 16,2倍扩容 无序集合
LinkedHashMap 基于HashMap,并自己维持了一个双向链表,按照插入顺序进行访问,实现了LRU算法 初始容量是 16,2倍扩容 有序集合
CocurrentHashMap Segments数组+ReentrantHashMap(作为互斥锁来控制并发访问)+链表,采用分段锁保证安全 链表元素超8个,数组大小超64,转红黑树 无序集合,性能比HashTable好
HashTable 数组+链表 初始大小为11,扩容为2n+1 无序集合
TreeMap 基于红黑树 2倍 有序集合
  • List:有序、可重复。
  • Set:无序、不可重复的集合。重复元素会覆盖掉。
  • Map:键值对,键唯一、值不唯一。Map 集合中存储的是键值对,键不能重复,值可以重复。
LinkedHashMap

LinkedHashMap继承自 HashMap,在 HashMap 基础上,通过维护一条双向链表,解决了 HashMap 不能随时保持遍历顺序和插入顺序一致的问题。在一些场景下,该特性很有用,比如缓存。

JAVA 学习 面试(八)集合类_第2张图片

LinkedHashMap实现LRU

accessOrder用于决定具体的迭代顺序:

当accessOrder标志位为true时,put和get方法均有调用recordAccess方法,将当前访问的Entry(put进来的Entry或get出来的Entry)移到双向链表的尾部,双向链表中的元素按照访问的先后顺序排列。

当标志位accessOrder的值为false时,只有put方法会调用recordAccess,即每次put到LinkedHashMap中的Entry都放在双向链表的尾部,按照Entry插入LinkedHashMap到中的先后顺序排序

public class LRU<K,V> extends LinkedHashMap<K, V> implements Map<K, V>{
    private static final long serialVersionUID = 1L;
    public LRU(int initialCapacity,
             float loadFactor,
                        boolean accessOrder) {
        super(initialCapacity, loadFactor, accessOrder);
    }
 
    /** 
     * @description 重写LinkedHashMap中的removeEldestEntry方法,当LRU中元素多余6个时,
     *              删除最不经常使用的元素 **/
    @Override
    protected boolean removeEldestEntry(java.util.Map.Entry<K, V> eldest) {
        // TODO Auto-generated method stub
        if(size() > 6){
            return true;
        }
        return false;
    }
    public static void main(String[] args) {
        LRU<Character, Integer> lru = new LRU<Character, Integer>(
                16, 0.75f, true);
        String s = "abcdefghijkl";
        for (int i = 0; i < s.length(); i++) {
            lru.put(s.charAt(i), i);
        }
        System.out.println("LRU中key为h的Entry的值为: " + lru.get('h'));
        System.out.println("LRU的大小 :" + lru.size());
        System.out.println("LRU :" + lru);
    }
}
hashMap和LinkedHashMap区别
  • 插入顺序:HashMap不保证映射的顺序,而LinkedHashMap会根据元素插入的顺序维护一个双向链表,因此保证了映射的顺序,可以通过get操作访问元素的插入顺序。
  • 迭代顺序:LinkedHashMap迭代元素的顺序是插入顺序,而HashMap的迭代顺序是随机的。
  • 性能:由于LinkedHashMap在底层额外维护了一个双向链表,因此在插入或删除元素时需要更多的操作,因此LinkedHashMap的性能通常比HashMap要低,内存占用通常比HashMap要高一些。
hashMap和TreeMap区别
  • 插入顺序:HashMap不保证映射的顺序,而TreeMap会根据元素的键值进行排序,因此保证了映射的顺序。
  • 元素访问时间复杂度:HashMap的元素访问时间复杂度是常数级别的,即O(1),而TreeMap的元素访问时间复杂度是基于红黑树的复杂度,通常是O(log(n))。
  • 键的类型:HashMap可以使用任何类型的对象作为键,只要它们能正确的实现hashCode()和equals()方法,而TreeMap的键必须实现Comparable接口或提供自定义的Comparator比较器来进行比较。
  • 内存占用:由于TreeMap需要维护红黑树的结构,因此它的内存占用相对较高。而HashMap在元素较少时,占用内存较小。
HashMap原理
  • HashMap在Jdk1.8以后是基于数组+链表+红黑树来实现的,特点是,key不能重复,可以为null,线程不安全

  • HashMap的扩容机制:

HashMap的默认容量为16,默认的负载因子为0.75,当HashMap中元素个数超过容量乘以负载因子的个数时,就创建一个大小为前一次两倍的新数组,再将原来数组中的数据复制到新数组中。当数组长度到达64且链表长度大于8时,链表转为红黑树

  • HashMap存取原理:

(1)计算key的hash值,然后进行二次hash,根据二次hash结果找到对应的索引位置

(2)如果这个位置有值,先进性equals比较,若结果为true则取代该元素,若结果为false,就使用高低位平移法将节点插入链表

  • 为什么不一开始就使用红黑树?

​ 因为直接采用红黑树的话每次加入元素需要进行平衡,而在超过8时再旋转变为红黑树可以达成平衡,因为大部分哈希槽的元素个数正态分布在8个左右,所以此时变为红黑树也满足了查找的效率。

- 想要线程安全的哈希表
(1)使用ConcurrentHashMap
(2)使用HashTable
(3)Collections.synchronizedHashMap()方法
## hash表:
构造:① 直接定址法;②平方取中法;③折叠法;④除留取余法
冲突解决:① 开放定址法(线性探测)② 链地址法
HashTable与HashMap的区别
  • (1)HashTable的每个方法都用synchronized修饰,因此是线程安全的,但同时读写效率很低

  • (2)HashTable的Key不允许为null

  • (3)HashTable只对key进行一次hash,HashMap进行了两次Hash

  • (4)HashTable底层使用的数组加链表

ConcurrenHashMap与HashTable的区别

ConcurrentHashMap性能更高,它基于分段锁+CAS 保证线程安全,分段锁基于 synchronized 实现,它仅仅锁住某个数组的某个槽位,而不是整个数组

  1. ConcurrentHashMap 没有大量使用 synchronsize 这种重量级锁。而是在一些关键位置使用乐观锁(CAS), 线程可以无阻塞的运行。
  2. ConcurrentHashMap读方法没有加锁
  3. ConcurrentHashMap扩容时老数据的转移是并发执行的,这样扩容的效率更高。
ArrayList和LinkedList的区别

ArratList的底层使用动态数组,默认容量为10,当元素数量到达容量时,生成一个新的数组,大小为前一次的1.5倍,然后将原来的数组copy过来;

因为数组有索引,所以ArrayList查找数据更快,但是添加数据效率更低

LinkedList的底层使用链表,在内存中是离散的,没有扩容机制;LinkedList在查找数据时需要从头遍历,所以查找慢,但是添加数据效率更高

如何保证ArrayList的线程安全?
(1)使用collentions.synchronizedList()方法为ArrayList加锁
(2)使用Vector,Vector底层与Arraylist相同,但是每个方法都由synchronized修饰,速度很慢
在Queue接口中, poll() 和 remove() 方法都用于从队列中移除并返回队头的元素。
如果队列为空,即没有元素可供移除时, pol0 方法会返回null。它是一个安全的方法不会抛出异常。
remove()在没有元素可供移除时,会抛出NoSuchElementException 异常。
怎么确保集合不可更改?

Java 集合框架提供了一些不可变集合类,如不可变列表(ImmutableList)、不可变集合(ImmutableSet)和不可变映射(ImmutableMap)。

List<String> list = ImmutableList.of("a", "b", "c");
Set<String> set = ImmutableSet.of("x", "y", "z");
Map<String, Integer> map = ImmutableMap.of("a", 1, "b", 2, "c", 3);
List<String> list2 = ImmutableList.<String>builder().addAll(list1).add("d").build();

你可能感兴趣的:(java,学习,面试)