# 技术栈知识点巩固——Java集合


Arraylist与LinkedList区别
  • ArrayList是实现了基于动态数组的数据结构,而LinkedList是基于链表的数据结构;

  • 对于随机访问getsetArrayList要优于LinkedList,因为LinkedList要移动指针;


Collections.sort和Arrays.sort的实现原理
  • Collections.sort:底层调用的还是Arrays.sort
public static <T extends Comparable<? super T>> void sort(List<T> list) {
    list.sort(null);
}
default void sort(Comparator<? super E> c) {
    Object[] a = this.toArray();
    Arrays.sort(a, (Comparator) c);
    ListIterator<E> i = this.listIterator();
    for (Object e : a) {
        i.next();
        i.set((E) e);
    }
}
  • Arrays.sort
public static void sort(int[] a) {
    DualPivotQuicksort.sort(a, 0, a.length - 1, null, 0, 0);
}
  • 底层实现都是TimSort实现的,这是jdk1.7新增的,以前是归并排序。TimSort算法就是找到已经排好序数据的子序列,然后对剩余部分排序,然后合并起来

HashMap原理,java8做了什么改变
  • key value键值对存储方式,数组、链表、红黑树。

  • HashMap使用哈希表进行存储,哈希表为解决冲突,采用开放地址法和链地址法等来解决问题。

  • 使用高位运算、取模运算计算keyhashcode值。

  • 先通过hash方法找到数组中的位置,然后再次hash得到在链表中的位置。

  • 当链表长度超过阈值8时,会将链表转换为红黑树,使HashMap的性能得到进一步提升。

  • 图片来自:https://www.lagou.com/lgeduarticle/18098.html

    img


List 和 Set,Map 的区别
List
  • List:元素可重复,元素有序

  • ArrayList() : 长度可变,查询块,插入删除慢。

  • LinkedList(): 在实现中采用链表数据结构,插入和删除速度快,访问速度慢。

Set
  • Set:元素无重复,元素无序。

  • HashSet:为快速查找设计的Set。存入HashSet的对象必须定义hashCode()。

  • TreeSet: 保存次序的Set, 底层为树结构。使用它可以从Set中提取有序的序列。

  • LinkedHashSet:具有HashSet的查询速度,且内部使用链表维护元素的顺序在使用迭代器遍历Set时,结果会按元素插入的次序显示。

Map
  • Map:键值对存储。
  • HashMapMap基于散列表的实现,插入和查询“键值对”的开销是固定的。可以通过构造器设置容量capacity和负载因子load factor,以调整容器的性能。
  • LinkedHashMap: 类似于HashMap,但是迭代遍历它时,取得“键值对”的顺序是其插入次序,或者是最近最少使用(LRU)的次序。只比HashMap慢一点。而在迭代访问时发而更快,因为它使用链表维护内部次序。
  • TreeMap : 基于红黑树数据结构的实现。
  • WeakHashMap :弱键MapMap中使用的对象也被允许释放: 这是为解决垃圾回收。

poll()方法和 remove()方法的区别?
  • poll从队列头部拿出一个元素。
  • poll() 在获取元素失败的时候会返回空,但是 remove() 失败的时候会抛出异常。

HashMap,HashTable,ConcurrentHashMap的共同点和区别
HashMap
  • 数组+链表+红黑树,存储null键和null值,线程不安全。
  • 初始size16,扩容:newsize = oldsize*2
  • 每次扩容时,原来数组中的元素依次重新计算存放位置,并重新插入。
  • Map中元素总数超过Entry数组的75%,触发扩容操作。
HashTable
  • 键与值成对存在,键是唯一的,不能重复。线程安全。
  • 存储结构中使用了数组、单链表,其中单链表是用来处理哈希冲突时用的。
  • HashTable处理冲突时采用链表,HashMap处理冲突采用链表+红黑树。
ConcurrentHashMap
  • 分段的数组+链表实现,线程安全。
  • 把整个Map分为NSegment,可以提供相同的线程安全。读操作不加锁,HashEntryvalue变量是 volatile的,也能保证读取到最新的值。
  • Hashtable的synchronized是针对整张Hash表的,即每次锁住整张表让线程独占。
  • 段内元素超过该段对应Entry数组长度的75%触发扩容,不会对整个Map进行扩容。

写一段代码在遍历 ArrayList 时移除一个元素
  • 使用迭代器
@Test
public void test(){
    List<String> list = new ArrayList<>();
    list.add("张三");
    list.add("张三");
    list.add("李四");

    Iterator<String> iterator = list.iterator();
    while (iterator.hasNext()){
        Object next = iterator.next();
        if("李四".equals(next)){
            iterator.remove();
        }
    }

    logger.info(JSON.toJSONString(list));

}
  • 使用removeIf
@Test
public void testOne(){
    List<String> list = new ArrayList<>();
    list.add("张三");
    list.add("张三");
    list.add("李四");
    list.removeIf("李四"::equals);
    logger.info(JSON.toJSONString(list));
}

TreeMap底层
  • TreeMap 的实现就是红黑树数据结构。
public class TreeMap<K,V>
    extends AbstractMap<K,V>
    implements NavigableMap<K,V>, Cloneable, java.io.Serializable
{
  	//自定义的比较器:
    private final Comparator<? super K> comparator;
	//红黑树的根节点:
    private transient Entry<K,V> root;
	//集合元素数量:
    private transient int size = 0;
    //对TreeMap操作的数量:
    private transient int modCount = 0;
    //无参构造方法:comparator属性置为null
    //代表使用key的自然顺序来维持TreeMap的顺序,这里要求key必须实现Comparable接口
    public TreeMap() {
        comparator = null;
    }
	//带有比较器的构造方法:初始化comparator属性;
    public TreeMap(Comparator<? super K> comparator) {
        this.comparator = comparator;
    }
 	//带有map的构造方法:
    //同样比较器comparator为空,使用key的自然顺序排序
    public TreeMap(Map<? extends K, ? extends V> m) {
        comparator = null;
        putAll(m);
    }
    //带有SortedMap的构造方法:
    //根据SortedMap的比较器来来维持TreeMap的顺序
    public TreeMap(SortedMap<K, ? extends V> m) {
        comparator = m.comparator();
        try {
            buildFromSorted(m.size(), m.entrySet().iterator(), null, null);
        } catch (java.io.IOException cannotHappen) {
        } catch (ClassNotFoundException cannotHappen) {
        }
    }
}

HashMap 的扩容过程
  • HashMap默认容量为16,加载因子,默认是0.75,阈值=容量*加载因子。默认12,当元素数量超过阈值时便会触发扩容。
  • 一般情况下,当元素数量超过阈值时便会触发扩容。每次扩容的容量都是之前容量的2倍。
  • 空参数的构造函数:实例化的HashMap默认内部数组是null,没有实例化。第一次调用put方法时,则会开始第一次初始化扩容,长度为16。
  • 有参构造函数:用于指定容量。会根据指定的正整数找到不小于指定容量的2的幂数,将这个数设置赋值给阈值。第一次调用put方法时,会将阈值赋值给容量。

HashSet是如何保证不重复的
  • 使用HashMapput方法,根据键的hash值判断,如果hash只重复则不存,否则放入Map
private transient HashMap<E,Object> map;
private static final Object PRESENT = new Object();

public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}

LinkedHashMap的应用,底层,原理
  • Map中的每个Entry是有序的。
  • 通过双向链表维护节点的顺序。
  • 例如下图所示的key value,图片来自:https://zhuanlan.zhihu.com/p/34490361

img

  • LinkedHashMap
public class LinkedHashMap<K,V>
    extends HashMap<K,V>
    implements Map<K,V>
{}

哪些集合类是线程安全的?哪些不安全?
线程安全线程不安全
Vector:方法前面都加了synchronized关键字HashMap
HashTable:使用了synchronized关键字Arraylist
ConcurrentHashMap:使用锁分段技术确保线性安全LinkedList
Stack:继承于VectorHashSet
TreeSet
TreeMap

ArrayList 和 Vector 的区别是什么?
  • Vector是线程安全的,ArrayList不是线程安全的。
  • ArrayList在底层数组不够用时在原来的基础上扩展0.5倍,Vector是扩展1倍。

Collection与Collections的区别是什么?
  • Collection是一个接口,里面有各种集合的操作接口
 interface Collection<E> extends Iterable<E> {
 
 	// ...
 }
  • Collections是一个工具类

如何决定使用 HashMap 还是TreeMap
  • HashMap不支持排序,TreeMap默认是按照key值升序排序的可以指定排序的比较器。
  • HashMap大多数情况下有更好的性能,尤其是读数据。
  • HashMap基于数组+链表实现,TreeMap基于红黑树实现。

如何实现数组和 List之间的转换?
@Test
public void test3() {
    int[] num = {1, 2, 3, 54, 4};
    List<int[]> ints = Arrays.asList(num);
    logger.info(JSON.toJSONString(ints));

    List<String> list = new ArrayList<>();
    list.add("张三");
    list.add("张三");
    list.add("李四");
    String[] objects = list.toArray(new String[0]);
    logger.info(JSON.toJSONString(objects));
}

迭代器 Iterator 是什么?怎么用,有什么特点?
  • Iterator 是遍历集合的工具,即我们通常通过Iterator迭代器来遍历集合

  • Collection接口中有接口Iterator<E> iterator();实现Collection接口的类通过iterator遍历集合中的元素。

  • Iterator只能单向移动。

  • Iterator.remove()是唯一安全的方式来在迭代过程中修改集合;


Iterator 和 ListIterator 有什么区别?
  • ListIteratorCollection框架中的一个接口;是用于扩展Iterator接口的。使用ListIterator,可以向前和向后遍历集合的元素。还可以添加、删除或修改集合中的任何元素。
ListIterator<String> iterator = list.listIterator();

怎么确保一个集合不能被修改?
  • 使用Collections中的API
List<String> list = new ArrayList<>();
List<String> unmodifiableList = Collections.unmodifiableList(list);
  • 使用Arrays.asList创建的集合
String[] str = {"one", "two"};
List<String> stringList = Arrays.asList(str);
logger.info(JSON.toJSONString(stringList));

快速失败(fail-fast)和安全失败(fail-safe)的区别是什么?

在这里插入图片描述

  • 当多个线程对Collection进行操作时,若其中某一个线程通过iterator去遍历集合时,该集合的内容被其他线程所改变;则会抛出ConcurrentModificationException异常。

  • Iterator的安全失败是基于对底层集合做拷贝,它不受源集合上修改的影响。

  • java.util包下面的所有的集合类都是快速失败的,而java.util.concurrent包下面的所有的类都是安全失败的。快速失败的迭代器会抛出ConcurrentModificationException异常,而安全失败的迭代器永远不会抛出这样的异常。

  • 可以使用``JUC`下面相关的类。


Java 中的 LinkedList是单向链表还是双向链表?
  • LinkedList 是基于双向链表而实现的。
public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
    transient int size = 0;

    /**
     * Pointer to first node.
     * Invariant: (first == null && last == null) ||
     *            (first.prev == null && first.item != null)
     */
    transient Node<E> first;

    /**
     * Pointer to last node.
     * Invariant: (first == null && last == null) ||
     *            (last.next == null && last.item != null)
     */
    transient Node<E> last;

    /**
     * Constructs an empty list.
     */
    public LinkedList() {
    }
    
}

说一说ArrayList 的扩容机制吧
  • ArrayList扩容发生在add()方法调用的时候, 调用ensureCapacityInternal()来扩容的,通过方法calculateCapacity(elementData, minCapacity)获取需要扩容的长度:
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}
  • calculateCapacity获取扩容长度
private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
}
  • ensureExplicitCapacity方法可以判断是否需要扩容:
  • ArrayList扩容的关键方法grow()获取到ArrayListelementData数组的内存空间长度 扩容至原来的1.5倍。
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);
}
  • 调用Arrays.copyOf方法将elementData数组指向新的内存空间时newCapacity的连续空间从此方法中我们可以清晰的看出其实ArrayList扩容的本质就是计算出新的扩容数组的size后实例化,并将原有数组内容复制到新数组中去。

ConcurrenHashMap 原理

concurrenthashmap-principle


ArrayList的默认大小
  • 默认大小是10;
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    /**
     * Default initial capacity.
     */
    private static final int DEFAULT_CAPACITY = 10;
}

我们如何对一组对象进行排序
  • Arrays.sort():数组排序。
  • Collections.sort(list):集合排序。
  • new TreeMap():定义比较器对Map中的元素排序。
  • 对象实现Comparable接口,重写compareTo方法;
public class Shop implements Comparable<Shop> {

    private String shopName;
    private String shopCode;
    
    @Override
    public int compareTo(Shop shop) {
        int length = shop.getShopName().length();
        return this.shopName.length() - length;
    }

}

当一个集合被作为参数传递给一个函数时,如何才可以确保函数不能修改它?
  • Collections.unmodifiableCollection(list)创建的集合不可修改,修改时后会抛出异常。
  • 使用Arrays.asList创建的集合。

HashSet 的实现原理
  • 使用mapput操作,当keyhash值和equals相等的时候就往里放了。
  • HashSet中的元素都存放在HashMapkey上面,而value中的值都是统一的一个private static final Object PRESENT = new Object()HashSetHashMap一样,都是一个存放链表的数组。

Array 和 ArrayList 有何区别
  • Array的空间大小是固定的。
  • ArrayList的空间是动态增长的。
  • Array数组可以包含基本类型和对象类型。
  • ArrayList却只能包含对象类型。

红黑树的特点

img

  • 每个节点或者是黑色,或者是红色。
  • 根节点是黑色。
  • 每个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!
  • 如果一个节点是红色的,则它的子节点必须是黑色的。
  • 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。

HashSet和TreeSet区别
  • 最大的区别就是里面元素TreeSet可以实现排序。
@Test
public void treeSetTest() {
    TreeSet<String> set = new TreeSet<String>((Comparator) (o1, o2) -> {
        int length1 = String.valueOf(o1).length();
        int length2 = String.valueOf(o2).length();
        if (length1 == length2) return 1;
        return Integer.compare(length1, length2);
    });
    set.add("zhangsan");
    set.add("test");
    set.add("lisi");
    set.add("1234");
    set.add("wangwu");
    logger.info(String.valueOf(set));
}

Set里的元素去重原理
  • add里面是mapput操作,通过计算hash值和内容是否相等进行add操作。

  • Set 里的元素是不能重复的,元素重复与否是使用 equals()方法进行判断的。
    equals()==方法决定引用值是否指向同一对象 。hash相等不一定内容相等,所以使用Equals进行判断。


ArrayList、LinkedList的存储性能和特性
  • ArrayList 采用的是数组形式来保存对象的,这种方式将对象放在连续的位置中,所以最大的缺点就是插入删除时非常麻烦。

  • LinkedList 采用的将对象存放在独立的空间中,而且在每个空间中还保存下一个链接的索引。增加删除快,查找慢。

  • Vector使用了sychronized方法(线程安全),所以在性能上比ArrayList要差些.

  • LinkedList使用双向链表方式存储数据,按序号索引数据需要前向或后向遍历数据,所以索引数据慢,是插入数据时只需要记录前后项即可,所以插入的速度快。


ArrayList集合加入1万条数据,应该怎么提高效率
  • 指定初始化集合的大小(这也是在一定的数量内,超过一定范围,性能会下降)。
  List<Integer> list = new ArrayList<>(1000000);

ArrayList 和 HashMap 的默认大小
  • ArrayList:默认是 10;
  • HashMap :默认是 16;

有没有有顺序的Map实现类,如果有,他们是怎么保证有序的
  • TreeMap见上面的实现方法。

HashMap是怎么解决哈希冲突的
  • Java中的HashMap采用的hash冲突解决方案就是单独链表法,也就是在hash表节点使用链表存储hash值相同的值。

HashMap中的内部属性
  • 负载因子:final loadFactor (默认为0.75f)
  • 实际容量: int threshold = loadFactor * tab.length
  • 树化阈值:int TREEIFY_THRESHOLD = 8;
  • 解除树化阈值: int UNTREEIFY_THRESHOLD = 6;
  • HashMap也采用了懒加载策略,第一次put时初始化哈希表
  • 树化逻辑:索引下标对应的链表长度达到阈值8并且当前哈希表长度达到64才不会树化,否则只是-调用resize()方法进行哈希表扩容。
集合补充知识点
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

全栈程序员

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

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

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

打赏作者

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

抵扣说明:

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

余额充值