技能提升:java容器详解

一: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类。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值