我们为什么需要集合框架?
对单个数据元素进行操作很简单,但是不能满足各种应用场景。比如全班一共30个学生,算班级的平均年龄。
使用数组,倒是可以实现这个目标,它可以把这30个学生的信息都存到一块连续的存储空间中,便利一遍把年龄值都加起来,再除以总数。
如果需要取出某个学生的信息,也很方便,因为每个学生有索引。数组下标对应好了就行。
但是如果要删除一个学生或者增加一个呢?
删除的情况,除了删最后一个学生的那种情况,其余的情况下,被删除学生之后的所有学生都要向前挪一位。
增加的情况,也会遇到相同的问题,因为要让位出来给新学生,还有一个问题就是,如果数组已经满了,怎么处理?
另外,如果我要把这个班的班主任的信息也放到学生们一起呢?数组是完全做不到的,因为它只能存储相同类型的元素。
通过以上分析,可以得出数组的几个缺点:
- 增加/删除元素不便
- 长度固定,扩展性差
- 只能放置同类型元素
集合框架有哪些?他们之间是什么关系?
集合是包含一组相关数据元素的对象,它提供了对其所包含的各种元素的操作。
所谓框架就是一个类库的集合。集合框架就是一个用来表示和操作集合的统一的架构,包含了实现集合的接口与类。
Collection:集合层次中的根接口
Set:不能包含重复的元素。SortedSet是一个按照升序排列元素的Set
List:是一个有序的集合,可以包含重复的元素。提供了按索引访问的方式
Map:包含了key-value对。Map不能包含重复的key。SortedMap是一个按照升序排列key的Map.
都继承自List的 ArrayList 和 LinkedList 有什么不一样?
学数据结构的时候,接触过两种很常见的数据结构:数组和链表,也用C语言写过,在Java中 ArrayList实现原理和数组一样,LinkedList实现原理和链表一样。
来扒一下 增加一个元素add(E e)的源码。
这是链表插入:
/**
* Links e as last element.
*/
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;
}
}
那么数组插入是怎样的呢?
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/
public boolean add(E e) {
//去检查一下空间够不够,如果不够就要扩充容量
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
根据应用场景的特点来选择Array和Lis;
Set 和 List 有什么区别?
set不能存放相同的元素,这是它最大的一个特征。
但是需要注意的是,一般情况下,set只能识别基本数据类型是否重复,对于自定义的类型(如图的Student),需要在定义这个类时加上hashcode()方法,此方法的代码可自动生成(右键->source->hashcode() equals()).
以下是add的核心代码,目前水平还没发一下看懂~
为什么set的add 要 map的put来帮忙?
另外,由于set是无需的,遍历时只能用foreach,不能用for.
/**
* Implements Map.put and related methods
*
* @param hash hash for key
* @param key the key
* @param value the value to put
* @param onlyIfAbsent if true, don't change existing value
* @param evict if false, the table is in creation mode.
* @return previous value, or null if none
*/
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)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
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);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
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)
resize();
afterNodeInsertion(evict);
return null;
}
HashMap 几乎是 Map系的一枝独秀,用键值对的方法来快速定位到一个对象
这里的put方法居然跟set里加一个元素的核心方法一样。why???