目录:Java集合详解
1 集合框架与数组的对比及概述
1、集合、数据都是对多个数据进行存储操作的结构,简称Java容器。
(此时的存储,主要指的是内存层面的存储,不涉及持久化的存储(.txt、数据库等))
2、数组在存储多个数据方面的特点
- 一旦初始化,其长度就确定了
- 一旦定义好,其元素的类型也就确定了
3、数组存储的缺点
- 数组初始化后,长度不可修改
- 数组提供的方法非常有限,对于增删改数据等操作,非常不方便,并且效率不高
- 数据存储的特点:有序、可重读。对于无序不可重复的需求,不能满足。
2 集合框架接口
1.Collection接口继承树
List:有序、可重复
Set:无序、不可重复
HashSet:线程不安全,可以存储Null值
LinkedHashSet:作为HashSet子类,遍历其内部数据时,可以按照添加的顺序遍历
TreeSet:可以按照添加对象的指定属性进行排序
1.Set
1.Set无序、不可重复(以HashSet为例)
- Set集合中没有额外的方法,使用的都是Collection里面的方法
- 无序性:不等于随机性。存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值来添加。
- 不可重复性:保证添加的元素按照equals()判断时,不能返回true,即相同的元素只能添加一个。
2.Set添加元素的过程(以HashSet为例)
向Set中添加的数据,其所在类一定要重写hashCode()和equals()。重写的hashCode()和equals()尽可能保持一致性:相等的对象必须具有相等的散列码。
- 向HashSet添加a元素,调用元素a所在类的hashCode()方法,计算a的哈希值
- 此哈希值接着通过某种算法计算出HashSet底层数组的存放位置(索引位置)
判断数组在此位置上是否有元素 - 如果此位置上没有其他元素,则a添加成功(情况1)
- 如果该位置有其他元素(或者是以链表形式存在多个元素),则比较a与b的hash值
----如果hash值不相同,则a添加成功(情况2)
----如果hash值相同,进而需要调用a所在类的equals()方法
--------equals()返回false,a添加失败(情况3)
--------equals()返回true,a添加成功
情况2和情况3:a是与已经存在索引位置上数据以链表的方式存储。(七上八下)
jdk1.7:a放到数组中,指向原来的元素。
jdk1.8:原来的元素在数组中,指向元素a。
2.TreeSet
- 向TreeSet中添加的数据,要求是相同类的对象。
- 比较大小时,要实现implements Comparator接口
----自然排序Comparable接口,compareTo()返回0,不再是equals()
----定制排序Comparator接口,compare()返回0,不再是equals()
2.Map接口继承树
HashMap:作为Map的主要实现类,线程不安全,效率高,存储null的key和value。
----LinkedHashMap:保证在遍历map元素时,可以按照添加的顺序遍历
TreeMap:保证按照添加的key-value进行排序,考虑key的自然排序或者定制排序,底层使用红黑树
Hashtable:作为古老的实现类,线程安全,效率低,不能存储null的key和value。
2 集合的遍历
1.Iterator迭代器遍历Collection
@Test
public void test3() {
Collection c = new ArrayList<Integer>();
c.add(1);
c.add(2);
Iterator iterator = c.iterator();
while (iterator.hasNext()){
// 指针下移
System.out.println(iterator.next());
if(iterator.next().equals(10)){
// 移除元素
iterator.remove();
}
}
}
3 集合的遍历
1.ArrayList源码
- JDK1.8之后,不是在构造器中创建长度为10的数组,而是在第一次调用add方法才创建。(jdk7中的ArrayList的对象创建类似于单例的饿汉式,而jdk1.8中的ArrayList的对象创建类似于单例的懒汉式,延迟了数组的创建,节省了内存)
- 如果此次添加导致底层的elementData数组容量不够,则扩容。
- 默认情况下,扩容为原来容量的1.5倍,同时将原有数组复制到新的数组中
- 如果大概知道ArrayList的容量为多少,则使用public ArrayList(int initialCapacity)构造方法,可避免频繁扩容
package com.ll;
import java.util.Arrays;
public class ArrayList<E> {
// 数组的默认长度是10
private static final int DEFAULT_CAPACITY = 10;
// 容器是Object[]类型的数组
transient Object[] elementData;
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
protected transient int modCount = 0;
private int size;
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() {
// 没有创建长度为10的数组, DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
// 如果此次添加导致底层的elementData数组容量不够,则扩容。
// 默认情况下,扩容为原来容量的1.5倍,同时将原有数组复制到新的数组中
//如果大概知道ArrayList的容量为多少,则使用public ArrayList(int initialCapacity)构造方法,可避免频繁扩容
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
// 确定内部的容量
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
// 默认情况下,扩容为原来容量的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 同时将原有数组复制到新的数组中
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
}
2.LinkedList源码
- list.add(123)将123封装到Node中,创建了Node对象
public class LinkedList<E> {
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;
}
}
transient Node<E> first;
transient Node<E> last;
private void linkFirst(E e) {
final Node<E> f = first;
final Node<E> newNode = new Node<E>(null, e, f);
first = newNode;
if (f == null)
last = newNode;
else
f.prev = newNode;
size++;
modCount++;
}
}
3.Vector源码
方法用synchronized修饰了
public synchronized void addElement(E obj) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = obj;
}
4.HashMap
1.实现原理
1.jdk7
在实例化以后,底层创建了长度是16的一维数组Entry[] table
map.put(key1,value1)
首先计算key1所在类的hashCode()计算key1哈希值,此哈希值是经过某种算法计算以后,得到Entry数组中的存放位置。
如果此位置上的数据为空,此时key1-value1添加成功
2.jdk8
2.源码
jdk7:数组+链表
jdk8:数组+链表+红黑树