集合
个人笔记----23.4.21
文章目录
Collection
单列集合(value)
Iterator
Iterable接口中里有方法iterator(),用以返回一个实现了Iterator接口的对象(迭代器)
迭代器遍历
Iterator iterator = list.iterator();
while(iterator.hasNext()){
Object dog = iterator.next();
system.out.println(dog);
}
增强for循环遍历
底层使用的也是迭代器
for(Object dog : list){
system.out.println(dog);
}
List
List接口是Collection接口的子接口
-
List集合类中元素有序(即添加顺序和取出顺序一致)、 且可重复
-
List集合中的每个元素都有其对应的顺序索引, 即支持索引。
-
List容器中的元素都对应一 个整数型的序号记载其在容器中的位置,可以根据序号取元素
-
JDK API中List接口的实现类有
AbstractList,AbstractSequentialList,ArrayList,AttributeList,CopyOnWriteArrayList, LinkedList,RoleList,RoleUnresolvedList ,Stack,Vector
ArrayList
- 可以放入null,甚至多个
- 是由数组来实现数据存储的
- 基本等同于Vector,但线程不安全(效率高)
底层结构和源码分析:
-
ArrayList中维护了一个Object类型的数组
transient Object[] elementData;
transient:瞬间,短暂的,表示该属性不会被序列化
-
当创建ArrayList对象时,如果使用的是无参构造器,则初始elementData容量为0, 第1
次添加,则扩容elementData为10, 如需要再次扩容,则扩容elementData为1.5倍。 -
如果使用的是指定大小的构造器,则初始elementData容量为指定大小, 如果需要扩容,
则直接扩容elementData为1.5倍。 -
源码分析
//创建了一个空的elementData数组={} public ArrayList() { this.elementData =DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } private static final Object[] 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); } } //1.先确定是否要扩容 2.赋值 public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; } private void ensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); } //确定minCapacity,第一次扩容为10 private static int calculateCapacity(Object[] elementData, int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return Math.max(DEFAULT_CAPACITY, minCapacity); } return minCapacity; } //1.modCount记录集合被修改的次数2.elementData大小不够就调用grow()扩容 private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); } //第一次newCapacity=10,以后1.5倍,扩容使用的是Arrays.copyOf(),底层为c语言实现 private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1); //1.5倍 if (newCapacity - minCapacity < 0) //初始是0就扩10 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); }
Vector
-
底层也是一个对象数组,
protected Object[] elementData;
-
线程同步,带有synchronized
-
扩容机制:无参默认10,满了2倍扩容;指定大小直接2倍
-
源码分析
//构造器 public Vector() { this(10); } public Vector(int initialCapacity) { this(initialCapacity, 0); } public Vector(int initialCapacity, int capacityIncrement) { super(); if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); this.elementData = new Object[initialCapacity]; this.capacityIncrement = capacityIncrement; } //add,基本跟ArrayList大同小异 public synchronized boolean add(E e) { modCount++; // ensureCapacityHelper(elementCount + 1); elementData[elementCount++] = e; return true; } private void ensureCapacityHelper(int minCapacity) { // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); } // private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); elementData = Arrays.copyOf(elementData, newCapacity); }
LinkedList
-
底层实现了双向链表和双端队列的特点
-
可以添加任意元素(包括null)并且可重复
-
线程不安全
-
LinkedList底层维护了一个双向链表
-
LinkedList中维护了两个属性first和last分别指向首节点和尾节点,每个节点(Node对象) ,里面又维护了prev、next、item三个属性,其中通过prev指向前一个,通过next指向后一个节点。最终实现双向链表
-
所以LinkedList的元素的添加和删除,不是通过数组完成的,相对来说效率较高。
-
源码分析
transient int size = 0; transient Node<E> first; transient Node<E> last; public LinkedList() { } //添加 public boolean add(E e) { linkLast(e); return true; } void linkLast(E e) { final Node<E> l = last; final Node<E> newNode = new Node<>(l, e, null); last = newNode; if (l == null) first = newNode; else l.next = newNode; size++; modCount++; } //Node结点结构 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; } } //删除 public E remove() { return removeFirst(); } public E removeFirst() { final Node<E> f = first; if (f == null) throw new NoSuchElementException(); return unlinkFirst(f); } private E unlinkFirst(Node<E> f) { // assert f == first && f != null; final E element = f.item; final Node<E> next = f.next; f.item = null; f.next = null; // help GC 地址为空,交给GC清理 first = next; if (next == null) last = null; else next.prev = null; size--; modCount++; return element; } //...其他类似,就是一个双向链表
Set
-
无序(添加和取出的顺序不一致),没有索引
-
不允许重复元素,所以最多包含一个null
-
JDK API中Set接口的实现类有
AbstractSet,ConcurrentHashMap. KeySetView,ConcurrentSkipListSet ,CopyOnWriteArraySet, EnumSet, HashSet,JobStateReasons,LinkedHashSet ,TreeSet
HashSet
- 底层是HashMap(数组+链表+红黑树),就是一个邻接表,当链达到一定长度(8)就变成红黑树
- 可以放null,但只有一个,不能重复
- 不保证元素有序,取决于hash后,再确定索引的结果
- 不能有重复元素/对象
经典面试题
set.add(new Dog("tom"));//0K
set.add(new Dog("tom"));//0k
set.add(new String("hsp"));//ok
set.add(new String("hsp"));//加入不了。
hashSet底层原理:
- HashSet 底层是HashMap
- 添加一个元素时,先得到hash值-会转成->索引值
- 找到存储数据表table ,看这个索引位置是否已经存放的有元素
- 如果没有,直接加入
- 如果有,调用equals比较,如果相同,就放弃添加,如果不相同,则
添加到最后 - 在Java8中,如果一条链表的元素个数到达TREEIFY_THRESHOLD(默认
是8),并且table的大小>=MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树)
-
源码
public HashSet() { map = new HashMap<>(); } //add public boolean add(E e) { return map.put(e, PRESENT)==null; //PRESENT只起到占位作用 } // Dummy value to associate with an Object in the backing Map private static final Object PRESENT = new Object(); public V put(K key, V value) { return putVal(hash(key), key, value, false, true); } //hash(key)得到key对应的hash值,hash值不和hashCode完全等价 static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); //无符号右移16位+^ } //核心代码 final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; //辅助变量 if ((tab = table) == null || (n = tab.length) == 0) //table为hashmap中邻接表数组 n = (tab = resize()).length; //该函数(扩容)见下 if ((p = tab[i = (n - 1) & hash]) == null) //根据key,得hash,计算其应存table中位置 tab[i] = newNode(hash, key, value, null);//并赋给p,然后判断p为空就newNode else { Node<K,V> e; K k; //辅助变量 /**当前索引位置对应的链表第一个元素和准备添加的key的hash值一样,并且满足条件(1.准备加入的key和p指向的Node结点的key是同一个对象2.不是同一个对象但内容相同(equals())就不能加入*/ if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; else if (p instanceof TreeNode)//判断p是一颗红黑树调用putTreeVal添加 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else {//如果table对应索引位置已经是链表,就for循环比较 for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash);//添加元素后达到8个链表树化(该方法见下) break;//与每个元素比较,都不相同,加入到链表最后,break } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break;//与元素比较过程中如果有相同,直接break p = e; } } if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; if (++size > threshold) //大于临界值扩容(只要增加了元素size++,不一定要填充table) resize(); afterNodeInsertion(evict); //留给hashmap子类(如linkedhashma)实现,做一些自己的动作 return null; } //扩容 final Node<K,V>[] resize() { Node<K,V>[] oldTab = table; int oldCap = (oldTab == null) ? 0 : oldTab.length; int oldThr = threshold; int newCap, newThr = 0; if (oldCap > 0) { if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; } else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) newThr = oldThr << 1; // double threshold } else if (oldThr > 0) // initial capacity was placed in threshold newCap = oldThr; else { // zero initial threshold signifies using defaults newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); //容量到0.75 } //就扩容,DEFAULT_LOAD_FACTOR(0.75)为加载因子 if (newThr == 0) { float ft = (float)newCap * loadFactor; newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE); } threshold = newThr; @SuppressWarnings({"rawtypes","unchecked"}) Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; table = newTab; if (oldTab != null) { for (int j = 0; j < oldCap; ++j) { Node<K,V> e; if ((e = oldTab[j]) != null) { oldTab[j] = null; if (e.next == null) newTab[e.hash & (newCap - 1)] = e; else if (e instanceof TreeNode) ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); else { // preserve order Node<K,V> loHead = null, loTail = null; Node<K,V> hiHead = null, hiTail = null; Node<K,V> next; do { next = e.next; if ((e.hash & oldCap) == 0) { if (loTail == null) loHead = e; else loTail.next = e; loTail = e; } else { if (hiTail == null) hiHead = e; else hiTail.next = e; hiTail = e; } } while ((e = next) != null); if (loTail != null) { loTail.next = null; newTab[j] = loHead; } if (hiTail != null) { hiTail.next = null; newTab[j + oldCap] = hiHead; } } } } } return newTab; } //树化(当链表长度到8个,且table到了64) final void treeifyBin(Node<K,V>[] tab, int hash) { int n, index; Node<K,V> e; if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) //64 resize(); else if ((e = tab[index = (n - 1) & hash]) != null) { TreeNode<K,V> hd = null, tl = null; do { TreeNode<K,V> p = replacementTreeNode(e, null); if (tl == null) hd = p; else { p.prev = tl; tl.next = p; } tl = p; } while ((e = e.next) != null); if ((tab[index] = hd) != null) hd.treeify(tab); } }
hashset扩容机制详解
-
HashSet底层是HashMap,第一次添加时,table 数组扩容到16,
临界值(threshold)=16*加载因子(loadFactor=0.75) = 12
-
如果table数组使用到了临界值(12),就会扩容到162 = 32,新的临界值就是
320.75 = 24,依次类推 -
在Java8中,如果一条链表的元素个数到达TREEIFY_THRESHOLD(默认是8),并且table的大小>=
MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树),否则仍然采用数组扩容机制 -
注意只要添加了元素(不管是在table上新占一个还是在链表后加一个都算),就算是size++
LinkedHashSet
- LinkedHashSet是HashSet的子类,
extends HashSet<E>
implements Set<E>
- 底层是LinkedHashMap,维护了一个数组+双向链表
- LinkedHashSet根据元素的hashCode值来决定元素的存储位置,同时使用链表维护元素的次序,这使得元素看起来是以插入顺序保存的。(拆开就是双向链表,不过结点hash到不同的结点 )
- LinkedHashSet不允许添重复元素
说明
-
LinkedHashSet加入顺序和取出元素/数据的顺序一致
-
LinkedHashSet底层维护的是一个LinkedHashMap( 是HashMap的子类)
-
LinkedHashSet底层结构( 数组tabLe+双向链表)
-
添加第一次时,直接将数组table扩容到16 , 存放的结点类型是LinkedHashMap E n t r y 数组是 H a s h M a p Entry 数组是HashMap Entry数组是HashMapNode[] 存放的元素/数据是LinkedHashMap$Entry类型
-
源码
static class Entry<K,V> extends HashMap.Node<K,V> { Entry<K,V> before, after; Entry(int hash, K key, V value, Node<K,V> next) { super(hash, key, value, next); } } //add等全使用父类hashmap的源码
TreeSet
-
使用无参构造器创建TreeSet时,任然是无序的
-
使用TreeSet提供的一个构造器,可以传入一个比较器(匿名内部类)并指定排序规则
-
源码
public TreeSet() { this(new TreeMap<E,Object>()); } //传入比较器 public TreeSet(Comparator<? super E> comparator) { this(new TreeMap<>(comparator)); } public TreeMap(Comparator<? super K> comparator) { this.comparator = comparator; } //add public boolean add(E e) { return m.put(e, PRESENT)==null; } private transient NavigableMap<E,Object> m;//m ...
Map
双列集合(key-value)
- Map与Collection并列存在。用于保存具有映射关系的数据:Key-Value
- Map中的key和value 可以是任何引用类型的数据,会封装到HashMap$Node对象中
- Map中的key不允许重复,原因和HashSet一样,前面分析过源码.
- Map中的value可以重复
- Map的key可以为null, value也可以为null ,注意key为null,只能有一个,value为null ,可以多个
- 常用String类作为Map的key
- key和value之间存在单向一对一关系,即通过指定的key总能找到对应的value
- Map存放的k-v是放在一个HashMap$Node中的(Node实现了Entry接口)
Node为HashMap的内部类
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
-
k-v最后是HashMap$Node
newNode(hash, key, value, null)
-
k-v为了方便程序员的遍历,还会创建EntrySet集合,该集合存放的元素类型Entry
transient Set<Map.Entry<K,V>> entrySet;
其中Entry(一个接口)中的K指向Node中的key,value指向value
-
Map.Entry定义了
K getKey(); K getValue();
HashMap
- 添加相同的key,会覆盖原来的key-val,等同于修改
- 没有实现同步,线程不安全
- 具体底层实现在HashSet已分析
HashTable
- 存放的元素是键值对:即K-V
- hashtable的键和值都不能为null,否则会抛出NullPointerException
- hashTable使用方法基本上和HashMap-样
- hashTable是线程安全的,hashMap是线程不安全的
底层:
-
底层有一个数组
private transient Entry<?,?>[] table;
初始化大小为11 -
临界值threshold 8 = 11 * 0.75
-
扩容:
if (count >= threshold)
满足时int newCapacity = (oldCapacity << 1) + 1;
-
源码
//put public synchronized V put(K key, V value) { // Make sure the value is not null if (value == null) { throw new NullPointerException(); } // Makes sure the key is not already in the hashtable. Entry<?,?> tab[] = table; int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; @SuppressWarnings("unchecked") Entry<K,V> entry = (Entry<K,V>)tab[index]; for(; entry != null ; entry = entry.next) { if ((entry.hash == hash) && entry.key.equals(key)) { V old = entry.value; entry.value = value; return old; } } addEntry(hash, key, value, index); return null; } //addEntry:添加k-v,封装到entry private void addEntry(int hash, K key, V value, int index) { modCount++; Entry<?,?> tab[] = table; if (count >= threshold) { // Rehash the table if the threshold is exceeded rehash(); tab = table; hash = key.hashCode(); index = (hash & 0x7FFFFFFF) % tab.length; } // Creates the new entry. @SuppressWarnings("unchecked") Entry<K,V> e = (Entry<K,V>) tab[index]; tab[index] = new Entry<>(hash, key, value, e); count++; } //rehash:扩容 protected void rehash() { int oldCapacity = table.length; Entry<?,?>[] oldMap = table; // overflow-conscious code int newCapacity = (oldCapacity << 1) + 1; if (newCapacity - MAX_ARRAY_SIZE > 0) { if (oldCapacity == MAX_ARRAY_SIZE) // Keep running with MAX_ARRAY_SIZE buckets return; newCapacity = MAX_ARRAY_SIZE; } Entry<?,?>[] newMap = new Entry<?,?>[newCapacity]; modCount++; threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1); table = newMap; for (int i = oldCapacity ; i-- > 0 ;) { for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) { Entry<K,V> e = old; old = old.next; int index = (e.hash & 0x7FFFFFFF) % newCapacity; e.next = (Entry<K,V>)newMap[index]; newMap[index] = e; } } }
Properties
- Properties类继承自Hashtable类并且实现了Map接口,也是使用一种键值对的形
式来保存数据。 - 他的使用特点和Hashtable类似
- Properties还可以用于从xxx.properties文件中,加载数据到Properties类对象,
并进行读取和修改 - 可以通过k-v存放数据,key和value不能为null
TreeMap
ConcurrentHashMap
Collections
- 一个操作Set、List、Map等集合的工具类
- 提供了一系列静态的方法对集合元素进行排序、查询和修改等操作
选用参考
总结-开发中如何选择集合实现类
在开发中,选择什么集合实现类,主要取决于业务操作特点,然后根据集合实现类特性进行选择,分析如下:
- 先判断存储的类型 (一组对象[单列]或一组键值对[双列])
- 一组对象[单列]:Collection接口
允许重复:List
增删多: LinkedList [底层维护了一个双向链表]
改查多: ArrayList [底层维护 Object类型的可变数组]
不允许重复:Set
无序: HashSet[底层是HashMap ,维护了一个哈希表 即(数组+链表+红黑树)]
排序: TreeSet
插入和取出顺序一致: LinkedHashSet ,维护数组+双向链表 - 一组键值对[双列]:Map
键无序: HashMap [底层是: 哈希表 jdk7: 数组+链表,jdk8: 数组+链表+红黑树]
键排序: TreeMap
键插入和取出顺序一致: LinkedHashMap
读取文件 Properties