JAVA集合串讲

集合与数组区别

  • 长度区别: 数组长度固定; 集合长度可变

  • 内容区别:数组可存储基础类型,也可存储引用类型; 集合只能存储引用类型

  • 存储类型种类:数组只能存储同种类型,集合可存储多种类型

List

ArrayList

底层是Object数组,属于线性结构,随机访问性强(查询快),增删慢,效率高,线程不安全,可存储重复元素(允许多个null元素对象)

遍历方式使用iterator或for循环

LinkedList

底层是双向链表,属于链表结构,随机访问性差(查询慢),增删快,效率低,线程不安全,可存储重复元素(允许多个null元素对象)

遍历方式使用iterator(强烈建议,速度极快;for循环遍历性能极差)

Vector

底层是数组,随机访问性强(查询快),增删慢,效率极低,线程不安全,可存储重复元素(允许多个null元素对象),现在基本不用

常见面试题

ArrayList与LinkedList区别

​ ArrayList是基于索引的数据接口,它的底层是数组。它可以以O(1)时间复杂度对元素进行随机访问。与此对应,LinkedList是以元素链表的形式存储数据,每一个元素都和它的前一个元素和后一个元素链接在一起(node节点),在这种情况下,查找某一个元素的时间复杂度为O(n)。

​ 相对于ArrayList,LinkedList的添加,插入和删除操作速度一般更快,因为当元素添加或插入到集合的任意位置的时候,不需要像数组那样重新计算大小或更新索引。

   LinkedList比ArrayList更占内存,因为LinkedList为每一个节点存储了两个引用,一个指向前一个元素,一个指向下一个元素。
  • ArrayList增删一定比LinkedList慢?

​ 不一定,ArrayList制定初始容量并使用尾插法,可以极大提升性能(甚至超过LinkedList,维护node)

  • ArrayList与LinkedList占用内存空间

    ​ 一般情况下,LinkedList占用空间大,因为每个节点要维护指向前后地址的2个节点,但不是绝对,一旦数据量超过ArrayList默认的临时容量,ArrayList占用内存并不比LinkedList小,因为扩容会浪费近原来数组一半的容量,不过ArrayList底层的数组变量使用transient(不想序列化)修饰,若ArrayList本身需要序列化,这部分多余的空间并不会序列化

    拓展

    以手写ArrayList为例

  package com.tedu;

  /**

   * @Description: 手写ArrayList,实现size,isEmpty,get,add,remove
   * @author: zfh
   * @email: hst1406959716@163.com
   * @date: 2021-06-04 20:01:24
     */
     public class MyArrayList {
      private Object[] elementData;
      private int size;

   public MyArrayList() {
    this(10);
   }

   public MyArrayList(int initialCapacity) {
    if (initialCapacity < 0) {
     throw new IllegalArgumentException("Illegal initialCapacity:" + initialCapacity);
    }
    elementData = new Object[initialCapacity];
  //  size = initialCapacity;
   }

   public int size() {
    return size;
   }

   public boolean isEmpty() {
    return size == 0;
   }

   public void add(Object obj) {
    // 扩容
    if (size == elementData.length) {
     Object[] newElementData = new Object[size * 2 + 1];
     System.arraycopy(elementData, 0, newElementData, 0, elementData.length);
     elementData = newElementData;
    }

    elementData[size] = obj;
    size++;
   }

   public Object get(int index) {
    if (index < 0 || index > size) {
     throw new IndexOutOfBoundsException();
    }
    return elementData[index];
   }public Object remove(int index) {if (index < 0 || index >= size) {throw new IllegalStateException();}
  ​        
  ​        Object oldValue = elementData[index];//要向前挪位子的元素个数int numMoved = size - index - 1;if (numMoved > 0) {System.arraycopy(elementData, index + 1, elementData, index, numMoved);}

  ​        elementData[size - 1] = null;//长度减1
  ​        size--;return oldValue;}

   public static void main(String[] args) {
          MyArrayList list = new MyArrayList(2);System.out.println(list.size());System.out.println(list.isEmpty());

  ​        list.add("小生不才");
  ​        list.add("齐雷");
  ​        list.add("刘昱江");System.out.println(list.size());System.out.println(list.get(2));System.out.println(list.remove(1));System.out.println(list.size());}
  }

CopyOnWriteArrayList

​ cow机制,jdk1.5后引入,写时复制(添加元素时不直接往容器中添加,现将当前容器复制一份,再将元素添加到新容器,最后将原容器的引用指向新容器),有点副本的意味

线程安全,适合读>写

缺点也比较明显,第一是内存占用,可能导致yong gc或full gc;第二是无法保证数据的实时一致性,只能保存数据的最终一致性。

Map

map并未继承Collection(而list,set继承)

HashTable

继承自Dictionary,线程安全(使用synchronized),功能与hashMap类似,效率低,现在基本不用

HashMap

​ HashMap存储的数据结构是一个键值对映射,它根据键的hashcode值存取数据,大多数情况下可以直接定位到它的值,因而具有很快的访问速度,但是遍历的顺序是不确定的。HashMap最多只允许一条记录的键为null,允许多条记录的值为null。HashMap是非线程安全的,任意时刻可以有多个线程同时写HashMap,可能会导致数据不一致。如果需要满足线程安全,可以使用Collections的SynchronizedMap方法使HashMap具备线程安全,或者使用ConcurrentHashMap。jdk1.7采用头插法,1.8采用尾插法.

存储结构

​ HashMap底层数据结构是由数组+链表+红黑树(jdk1.8之后引入的)实现的,从源码可知,HashMap底层有一个非常重要的数组,Node数组即哈希桶数组。Node是HashMap的一个内部类,实现了Map.Entry接口,本质就是一个键值对的映射。HashMap就是使用哈希表来存储的。哈希表为解决冲突,可以采用开放地址法和链地址法。Java中的HashMap采用了链地址法,就是数组加链表的集合。在每个数组的元素上都添加一个链表结构,当数据被hash后,得到数组下标,把数据放在对应下标元素的链表上。

put过程

​ 先判断键值对数组table[i]是否为空或为null,否则执行resize()方法扩容;再根据键值key计算hash值得到插入的数组索引i,如果table[i]==null,直接新建节点添加,如果不为空,就判断table[i]的首个元素的hashcode是否和key的hashcode相同,如果相同直接覆盖value。否则就判断table[i]是否为treeNode,即table[i]是否为红黑树,如果是红黑树,则直接在红黑树中插入键值对。否则就遍历table[i],判断链表的长度是否大于8,大于8的话就把链表转换为红黑树,在红黑树中执行插入操作,否则进行链表的插入操作,遍历过程中若发现key已存在就会直接覆盖value。插入成功后,判断实际存在的键值对数量size是否超过了最大容量,如果超过,进行扩容。

get过程

​ 先前HashMap通过hashcode来存放数据,那么get方法一样要通过hashcode来获取数据。可以看到如果当前table没有数据的话直接返回null,反之通过传进来的hash值找到对应节点(Node)first,如果first的hash值以及Key跟传入的参数匹配就返回对应的value,反之判断是否是红黑树,如果是红黑树则从根节点开始进行匹配如果有对应的数据则返回结果,否则返回Null,如果是链表的话就会循环查询链表,如果当前的节点不匹配的话就会从当前节点获取下一个节点来进行循环匹配,如果有对应的数据则返回结果否则返回Null。

LinkedHashMap

​ LinkedHashMap是HashMap的一个子类,保存了记录的插入顺序,在用iterator遍历时,先得到的记录肯定是先插入的,也可以在构造时带参数,按照访问次序排序。

TreeMap

​ TreeMap实现SortedMap接口,能够把它保存的记录根据键排序,默认是按键值的升序排序,也可以指定排序的比较器,当用iterator遍历TreeMap时,得到的记录是排过序的。如果使用排序的映射,建议使用TreeMap。在使用TreeMap时,key必须实现Comparable接口或者在构造TreeMap时传入自定义的Comparator,否则在运行时抛出ClassCastException异常。

常见面试题

hashTable与hashMap区别

  • hashTable线程安全,效率低;
  • hashMap允许k,v为null,hashTable不允许

拓展

​ ConcurrentHashMap是一个线程安全的HashMap,它的底层数据结构是node+syncronized+CAS+红黑树(jdk1.8)。在JDK1.7采用分段锁(syncronized+segment)锁机制,每一把锁只锁容器其中的一部分数据,这样多线程访问容器里不同数据段的数据,就不会存在锁竞争的问题,提高了并发访问率。JDK1.8之后对分段锁进行了优化,采用syncronized+CAS(即比较并交换,是解决多线程并行情况下使用锁造成性能损耗的一种机制)锁机制。

jdk7

​ ReentrantLock+Segment+HashEntry,一个Segment中包含一个HashEntry, 每个HashEntry又是一个链表结构

Segment分段锁继承ReentrantLock,锁定操作的Segment,其余的segment不受影响,并发度为segment的个数,可通过构造函数指定,数组扩容不会影响其余的segment

get操作无锁

jdk8

syncronized+CAS+node+红黑树,其中node的v和next均用volatile修饰,保证可见性

锁链表的head结点,不影响其他元素的读写,锁的粒度更细,效率更高;扩容时阻塞所有的读写操作,并发扩容

Set

HashSet

​ 底层数据结构采用哈希表实现,元素无序且唯一,线程不安全,效率高,可以存储null元素,元素的唯一性是靠所存储的元素类型是否重写了hashcode()和equals()方法来保证的,如果没有重写这两个方法,则无法保证元素的唯一性。HashSet采用哈希算法保证元素不重复的,底层用map存储数据,默认初始化容量16,加载因子0.75;Object类中的hashCode()的方法是所有子类都会继承的方法,这个方法会用Hash算法算出一个Hash(哈希)码值,HashSet会用Hash码值去和数组长度取模, 模(这个模就是对象要存放在数组中的位置)相同时才会判断数组中的元素和要加入的对象的内容是否相同,如果不同才会添加进去。

LinkedHashSet

​ 底层数据结构采用链表和哈希表共同实现,链表保证了元素的顺序与存储顺序一致,哈希表保证了元素的唯一性。线程不安全,效率高。

TreeSet

​ 底层数据结构采用二叉树来实现,元素唯一并且是已经排好序的;唯一性同样需要重写hashcode()和equals()方法,二叉树结构保证了元素的有序性。根据构造方法不同分为自然排序(无参构造)和比较器排序(有参构造),自然排序要求元素必须实现Compareable接口,并重写里面的CompareTo()方法,元素通过比较返回的int值来判断排序序列,返回0说明两个对象相同,不需要存储;比较器排序需要在TreeSet初始化的时候传入一个实现Comparator接口的比较器对象,或者采用匿名内部类的方式new一个Comparator对象,重写里面的Compare()方法。

常见面试题

List与Set区别

  • list有序,按元素进入的顺序保存对象,可重复,允许多个null元素对象
  • set无序,不可重复,最多允许1个null元素对象

拓展

以手写hashSet为例

package cn.tedu;

import java.util.HashMap;


/**
 * @description:简易版hashSet
 * @author: zfh
 * @version: 1.0
 * @date: 2021/06/07 13:55:02
 **/
public class MyHashSet {
    private HashMap map;
    private static final Object PRESENT = new Object();

    public MyHashSet(){
        map = new HashMap();
    }

    public int size(){
        return map.size();
    }

    public boolean isEmpty(){
        return map.isEmpty();
    }

    public boolean contains(Object o){
        return map.containsKey(o);
    }

    public void clear(){
        map.clear();
    }

    public void add(Object o){
        map.put(o,PRESENT);
    }

    public static void main(String[] args) {
        MyHashSet myHashSet = new MyHashSet();
        System.out.println(myHashSet.isEmpty());
        System.out.println(myHashSet.size());
        System.out.println();

        myHashSet.add("小生不才");
        System.out.println(myHashSet.contains("小生不才"));
        System.out.println(myHashSet.isEmpty());
        System.out.println(myHashSet.size());

        System.out.println();
        myHashSet.clear();
        System.out.println(myHashSet.isEmpty());
        System.out.println(myHashSet.size());
    }
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值