常用容器

常用容器

List

ArrayList
  • ArrayList是实现了List接口的,可调整大小的数组。
  • ArrayList内部是按照顺序进行元素的维护的,即不断向数组队列的尾部追加数据。
  • ArrayList允许添加空值null和重复的值。
  • ArrayList可以有泛型的表示,但是泛型必须是除8大基本类型以外的引用类型。如果想添加基本类型的数据,就必须使用其对应的包装类型,例如整型需要使用Integer,布尔类型需要使用Boolean等等。
  • 由于ArrayList内部的方法不是同步的,因此当多线程访问一个ArrayList对象时,需要手动对同步做以控制。
  • 关于ArrayList的初始化
    • 调用new ArrayList()无参构造方法时,会创建一个容量为0,大小为0的数组。但是一旦向ArrayList中添加一个元素后,ArrayList的容量会被设置为默认的容量10。当元素个数超过当前容量时,ArrayList需要扩容,扩容时的算法时按照当前容量大小计算出1.5倍的容量值,然后通过数组拷贝,将当前所有元素复制到一个新的容量的数组中,并将这个数组赋值给原来的数组引用。
    • 调用new ArrayList(int initialCapacity)构造方法时,会直接将容器的容量设置为参数值initialCapacity,而后如果容器中元素的数量超过initialCapacity时,将会按照上述的扩容方案进行1.5倍的扩容。
    • 调用new ArrayList(Collection c)构造方法时,会将入参的集合进行拷贝赋值给ArrayList。
  • 在实际开发中,如果能大致估算ArrayList元素的数量,最好在创建ArrayList对象的时候就指定容器的大小,这样就可以避免因容器容量不足而引起扩容这样不必要的性能消耗。
LinkedList
  • LinkedList是链表,并且是双向链表,那么意味着LinkedList的遍历可以从容器的任意方向开始。
  • LinkedList没有初始化容器容量大小,不存在扩容机制。
  • LinkedList中每个元素是以其内部类Node来表示的。item为当前节点存储的内容,next为当前节点后一节点的内存位置信息,prev为当前节点前一节点的内存位置信息。
private static class Node<E> {
    E item;
    Node<E> next;
    Node<E> prev;

    Node(Node<E> prev, E element, Node<E> next) {
        this.item = element;
        this.next = next;
        this.prev = prev;
    }
}
  • 需要快速插入或删除元素时,应该使用LinkedList。因为在插入或删除元素的过程中,在查找要插入或删除的位置索引的时,LinkedList会通过二分法判断索引位置位于整个链表的前半段还是后半段,从而减少一半的遍历工作量,因此效率会比ArrayList高。
  • 在调用get方法随机访问时,LinkedList由于依然需要通过二分法定位到元素的位置,因此会比ArrayList的直接访问索引位置元素慢。
Vector
  • Vector是向量队列,它实现了List,RandomAccess,Cloneable,Serializable接口,没有实现Collection集合接口,因此它不属于集合。
  • Vector类似于ArayList,是个动态数组,底层都由数组实现。
  • 与ArrayList相比,它是线程安全的,在它内部实现中,方法都已经被synchronized关键字修饰。
  • 关于扩容,当元素个数大于当前容器的容量时,扩容遵循翻倍的方案,即假如当前为默认容量10,当添加第11个元素时,就会进行扩容,扩充后容器容量为20。

Set

Set是不包含重复元素的集合。Set接口继承了Collection接口,因此它包含Collection接口全部的对集合的操作方法。

HashSet
  • HashSet底层实现为HashMap,使用添加到Set的元素作为Map的key,因此实现了元素的不重复。下面是HashSet的构造方法及元素添加方法源码。
private transient HashMap<E,Object> map;

// 定义一个虚拟的值作为每个添加到Map中的value值
private static final Object PRESENT = new Object();

// 使用HashMap作为HashSet的实现
public HashSet() {
    map = new HashMap<>();
}

// 添加时,将添加的元素作为key值放进HashMap,以确保元素不会重复
public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}
  • HashSet没有重写equals方法,因此如果需要比较其中的key值元素,则需要手动重写equals方法和hashcode方法。
  • HashSet由于底层是HashMap实现,因此HashSet的扩容机制与HashMap一致,初始化容量16,扩容因子0.75。
LinkedHashSet
  • LinkedHashSet是HashSet的子类,它继承自HashSet,因此在HashSet的基础上新增了双向链表的结构。
TreeSet
  • TreeSet是有序的,并且没有重复元素容器。
  • 对于Java中的基本类型及常用的字符串类型的数据,TreeSet都可以进行排序。对于自定义的数据类型,必须要实现Comparable接口,并重写compareTo方法,这样就可以进行比较排序了。
public class Demo2 {

    public static void main(String[] args) {
        Person p1=new Person("a",22);
        Person p2=new Person("b",21);
        Person p3=new Person("c",35);
        Person p4=new Person("d",12);

        Set<Person> personSet=new TreeSet<>();
        personSet.add(p1);
        personSet.add(p2);
        personSet.add(p3);
        personSet.add(p4);

        System.out.println(personSet);
    }
}

class Person implements Comparable<Person>{
    private String name;
    private Integer age;

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public Integer getAge() {
        return age;
    }

    @Override
    public int compareTo(Person o) {
        return age.compareTo(o.getAge());//按照年龄进行比较
    }

    @Override
    public String toString() {
        return "Person{name='" + name + ", age=" + age + '}';
    }
}

  • 由于Set的底层实现是HashMap和TreeMap,因此它们都不是线程安全的,如果有多线程访问操作的情况,需要手动在外部进行同步控制。

Map

HashMap
  • 关于扩容,HashMap的扩容需要依据容量与扩容因子进行计算,默认容量为16,默认扩容因子为0.75,因此当容器中元素个数大于16*0.75,即大于12时触发扩容。扩容后的容量为当前容量的2倍。
  • HashMap不是线程安全的,因此当多线程访问HashMap对象时,需要手动进行同步控制。也可以在创建HashMap对象时,使用Collections.synchronizedMap方法将HashMap对象转化成一个线程安全的Map对象。
  • HashMap的key和value都可以为null。
    • 关于HashMap的存储结构,可以参考以下示例图:
  • 常用容器_第1张图片
LinkedHashMap

LinkedHashMap是HashMap的子类,它继承自HashMap,因此在HashMap的基础上新增了双向链表的结构。

Hashtable
  • Hashtable是线程安全的,因为在它其中的实现方法声明上都被synchronized关键字所修饰。
  • Hashtable不可以使用null作为value值,会抛出空指针异常。
  • Hashtable默认初始化容器大小为11,扩容因子为0.75。

关于Hashtable,Collections.synchronizedMap,ConcurrentHashMap对于同步的实现比较

  • Hashtable是在其内部所有方法上添加了synchronized修饰,将整个集合对象锁住。
  • Collections.synchronizedMap是将传入的线程不安全的Map对象,转换成线程安全的Map对象,其内部实现是使用了一个新的Map对象,并且将Map的诸如get,put,size等等操作方法使用synchronized修饰,也是锁住的整个对象。
  • ConcurrentHashMap内部使用的是分段锁机制,众所周知,HashMap底层是采用数组存储数据,ConcurrentHashMap就是将数组分成若干段,对于每一段数据都是用Segment数据结构存储,Segment是可重入锁ReentrantLock的实现,每次访问该段Segment时,只会使用锁的lock方法对该段Segment进行同步锁定,如果有多线程访问其他Segment也不会出现线程安全问题。ConcurrentHashMap默认初始大小与HashMap一样为16,因此可以并发16个线程进行操作,效率显著提升。
  • ConcurrentHashMap与另外两种相比,效率更高。

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