Collection概念 - 容器
有序集合
List是有序的Collection 实现类ArrayList Vector LinkedList
外部做同步操作
List list = Collections.synchronizedList(new LinkedList(...));
ArrayList
- 基于数组,在数组中搜索和读取数据是很快的。因此 ArrayList 获取数据的时间复杂度是O(1);
- 但是添加、删除时该元素后面的所有元素都要移动,所以添加/删除数据效率不高;
- 另外其实还是有容量的,每次达到阈值需要扩容,这个操作比较影响效率。
LinkedList
- 基于双端链表,添加/删除元素只会影响周围的两个节点,开销很低;
- 只能顺序遍历,无法按照索引获得元素,因此查询效率不高;
- 没有固定容量,不需要扩容;
- 需要更多的内存,如文章开头图片所示 LinkedList 每个节点中需要多存储前后节点的信息,占用空间更多些。
-
ArrayList
原理: 底层是基于数组来实现容量大小动态变化的 使用了System.arrayCopy()数组拷贝
// 构造一个空列表的初始容量10。 ArrayList(Collection<? extends E> c) // 构造一个包含指定集合的元素的列表,它们的顺序返回的集合的迭代器。 ArrayList(int initialCapacity) // 构造一个与指定初始容量的空列表。
// 默认大小 private static final int DEFAULT_CAPACITY = 10; // 底层是Object数组,ArrayList比泛型出现早 transient Object[] elementData; // 实际有多少个元素 private int size; // elementData.length 为集合容量 // 继承父类AbstractList 记录操作数 主要使用是在 Iterator,是防止在迭代的过程中集合被修改 protected transient int modCount = 0; // 有参构造 private static final Object[] EMPTY_ELEMENTDATA = {}; // 空参构造 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 第一次添加元素时容量扩大至 10 的。 public ArrayList() {this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;} public ArrayList(int initialCapacity) { if (initialCapacity > 0) {this.elementData = new Object[initialCapacity];} else if (initialCapacity == 0) {this.elementData = EMPTY_ELEMENTDATA;} else {throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);}//不合法的参数异常 } public ArrayList(Collection<? extends E> c) { Object[] a = c.toArray(); if ((size = a.length) != 0) {//集合容量 if (c.getClass() == ArrayList.class) {//判断传入集合是不是ArrayList elementData = a; } else {//不是ArrayList就复制 elementData = Arrays.copyOf(a, size, Object[].class); } } else { elementData = EMPTY_ELEMENTDATA; } }
public boolean add(E e) { modCount++; add(e, elementData, size);//private void add(E e, Object[] elementData, int s) return true; } private void add(E e, Object[] elementData, int s) { if (s == elementData.length) elementData = grow();//private Object[] grow() elementData[s] = e; size = s + 1; } private Object[] grow() { return grow(size + 1);//private Object[] grow(int minCapacity) } private Object[] grow(int minCapacity) { return elementData = Arrays.copyOf(elementData, newCapacity(minCapacity));// 扩容 } private int newCapacity(int minCapacity) { int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity <= 0) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) return Math.max(DEFAULT_CAPACITY, minCapacity);// 扩容 if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return minCapacity; } return (newCapacity - MAX_ARRAY_SIZE <= 0) ? newCapacity : hugeCapacity(minCapacity); }
2.Vector 与 ArrayList 原理相同 只是加上 synchronized
区别 : Vector线程安全 ArrayList线程不安全
3.LinkedList 线程不安全
原理 : 双向链表实现
public LinkedList() {} public LinkedList(Collection<? extends E> c) { this(); addAll(c); }
transient int size = 0; transient Node<E> first;//头节点 transient Node<E> last;//尾节点
// 内部类 双向链表 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; } }
// 迭代器 线程不安全 (并发修改异常,与ArrayList类似) ListItr(int index) { next = (index == size) ? null : node(index); nextIndex = index; } //倒序迭代器 private class DescendingIterator implements Iterator<E> { private final ListItr itr = new ListItr(size()); public boolean hasNext() { return itr.hasPrevious(); } public E next() { return itr.previous(); } public void remove() { itr.remove(); } }
有序不重复
1.TreeSet
原理 : 二叉树 , 对于新添加的对象按照指定的顺序排序 , 每添加一个对象都会排序 , 并插入二叉树指定位置
TreeSet是一个有序的集合,它的作用是提供有序的Set集合。它继承了AbstractSet抽象类,实现了NavigableSet,Cloneable,Serializable接口。
TreeSet是基于TreeMap实现的,TreeSet的元素支持2种排序方式:自然排序或者根据提供的Comparator进行排序。
Integer和String等基础对象 , TreeSet默认排序存储 , 自定义类型必须实现Comparable接口 , 重写compareTo方法 , 升序 this - 对象 == -1 降序 this - 对象 == 1
TreeSet(NavigableMap<E,Object> m) { this.m = m; } public TreeSet() { this(new TreeMap<>()); } public TreeSet(Comparator<? super E> comparator) { this(new TreeMap<>(comparator)); } public TreeSet(Collection<? extends E> c) { this(); addAll(c); } public TreeSet(SortedSet<E> s) { this(s.comparator()); addAll(s); }
public class Person implements Comparable<Person> { private String name; private int age; ... public int compareTo(Person o) { return 0; //当compareTo方法返回0的时候集合中只有一个元素 return 1; //当compareTo方法返回正数的时候集合会怎么存就怎么取 return -1; //当compareTo方法返回负数的时候集合会倒序存储 } }
//定义一个类,实现Comparator接口,并重写compare()方法, class CompareByLength implements Comparator<String> { @Override public int compare(String s1, String s2) { //按照字符串的长度比较 int num = s1.length() - s2.length(); //长度为主要条件 return num == 0 ? s1.compareTo(s2) : num; //内容为次要条件 } }
2 . LinekdHashSet 继承于HashSet
原理 : 底层使用LinkedHashMap
无序集合
Set是无序的Collection 实现类HashSet TreeSet LinkHashSet
核心特性 : 存储无序且值不相等
1.HashSet
原理 : HashMap的数据存储是通过数组+链表/红黑树实现的,存储大概流程是通过hash函数计算在数组中存储的位置,如果该位置已经有值了,判断key是否相同,相同则覆盖,不相同则放到元素对应的链表中,如果链表长度大于8,就转化为红黑树,如果容量不够,则需扩容
HashSet 存放的是散列值 按照元素的散列值存取元素 (HashMap)
HashSet 先判断HashCode 如果HashCode相等 再通过equals方法判断 所以判断对象 需要重写HashCode方法和equals方法
private transient HashMap<E,Object> map; //默认构造器 public HashSet() { map = new HashMap<>(); } //将传入的集合添加到HashSet的构造器 public HashSet(Collection<? extends E> c) { map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16)); addAll(c); } //明确初始容量和装载因子的构造器 public HashSet(int initialCapacity, float loadFactor) { map = new HashMap<>(initialCapacity, loadFactor); } //仅明确初始容量的构造器(装载因子默认0.75) public HashSet(int initialCapacity) { map = new HashMap<>(initialCapacity); }