一:List
特点:有序,不唯一
1.ArrayList线性表中的顺序表
- 在内存中分配连续的空间,实现长度可变的数组
- 优点:遍历元素和随机访问元素的效率比较高
- 缺点:添加删除需要大量移动元素效率低,按照内容查找效率低
2.LinkedList线性表中的双向链表
- 采用双向链表的存储方式
- 优点:插入删除元素效率高
- 缺点:遍历随机访问元素效率低
3.ArrayList源码分析
ArrayList实现List接口,再看看其构造方法
transient Object[] elementData;//ArrayList底层是一个长度可变的数组,默认没有分配空间
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};//静态常量,空数组,分配0个空间
private int size;//集合元素个数,默认0
private static final Object[] EMPTY_ELEMENTDATA = {};
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;//无参数构造时,分配0个空间
}
public ArrayList(int initialCapacity) {//有参构造方法,指定初始容量
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];//初始化指定空间
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {//小于0抛出异常
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
add()方法
private static final int DEFAULT_CAPACITY = 10;
public boolean add(E e) {
ensureCapacityInternal(size + 1); //判断集合容量是否够用,实现扩容
elementData[size++] = e;//将指定元素加到集合最后
return true;
}
private void ensureCapacityInternal(int minCapacity) {//传入添加当前元素所需的最小容量
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {//如果存储元素的数组为null即第一次添加元素
return Math.max(DEFAULT_CAPACITY, minCapacity);//最小容量就为10,DEFAULT_CAPACITY=10,取DEFAULT_CAPACITY与minCapacity的最大值
}
return minCapacity;//否则就最小容量就为传入的值
}
private void ensureExplicitCapacity(int minCapacity) {//传入添加当前元素是所需的数组空间大小
modCount++;
if (minCapacity - elementData.length > 0)//判断所需数组空间大小是否大于现在的数组长度,大于,则扩容
grow(minCapacity);//扩容
}
private void grow(int minCapacity) {//扩容
int oldCapacity = elementData.length;//获得当前数组大小
int newCapacity = oldCapacity + (oldCapacity >> 1);//通过就容量获得新容量,每次扩容50%
if (newCapacity - minCapacity < 0)//如果新扩容的容量比所需空间大小还小
newCapacity = minCapacity;//则新容量就等于所需容量
if (newCapacity - MAX_ARRAY_SIZE > 0)//如果新扩容的容量比所需空间大小大
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);//根据当前存储数组及新容量,复制出新的数组
}
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // 如果新容量小于0,则抛出异常
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?//如果新容量大于Int最大值-8,则返回int最大值,否则返回当前新容量
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
- 进行空间检查,决定是否进行扩容,以及确定最少需要的容量,第一次容量为10
- 如果确定扩容,就执行grow(int minCapacity),minCapacity为最少需要的容量
- 第一次扩容,逻辑为newCapacity = oldCapacity + (oldCapacity >> 1);即在原有的容量基础上增加一半。
- 第一次扩容后,如果容量还是小于minCapacity,就将容量扩充为minCapacity。
- 对扩容后的容量进行判断,如果大于允许的最大容量MAX_ARRAY_SIZE(Integer.MAX_VALUE-8),则将容量再次调整为MAX_ARRAY_SIZE。至此扩容操作结束。
size()方法
public int size() {
return size;//返回当前数组大小
}
get(index)方法
public E get(int index) {
rangeCheck(index);//检查获取索引
return elementData(index);//获得数组的index索引的数据,并返回
}
private void rangeCheck(int index) {
if (index >= size)//判断获取的索引是否大于等于存储数据的数组的大小,大于则抛出异常
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
iterator()迭代器方法
public Iterator<E> iterator() {
return new Itr();//返回新建的Itr内部类对象
}
private class Itr implements Iterator<E> {
int cursor; // 用于记录当前迭代到的索引
int lastRet = -1; // 记录当前迭代的前一个位置
int expectedModCount = modCount;
Itr() {}
public boolean hasNext() {
return cursor != size;//判断当前迭代到的索引是否等于数组大小,不等于说明还有数据
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)//如果当前迭代的位置大于等于数组大小,抛出异常
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;//获得存储数据的数组
if (i >= elementData.length)//判断当前迭代的位置是否大于等于数组的大小,大于等于则抛出异常
throw new ConcurrentModificationException();
cursor = i + 1;//记录下一个需要迭代的位置
return (E) elementData[lastRet = i];//返回当前迭代位置的数据,并赋值lastRet
}
//其他方法不常用,有兴趣可以自己查看
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
Objects.requireNonNull(consumer);
final int size = ArrayList.this.size;
int i = cursor;
if (i >= size) {
return;
}
final Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
while (i != size && modCount == expectedModCount) {
consumer.accept((E) elementData[i++]);
}
// update once at end of iteration to reduce heap write traffic
cursor = i;
lastRet = i - 1;
checkForComodification();
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
4.LinkedList源码解析
构造方法
transient int size = 0;//默认一个元素
transient Node<E> first;//第一个节点
transient Node<E> last;//最后一个节点
public LinkedList() {}
//内部类,描述双向链表的每一个节点
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;
}
}
add()方法
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;//将新节点复制给last节点,表示新建节点在这次添加操作完后,为最后节点
if (l == null)//如果最后一个节点为null,表示第一次创建
first = newNode;//第一个节点等于新建节点
else
l.next = newNode;//最后一个节点的下一个节点指向新建节点
size++;//链表大小+1
modCount++;
}
get()方法
public E get(int index) {
checkElementIndex(index);//判断获取的索引
return node(index).item;//返回index对应节点中存储的值
}
private void checkElementIndex(int index) {
if (!isElementIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
Node<E> node(int index) {
if (index < (size >> 1)) {//判断索引是否<链表大小的一半
Node<E> x = first;//获得头结点
for (int i = 0; i < index; i++)//从第一个索引开始向后遍历到index
x = x.next;
return x;//获取到index对应节点返回
} else {
Node<E> x = last;//获得尾节点
for (int i = size - 1; i > index; i--)//从链表尾部开始向前遍历到index
x = x.prev;//获得上一个节点
return x;//获取到index对应节点返回
}
}
二:Set
特点:无序,唯一
1.HashSet
- 使用Hashtable哈希表存储结构
- 优点:添加速度快,查询速度快,删除速度快
- 缺点:无序
2.LinkedHashSet
- 使用哈希表存储结构,同时使用链表维护次序
- 添加顺序有序
3.TreeSet
- 采用二叉树(红黑树)存储数据
- 优点:有序(大小顺序),查询速度比List快
- 缺点:查询速度没有HashSet快
4.Hash表原理
对于底层使用Hash表存储数据时,存储元素需要重写hashCode及equals方法,这是为啥?
hash表也叫散列表,器实现多种多样,最常见的就是顺序表+链表的形式,主表为顺序表,每一个顺序表节点引出一个链表
hash表添加数据
- 通过hashCode()方法计算hash码
- 通过hash码计算存储位置
- 存储数据到指定位置
- 如果存储位置已经存在数据,则会发生冲突,就会调用对象的equals方法对链表数据依次比较,如都不相等,就会在链表存储该数据,如果有相等的就不添加,保证唯一性。
hash查询数据
- 计算哈希码
- 计算存储位置
- 到指定位置查询数据,如果存储位置就一个数据,就直接查到,如果过存储位置是链表,就依次查询链表,直到查询到,如果链表查询完依旧没有找到,数据不存在。
加载因子:存储数据大小与哈希表中数组的长度的比值(数组长度10,存储5个对象,加载因子=5/10=0.5),加载因子达到0.5是,性能最好,HashMap默认设置为0.75,当加载因子大于0.75时,数组长度就会扩容。
JDK1.8之后hash表结构
试想,如果过数据量大,就有可能出现大量冲突,就会导致一个数组节点后面拉起一个很长的链表,这样查询速度就会变慢,到了JDK1.8之后,链表节点个数>=(8-1)后,就会将链表转换为红黑树。
5.TreeSet中元素的大小比较
内部比较器:要求元素实现Comparable接口,实现compareTo方法,自定义比较规则,返回结果>0表示大于,<0表示小于,=0表示等于,内部比较器适用于比较规则为一种,比如以学生学号大小作比较。
外部比较器:自定义Comparator接口的实现类,实现compare方法,返回结果>1表示大于,<1表示小于,=0表示等于,外部比较器适用于多种比较规则,可以有多个Comparator接口的实现,实现多种比较规则,通过TreeSet的构造方法传值,TreeSet就会使用外部比较器比较元素。
TreeSet默认使用内部比较器,内外部比较器都存在时,使用外部比较器。
6.TreeSet存储数据及遍历数据原理
红黑树保持输出数据有序:存储有序,输出有序(中序遍历)
三:Map
特点:键值对的映射
1.HashMap
- key无序唯一
- value无序不唯一
2.LinkedHashMap
- 有序的HashMap,速度快
3.TreeMap
- 有序速度快,但没有Hash快
Set的底层就是使用Map实现的,只不过,Set底层的Map不存在value只有key
4.HashMap源码解析(JDK1.7为例)
实现Map接口,在看其构造方法
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 hash表的主数组默认长度16
static final int MAXIMUM_CAPACITY = 1 << 30;//主数组最大容量2^30
static final float DEFAULT_LOAD_FACTOR = 0.75f;//默认加载因子
static final Entry<?,?>[] EMPTY_TABLE = {};
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;//主数组的引用,默认没有存储空间
transient int size;元素个数
int threshold;//阈值(临界值), 16*0.75=12
final float loadFactor;//加载因子
public HashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);//设置默认主数组长度为16,加载因子为0.75
}
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)//初始主数组容量<0抛出异常
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)//初始容量大于最大容量是,默认就是最大容量
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))//加载因子不能<0
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;//设置加载因子
threshold = initialCapacity;//指定阈值
init();
}
put方法
public V put(K key, V value) {
if (table == EMPTY_TABLE) {//如果主数组为空,就初始化主数组
inflateTable(threshold);
}
if (key == null)//如果key等于null,这里不需要过多了解
return putForNullKey(value);
int hash = hash(key);//计算key的hash码
int i = indexFor(hash, table.length);//通过key的hash码得到存储位置
//找到要添加的位置并加入,key重复不添加
//Entry<K,V> e = table[i]先获得主数组i位置的Entry,e != null表示i位置存在数据,则进行比较
for (Entry<K,V> e = table[i]; e != null; e = e.next) {//循环比较每一个节点
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {//用新的value替换原先value
V oldValue = e.value;//记录原先的value
e.value = value;//指定新的value
e.recordAccess(this);
return oldValue;//返回原先的value
}
}
modCount++;
addEntry(hash, key, value, i);//产生新的加点,加入
return null;
}
//创建主数组
private void inflateTable(int toSize) {
int capacity = roundUpToPowerOf2(toSize);//通过指定主数组长度来计算主数组长度,主数组长度必须为2^n(n>0)
threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);//计算阈值
table = new Entry[capacity];//创建主数组,初始长度为16
initHashSeedAsNeeded(capacity);
}
//计算主数组长度
private static int roundUpToPowerOf2(int number) {
// 判断指定的主数组大小是否大于最大值,大于就指定为最大值,否则就判断指定的主数组大小是否大于1,
//大于则取大于指定值且最接近指定值的2的幂次方的值,否则就指定为1
//number=3/5 Integer.highestOneBit((number - 1) << 1)=4/8
return number >= MAXIMUM_CAPACITY
? MAXIMUM_CAPACITY
: (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;
}
transient int hashSeed = 0;
//获取key的hash码
final int hash(Object k) {
int h = hashSeed;
if (0 != h && k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}
h ^= k.hashCode();//调用key的hashCode()方法
h ^= (h >>> 20) ^ (h >>> 12);//对hashCode()方法的hash码进行多次处理,尽量让Hash码更加散列,数据分布更均匀
return h ^ (h >>> 7) ^ (h >>> 4);
}
//通过key的hash码。获得存储位置,length为主数组长度
static int indexFor(int h, int length) {
return h & (length-1);//通过key的hash码,对主数组长度进行位运算相当于取余只是速度更快,获得存储位置
}
//添加新的节点
void addEntry(int hash, K key, V value, int bucketIndex) {
//元素个数大于阈值需要扩容主数组,
if ((size >= threshold) && (null != table[bucketIndex])) {
//扩容原来主数组长度2倍
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
//添加一个节点
createEntry(hash, key, value, bucketIndex);
}
//添加节点,添加到链表头
void createEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<>(hash, key, value, e);
size++;//数量+1
}
//每个存储数据的节点
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;//键
V value;//值
Entry<K,V> next;//下一个节点
int hash;//key的Hash码
Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}
........
}
get()方法
public V get(Object key) {
if (key == null)//key等于null,不关心
return getForNullKey();
Entry<K,V> entry = getEntry(key);
return null == entry ? null : entry.getValue();
}
//通过key获得entry
final Entry<K,V> getEntry(Object key) {
if (size == 0) {
return null;
}
int hash = (key == null) ? 0 : hash(key);//获得key的hash码
for (Entry<K,V> e = table[indexFor(hash, table.length)];//计算存储位置,从存储位置查找entry,循环查找
e != null;
e = e.next) {
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;//找到就返回
}
return null;//不存在返回null
}
JDK1.8HashMap源码解析:https://blog.csdn.net/qq_36625757/article/details/90038751
5.HashSet源码解析
构造方法
private transient HashMap<E,Object> map;//成员变量为HashMap
private static final Object PRESENT = new Object();//
public HashSet() {
map = new HashMap<>();//创建HashMap对象
}
public HashSet(Collection<? extends E> c) {
map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
addAll(c);
}
由此可以看出HashSet底层使用HashMap实现
add()方法
public boolean add(E e) {
return map.put(e, PRESENT)==null;//直接将传来的值作为key,常量PRESENT作为value存储到map中
}
iterator()方法
public Iterator<E> iterator() {
return map.keySet().iterator();//获得map中所有的key,就获得了所有元素
}
6.TreeMap源码解析
继承实现关系
构造方法
private final Comparator<? super K> comparator;//外部比较器
private transient Entry<K,V> root = null;//起始指向红黑树根节点
private transient int size = 0;//红黑树的节点数(map的元素个数)
public TreeMap() {
comparator = null;//外部比较器为null,就是用内部比较器
}
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;//指定外部比较器
}
put()方法
public V put(K key, V value) {
Entry<K,V> t = root;//获取根节点
if (t == null) {//根节点为空,添加第一个节点
compare(key, key);
root = new Entry<>(key, value, null);//创建节点,赋值给root,作为根节点
size = 1;//元素数量为1
modCount++;
return null;
}
int cmp;
Entry<K,V> parent;
Comparator<? super K> cpr = comparator;//获得外部比较器
if (cpr != null) {//外部比较器不等于null,就使用外部比较器
do {
parent = t;//获得根节点,赋值给parent,留给之后使用
cmp = cpr.compare(key, t.key);//比较节点中key大小
if (cmp < 0)//根节点key>需要添加的key
t = t.left;//获得根节点的左节点,作为新的根节点,下次while使用
else if (cmp > 0)//根节点key<需要添加的key
t = t.right;//获得根节点的右节点,作为新的根节点,下次while使用
else//等于
return t.setValue(value);//覆盖节点的value为新的value
} while (t != null);
}
else {//否则使用内部比较器,其中逻辑同上
if (key == null)
throw new NullPointerException();
Comparable<? super K> k = (Comparable<? super K>) key;
do {
parent = t;
cmp = k.compareTo(t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
Entry<K,V> e = new Entry<>(key, value, parent);//创建新的红黑树节点
if (cmp < 0)//根据上面的比较结果,加入到parent的左或右
parent.left = e;
else
parent.right = e;
fixAfterInsertion(e);
size++;//元素数量+1
modCount++;
return null;
}
//TreeMap内部类,用于描述红黑树的每一个节点
static final class Entry<K,V> implements Map.Entry<K,V> {
K key;//键
V value;//值
Entry<K,V> left = null;//左节点
Entry<K,V> right = null;//右节点
Entry<K,V> parent;//父节点
boolean color = BLACK;//红或黑,默认黑
Entry(K key, V value, Entry<K,V> parent) {
this.key = key;
this.value = value;
this.parent = parent;
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
public V setValue(V value) {
V oldValue = this.value;
this.value = value;
return oldValue;
}
.........
}
get()方法
public V get(Object key) {
Entry<K,V> p = getEntry(key);
return (p==null ? null : p.value);
}
//获得Entry
final Entry<K,V> getEntry(Object key) {
if (comparator != null)//存在外部比较器,就是用外部比较器表查找
return getEntryUsingComparator(key);
if (key == null)
throw new NullPointerException();
//TreeMap中的key在不传外部比较器的情况下,key必须要实现Comparable接口,所以这里将其强转为内部比较器,
//使用内部比较器比较查找
Comparable<? super K> k = (Comparable<? super K>) key;
Entry<K,V> p = root;//获得根节点
while (p != null) {
int cmp = k.compareTo(p.key);
if (cmp < 0)
p = p.left;
else if (cmp > 0)
p = p.right;
else
return p;//找到节点就返回
}
return null;
}
//外部比较器原理同内部比较器
final Entry<K,V> getEntryUsingComparator(Object key) {
K k = (K) key;
Comparator<? super K> cpr = comparator;
if (cpr != null) {
Entry<K,V> p = root;
while (p != null) {
int cmp = cpr.compare(k, p.key);
if (cmp < 0)
p = p.left;
else if (cmp > 0)
p = p.right;
else
return p;
}
}
return null;
}
面试题:为啥HashMap每次扩容都是2^n
HashMap中的数据结构是数组+单链表的组合,我们希望元素存放的更均匀,最理想的效果是Entry数组中每个位置都只有一个元素,这样,查询的时候效率最高,不需要遍历单链表,也不需要通过equals去比较Key,而且空间利用率最大。那么如何计算才会分布最均匀呢?我们首先想到的就是%运算,哈希值%容量=存储位置索引,我们来看源码
static int indexFor(int h,int length){
return h & (length - 1);
}
h是通过k的hashCode最终计算出来的哈希值,并不是hashCode本身,而是hashCode之上又经过一层运算的hash值,length是目前容量。当容量是2^n时,h & (length -1) == h % length。这个等式实际上可以推理出来,2^n转换成二进制就是1+n个0,减1之后就是0+n个1,如16 -> 10000,15 -> 01111。
当HashMap的容量是16时,它的二进制是10000,(n-1)的二进制是01111,与hash值得计算结果如下:
上面四种情况我们可以看出,不同的hash值,和(n-1)进行位运算后,能够得出不同的值,使得添加的元素能够均匀分布在集合中不同的位置上,避免hash碰撞。下面就来看一下HashMap的容量不是2的n次幂的情况,当容量为10时,二进制为01010,(n-1)的二进制是01001,向里面添加同样的元素,结果为:
可以看出,有三个不同的元素进过&运算得出了同样的结果,严重的hash碰撞了。
终上所述,HashMap计算添加元素的位置时,使用的位运算,这是特别高效的运算;另外,HashMap的初始容量是2的n次幂,扩容也是2倍的形式进行扩容,是因为容量是2的n次幂,可以使得添加的元素均匀分布在HashMap中的数组上,减少hash碰撞,避免形成链表的结构,使得查询效率降低!
面试题:HashMap能否排序
可以排序
class Main {
public static void main(String[] args) {
Map<Integer, String> map1 = new HashMap<>();
map1.put(3, "三");
map1.put(6, "六");
map1.put(1, "一");
Map<String, Integer> map2 = new HashMap<>();
map2.put("三", 3);
map2.put("一", 1);
map2.put("六", 6);
sortByKey(map1);
sortByValue(map2);
}
// 按照键排序
static void sortByKey(Map map) {
Object[] objects = map.keySet().toArray();
Arrays.sort(objects);
for (int i = 0; i < objects.length; i++) {
System.out.println("键:" + objects[i] + " 值:" + map.get(objects[i]));
}
}
// 按照值排序
static void sortByValue(Map map) {
List<Map.Entry<String, Integer>> list = new ArrayList<Map.Entry<String, Integer>>(map.entrySet());
Collections.sort(list, new Comparator<Map.Entry<String, Integer>>() {
@Override
public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {
return o1.getValue().compareTo(o2.getValue());
}
});
for (Map.Entry<String, Integer> mapping : list) {
System.out.println("键:" + mapping.getKey() + " 值:" + mapping.getValue());
}
}
}
7.TreeSet源码解析
构造方法
private transient NavigableMap<E,Object> m;//成员变量,TreeMap的父接口
private static final Object PRESENT = new Object();
TreeSet(NavigableMap<E,Object> m) {
this.m = m;
}
public TreeSet() {
this(new TreeMap<E,Object>());//创建TreeMap赋值给成员m
}
由此可以看出,TreeSet底层使用TreeMap
add()方法
public boolean add(E e) {
return m.put(e, PRESENT)==null;//向TreeMap中添加,传入的值作为key,常量PRESENT作为value
}
iterator()方法
public Iterator<E> iterator() {
return m.navigableKeySet().iterator();//获得TreeMap的所有key
}
8.Iterator迭代器注意
在List及Set使用for或foreach进行循环时,不能对集合进行remove操作,否则会抛出异常,要想在循环中移除元素,可以使用迭代器的remove方法。
Collection及其子类可以使用迭代器,而Map不行,其原因是因为Collection继承Iterable,实现iterator()方法,而Map则没有。
四:旧的集合类
1.Vector
- 实现原理与ArrayList相同,功能相同,都是长度可变的数组结构,大多数情况下可以互用
- 两者的区别:
- Vector是早期JDK接口,而ArrayList是替换Vector的接口
- Vector是线程安全的,但效率低,ArrayList注重效率,但存在线程安全性问题
- 扩容时,Vector增长一倍,而ArrayList增长50%
2.Hashtable
- 实现原理与HashMap相同,底层都是使用Hash表存储数据,查询速度快,很多情况可以互用
- 两者区别:
- HashMap允许key和value为null,Hashtable不允许。
- HashMap的默认初始容量为16,Hashtable为11。
- HashMap的扩容为原来的2倍,Hashtable的扩容为原来的2倍加1。
- HashMap是非线程安全的,Hashtable是线程安全的。
- HashMap的hash值重新计算过,Hashtable直接使用hashCode。
- HashMap去掉了Hashtable中的contains方法。
- HashMap继承自AbstractMap类,Hashtable继承自Dictionary类。