一、Java集合框架总览
二、接口介绍
- Iterator和Iterable
Iterator是一个迭代器,能够对集合进行元素的遍历,下面是Iterator迭代器的所有内部函数
而Iterable内部定义了iterator()方法,属于对Iterator的再次封装
简单写了个迭代器方式遍历List集合
List list=new ArrayList();
Iterator iterator=list.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
疑问一:为什么Iterable要对Iterator再次封装,为什么不直接实现Iterator或在Iterable内部定义hasNext()和next()等方法?
答案可参考这篇文章:https://www.jianshu.com/p/cf82ab7e51ef
- Collection接口
Collection接口是集合结构层次中的根接口,它代表了一组Object对象,一些集合允许重复元素,一些集合不允许重复元素。JDK并没有提供任何这个接口的直接实现。他提供了特定的子接口(例如:List、Set)。此接口通常用于在需要最大通用性的地方传递和操作集合。
(1)Collection接口继承了Iterable接口
(2)元素无序且可重复 - List接口和Set接口特点
(1)List接口
List接口继承了Collection接口,它是一个有序集合,使用此接口的用户可以精确地控制每个元素在列表中的插入位置。用户可以通过其整数索引访问元素,并搜索列表中的元素。与Set不同,List允许重复元素。
(2)Set接口
Set接口继承了Collection接口,集合内元素无序且不重复,最多包含一个null元素。 - Map接口
Map中键和值一一映射(Key-Value),一个键最多映射一个值,并且键不允许重复且是无序的。Map中的Key和Value可以是任意引用类型的数据,当非系统类作为Map的Key时,必须重写equals()和hashCode()方法。
遍历Map的四种方法:
Map<String,Object> map=new HashMap();
/**
* 1.第一种方式:使用 entrySet 遍历 Map 类(最常用)
*/
for (Map.Entry<String,Object> entry:map.entrySet()){
String key=entry.getKey();
Object value=entry.getValue();
System.out.println("key="+key+" value="+value);
}
/**
* 第二种方式:通过Map.keySet得到所有的key,遍历key得到value
*/
for (String key:map.keySet()){
Object value=map.get(key);
System.out.println("key="+key+" value="+value);
}
/**
* 第三种方式:通过Map.values()遍历所有的value,但不能遍历key
*/
for (Object value:map.values()){
System.out.println("value="+value);
}
/**
* 第四种方式:通过Map.entrySet使用iterator遍历key和value(不推荐)
*/
Iterator<Map.Entry<String,Object>> iterator1=map.entrySet().iterator();
while (iterator1.hasNext()){
Map.Entry<String,Object> entry=iterator1.next();
String key=entry.getKey();
Object value=entry.getValue();
System.out.println("key="+key+" value="+value);
}
三、接口实现类介绍
- List接口的实现类
(1)ArrayList
ArrayList类实现了List接口,它的底层数据结构是数组,所以删除插入操作时间复杂度较高。不是线程安全的。
两个带参的构造方法:
a.public ArrayList(int initialCapacity)
initialCapacity是初始化容量,initialCapacity是否为0决定创建的是不是空数组,
b.public ArrayList(Collection<? extends E> c)
这个构造方法表示可接收所有实现Collection接口的类,并在构造方法内部调用toArray()赋值给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) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
ArrayList的扩容函数grow(int minCapacity),扩容规则:新数据容量=当前数组容量+(当前数组 容量/2),即当前数组容量*1.5。新容量不能小于最小容量minCapacity,否则将声明最小容量的数组大小。新容量也不能大于最大容量MAX_ARRAY_SIZE ,否则将声明最大容量的数组大小。
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
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);
}
ArrayList的底层数据结构就是数组,所以根据下标查询某元素时间复杂度O(1),某位置增加删除元素的时间负责度则是O(n),因为不论增加还是删除都涉及到后面的元素的移动。
(2)LinkedList
LinkedList类实现了List接口,底层数据结构是双向链表,由于是链表,所以搜索的时间复杂度O(n),增加删除的时间复杂度为O(1)。不是线程安全的。
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 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++;
}
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
final Node<E> pred = succ.prev;
final Node<E> newNode = new Node<>(pred, e, succ);
succ.prev = newNode;
if (pred == null)
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}
(3) Vector
Vector实现了List接口,从构造函数中可以很容易发现Vector的底层数据结构使数组:
public Vector(int initialCapacity, int capacityIncrement) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
this.capacityIncrement = capacityIncrement;
}
Vector是线程安全的,add()、set()、get()方法都是synchronized的,可见锁粒度较大,会导致操作效率低下。ConcurrentHashMap降低了锁粒度。下面也会说到。
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
扩容方法如下:
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);
}
- Set接口的实现类
(1)HashSet
HashSet实现了Set接口,元素不可重复。实际上底层是一个HashMap实例。
public HashSet() {
map = new HashMap<>();
}
但HashMap是以键值对的方式进行存储的,所以每次add新的元素时会默认赋值一个Object对象给值。不是线程安全的。
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
(2)TreeSet
TreeSet实现了NavigableSet接口,NavigableSet接口继承了SortedSet接口,SortedSet又继承了Set接口,从SortedSet这个接口的命名就已经清楚的告诉我们,直接或间接实现这个接口的类都是有序的。
TreeSet底层实现是一个TreeMap实例,同样键值对中的值被赋值为Object。不是线程安全的。
public TreeSet() {
this(new TreeMap<E,Object>());
}
- Map接口的实现类
(1)HashMap
HashMap的详解可参考我的另一篇文章:https://blog.csdn.net/m0_37841477/article/details/105009417
(2)Hashtable
先总结Hashtable的特点,元素是无序不重复的,线程安全,使用共享锁,性能差
Hashtable实现了Map接口,它的底层的数据结构使哈希桶和单项链表
private static class Entry<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Entry<K,V> next;
}
public Hashtable(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal Load: "+loadFactor);
if (initialCapacity==0)
initialCapacity = 1;
this.loadFactor = loadFactor;
table = new Entry<?,?>[initialCapacity];
threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
}
Hashtable的线程安全很好理解,它的put()、get()方法都使用了关键字synchronized
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;
}
未完待续
(3)ConcurrentHashMap
未完待续
(4)TreeMap
未完待续