集合 - List、Set、Map
集合
1 Java集合框架概述
1.1 概述
- Colletion接口:单列集合,存储一个一个的数据
- List接口:存储有序的、可重复的数据
- ArrayList
- LinkedList
- Vector
- Set接口:存储无序的、不可重复的数据
- HashSet
- LinkedHashSet
- TreeSet
- List接口:存储有序的、可重复的数据
- Map接口:双列集合,存储一对一对(Key - Value)的数据
- HashMap
- LinkedHashMap
- TreeMap
- Hashtable
- Properties
-
集合、数组都是对多个数据进行存储操作的结构,简称Java容器
此时的存储,主要指的是内存层面的存储,不涉及到持久化的存储
-
数组在内存存储方面的特点:
- 数组初始化以后,长度就确定了
- 数组声明的类型,就决定了进行元素初始化时的类型
-
数组在存储数据方面的弊端:
- 数组初始化以后,长度就不可变了,不便于扩展
- 数组中提供的属性和方法少,不便于进行添加、删除、插入等操作,且效率不高。 同时无法直接获取存储元素的个数
- 数组存储的数据是有序的、可以重复的。---->存储数据的特点单一
1.2 Colloection、Map
1.3 Collection接口继承树
实线:继承关系
虚线:实现关系
1.4 Map接口继承树
实线:继承关系
虚线:实现关系
2 Collection接口方法
像Collection接口的实现类的对象中添加数据obj时,要求obj所在类要重写equals()
@Test
public void test(){
Collection coll = new ArrayList();
//add(Object e):将元素e添加到集合coll中
coll.add("AA");
coll.add("BB");
coll.add(123);//自动装箱
coll.add(new Date());
coll.add(new String("Tome"));
//Person p = new Person("Jerry", 20);
//coll.add(p);
coll.add(new Person("Jerry", 20));
//addAll(Collection coll):将other集合中的元素添加到当前的集合中
Collection other = new ArrayList();
other.add("CC");
other.add(456);
coll.addAll(other);
//int size():获取添加的元素个数
coll.size();//4
//clear():清空集合元素
other.claer();
//boolean isEmpty():判断当前集合是否为空(是否有元素)
coll.isEmpty();//false
//boolean contains(Object obj):判断当前集合中是否包含obj
//我们在判断时会调用obj对象所在类的equals()
coll.contains("AA");//true
coll.contains(new String("Tome"));//true
coll.contains(p);//false
//boolean containsAll(Collection coll1):判断coll1中的所有元素是否都存在于当前集合中
Collection coll1 = Arrays.asList(123,456);
coll.containsAll(coll1);//true
Collection coll2 = Arrays.asList(123,456,789);
coll.containsAll(coll2);//false
//boolean remove(Object obj):从当前集合中移除obj元素,若有重复则移除一个
coll.remove(123);
coll.remove(new Person("Jerry", 20));
//boolean removeAll(Collection coll1):差集,从当前集合中移除coll1中所有的元素
coll.removeAll(coll1);//只会移除123,456
//boolean retainAll(Collection coll2):交集,获取当前集合和coll2集合的交集,并返回给当前集合
coll.retainAll(coll2);//保留123,456
//boolean equals(Object obj):要想返回true,需要当前集合和形参集合的元素都相同,顺序也要相同
Collection coll3 = new ArrayList();
coll3.add("AA");
coll3.add("BB");
coll3.add(123);//自动装箱
coll3.add(new Date());
coll3.add(new String("Tome"));
coll3.add(new Person("Jerry", 20));
coll.equals(coll3);//true
//Object[] toArray():集合 --> 数组
Object[] arr = coll.toArray;
for(int i = 0; i < arr.length; i++){
System.out.println(arr[i]);
}
//拓展:数组 --> 集合 :调用Arrays类的静态方法asList(T ... t)//
List<String> list = Arrays.asList(new String[]{"AA","BB"});
System.out.println(list);//[AA,BB]
List<int[]> arr1 = Arrays.asList(new int[]{123,456});
//List arr1 = Arrays.asList(new int[]{123,456});
System.out.println(arr1);//[[I@22927a81] 一个元素
List arr2 = Arrays.asList(123, 456);
System.out.println(arr2);//123,456 两个元素
List arr3 = Arrays.asList(new Integer[]{123, 456});
System.out.println(arr3);//123,456 两个元素
//int hashCode():返回当前对象的哈希值
System.out.println(coll.hashCode());
//iterator():返回Iterator接口的实例,用于遍历集合元素
}
3 Iterator迭代器接口
3.1 遍历 - hasNext()、next()
@Test
public void test(){
Collection coll = new ArrayList();
//add(Object e):将元素e添加到集合coll中
coll.add(123);
coll.add(456);
coll.add(false);
coll.add(new String("Tome"));
coll.add(new Person("Jerry", 20));
//Iterator iterator():返回Iterator接口的实例,用于遍历集合元素
Iterator iterator = coll.iterator();
//方式一
System.out.println(iterator.next());
System.out.println(iterator.next());
System.out.println(iterator.next());
System.out.println(iterator.next());
System.out.println(iterator.next());
//报异常:NoSuchElementException
System.out.println(iterator.next());
//方式二:不推荐
for(int i = 0;i < coll.size();i++){
System.out.println(iterator.next());
}
//方式三:推荐
//hasNext():判断是否还有下一个元素
while(iterator.hasNext()){
//next():①指针下移 ②将下移以后集合位置上的元素返回
System.out.println(iterator.next());
}
}
3.2 移除 - remove()
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(false);
coll.add(new String("Tome"));
coll.add(new Person("Jerry", 20));
//remove()
Iterator iterator = coll.iterator();//回到起点
while(iterator.hasNext()){
Object obj = iterator.next();
if(obj.equals("Tom")){
iterator.remove();
}
}
3.3 增强for循环
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(false);
coll.add(new String("Tome"));
coll.add(new Person("Jerry", 20));
//for( 集合中元素的类型 局部变量 : 集合对象 )
//内部仍然调用了迭代器
for(Object obj : coll){
System.out.println(obj);
}
int[] arr = new int[]{1,2,3,5,6};
//for( 数组中元素的类型 局部变量 : 数组对象 )
for(int i : arr){
System.out.println(i);
}
3.4 练习
atguigu、atguigu、atguigu、atguigu、atguigu
null、null、null、null、null
4 Collection子接口一:List(“动态”数组)
有序可重复
4.1 概述
4.2 List接口方法
ArrayList list = new ArrayList();
list.add(123);
list.add(456);
list.add("AA");
list.add(new Person("Tome", 12));
list.add(456);
//void add(int index, Object ele):
//在index位置插入ele元素
list.add(1,"BB");//[123,BB,456,AA,Person{name='Tom', age=12},456]
//boolean addAll(int index, Collection eles)
//从index位置开始将eles中的所有元素添加进来
List list1 = Arrays.asList(1,2,3);
list.addAll(list1);//[123,BB,456,AA,Person{name='Tom', age=12},456,1,2,3]
//Object get(int index)
//获取指定index位置的元素
list.get(0);//123
//int indexOf(Object obj)
//返回obj在集合中首次出现的位置,没有返-1
int index = list.indexOf(456);//2
//int lastIndexOf(Object obj)
//返回obj在当前集合中末次出现的位置,没有返-1
int index = list.indexOf(456);//5
//Object remove(int index)
//移除指定index位置的元素,并返回此元素
Object obj = list.remove(0);//[BB,456,AA,Person{name='Tom', age=12},456,1,2,3]
//Object set(int index, Object ele)
//设置指定index位置的元素为ele
list.set(0,"CC");//[CC,456,AA,Person{name='Tom', age=12},456,1,2,3]
//List subList(int fromIndex, int toIndex)
//返回从fromIndex到toIndex位置的子集合,左闭右开
List sublist = list.subList(2,4);//[AA,Person{name='Tom', age=12}]
4.2.1 总结常用方法
- 增:
- add(Object obj)
- addAll(Collection coll)
- 删:
- remove(Object obj)
- remove(int index)
- 改:
- set(int index, Object ele)
- 查:
- get(int index)
- 插:
- add(int index, Object ele)
- addAll(int index, Collection eles)
- 长度:
- size()
- 遍历:
- Iterator迭代器方式
- 增强for循环
- 普通for循环
4.3 List实现类之一:ArrayList
4.3.1 源码分析
4.3.1.1 JDK7
ArrayList list = new ArrayList();//底层创建了长度为10的Object[]数组elementData
list.add(1);//elementData[0] = new Integer(123);
...
list.add(11);//如果此次的添加导致底层elementData数组容量不够,则扩容,默认情况下,扩容为原来的容量的1.5倍的新数组,同时需要将原有数组中的数据复制到新的数组中
建议开发中使用带参的构造器
ArrayList list = new ArrayList(int capacity);
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
private static final long serialVersionUID = 8683452581122892189L;
private transient Object[] elementData;
private int size;
//----------------------------------------------
//构造器
public ArrayList(int initialCapacity) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
}
//空参构造器,把当前底层的数组给初始化了,容量为10
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
this(10);
}
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
size = elementData.length;
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
}
//----------------------------------------------
//扩容
private void grow(int minCapacity) {//扩容为原来的1.5倍
// overflow-conscious code
int oldCapacity = elementData.length;//先记录底层数组的长度
int newCapacity = oldCapacity + (oldCapacity >> 1);//【oldCapacity >> 1 相当于除以2】扩容为原来的1.5倍
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;//还不够直接拿你的容量
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);//超过MAX_ARRAY_SIZE的情况,取整形的最大值
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
//超过MAX_ARRAY_SIZE的情况,取整形的最大值
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
//确定长度是否够
private void ensureCapacityInternal(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)//不够则扩容
grow(minCapacity);//扩容
}
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!//确定长度是否够
elementData[size++] = e;
return true;
}
}
4.3.1.2 JDK8
ArrayList list = new ArrayList();//底层Object[] elementData初始化为{}.并没有创建长度为10的数组
list.add(1);//第一次调用add()时,底层才创建长度为10的数组,并将数据1添加到elementData[0]中
...
list.add(11);//后续的操作与AJD7无异
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
private static final long serialVersionUID = 8683452581122892189L;
private static final int DEFAULT_CAPACITY = 10;
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};//什么都没有
transient Object[] elementData;
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() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
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;
}
}
//----------------------------------------------
//扩容
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);
}
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 ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
}
4.3.1.3 小结
内存延迟
JDK1.7:ArrayList像饿汉式,直接创建一个初始容量为10的数组
JDK1.8:ArrayList像懒汉式,一开始创建一个长度为0的数组,当添加第一个元素时再创建一个容量为10的数组
4.3.2 面试题
index
包装类
4.4 List实现类之二:LinkedList
4.4.1 源码分析
LinkedList list = new LinkedList();//内部声明了Node类型的first和last属性,默认值为null
list.add(123);///将123封装到Node中,创建了Node对象
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
transient int size = 0;
transient Node<E> first;
transient Node<E> last;
//----------------------------------------------
//构造器
public LinkedList() {
}
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
//----------------------------------------------
//add()
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 boolean add(E e) {
linkLast(e);
return true;
}
//----------------------------------------------
//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;
}
}
}
4.5 List 实现类之三:Vector
默认情况下,扩容为原来的2倍
4.6 面试题
4.6.1 ArrayList、LinkedList、Vector三者的异同
-
同:三个实现类都是实现了List接口,存储数据的特点相同:有序、可重复
-
异:
-
ArrayList:
-
作为List接口的主要实现类
-
线程不安全的,效率高
-
底层使用 Object[] 存储
-
-
LinkedList:
-
底层使用 双向链表 存储()【对于频繁的插入或删除元素的操作,建议使用LinkedList类,效率较高】
-
-
Vector:
-
作为List接口的古老实现类(JDK1.0就有了)
-
线程安全的,效率低
-
底层使用 Object[] 存储
-
-
5 Collection子接口二:Set
无序不可重复
5.1 概述
- HashSet:(底层:数组+链表的结构)
- 作为Set接口的主要实现类
- 线程不安全的
- 可以存储null值
- LinkedHashSet:
- 作为HashSet的子类,让他看似是有序
- 遍历其内部数据时,可以按照添加的顺序遍历
- 对于频繁的遍历操作,效率高于HashSet
- TreeSet:
- 可以按照添加对象的指定属性,进行排序
Set set = new HashSet();
set.add(123);
set.add(456);
set.add("AA");
set.add("CC");
set.add(new Person("Tom", 12));
HashSet:(底层:数组+链表的结构)
5.1.1 无序性与不可重复性
以HashSet为例说明:
-
无序性:不等于随机性。存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值添加
-
不可重复性:保证添加的元素按照equlas()判断时【既要重写equlas()也要重写Hashcode()】,不能返回true,即:相同的元素只能添加一个
5.1.2 添加元素的过程
以HashSet为例说明:
创建一个16长度的数组,我们向HashSet中添加元素a时,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值,此哈希值接着通过某种算法计算除HashSet底层数组种的存放位置(即为:索引位置),
再判断数组此位置上是否已经有元素:
-
如果此位置上没有其他元素,则元素a添加成功 ---->情况1
-
如果此位置上有其他元素b(或者已经有以链表形式存在的多个元素),则比较元素a于元素b的hash值:
- 如果hash值不相同,则元素a添加成功 ---->情况2
- 如果hash值相同,进而需要调用元素a所在类的qeulas()方法:
- equals()返回true,元素a添加失败
- equals()返回false,元素a添加成功 ---->情况3
对于添加成功的情况2和情况3而言:元素a 与已经存在指定索引位置上数据以链表的方式存储。
jdk7 :元素a放到数组中,指向原来的元素
jdk8 :原来的元素在数组中,指向元素a
即七上八下
创建一个16长度的数组,添加先计算哈希值(相同属性则哈希值相同),通过哈希值判断存储在数组的哪里
七上八下
3,4哈希值不一样
1,5哈希值一样
则equals(),返回true,相同,则添加失败
链表长度超过8,则变成红黑树
5.2 要求
- 向Set(主要值HashSet、LinkedHashSet)中添加的数组,其所在的类一定要重写hashCode()和equals()
- 重写的hashCode()和euals()尽可能保存一致性
5.3 Set实现类之一:HashSet
作为Set接口的主要实现类
5.4 Set实现类之二:LinkedHashSet
还是无序的,在原有的HashSet的基础上添加了一对双向链表,来记录添加的先后顺序
优点:对于频繁的遍历操作,效率高于HashSet
5.5 Set实现类之三:TreeSet
-
向TreeSet中添加的数据,要求是相同类的对象
-
两种排序方式:自然排序(实现Comparable接口) 和 定制排序
-
自然排序中,比较两个对象是否相同的标准为:compareTo()返回0,不再是equals()
-
定制排序中,比较两个对象是否相同的标准为:compare()返回0,不再是equals()
TreeSet set = new TreeSet();
//方式一
set.add(11);
set.add(22);
set.add(-11);
set.add(0);
set.add(33);
Iterator iterator = set.iterator();
while(iterator.hashNext()){
System.out.println(iterator.next());
}//-11、0、11、22、33
//方式二
//自然排序
set.add(new User("Tom", 12));
set.add(new User("Mike", 65));
set.add(new User("Jim", 2));
set.add(new User("Jerry", 30));
set.add(new User("Jerry", 40));
while(iterator.hashNext()){
System.out.println(iterator.next());
}
//User{name='', age=}
class User implement Comparable{
String name;
int age;
//构造器,equals(),hashCode(),
//compareTo():姓名从小到大,年龄从小到大
@Override
public int compareTo(Object obj){
if(o instanceof User){
User user = (user)o;
// return this.name.compareTo(user.name);
int compare = this.name.compareTo(user.name);
if(compare != 0){
return compare;
}else{
return Integer.compare(this.age, user.age);
}
}else{
throw new RuntimeException("输入类型不匹配")
}
}
}
//定制排序
Comparator com = new Comparator(){
//年龄从小到大
@Override
public int compare(Object o1,Object o2){
if(o1 instancef User && o2 instancef User){
User u1 = (Goods) o1;
User u2 = (Goods) o2;
return Integer.compare(u1.getAge(), u2.getAge());
}else{
throw new RuntimeException("输入的类型不一样");
}
}
};
TreeSet set = new TreeSet(com);
set.add(new User("Tom", 12));
set.add(new User("Mike", 65));
set.add(new User("Jim", 2));
set.add(new User("Jerry", 30));
set.add(new User("Jerry", 40));
5.5.1 自然排序
5.5.2 定制排序
5.5.3 面试题
remove之后,还是两个
remove找的hash值不是之前那个了
public int compareTo(Object o){
if(o instanceof Employee){
Employee e = (Employee)o;
return this.name.compareTo(e.name);
}
//return 0;
throw new RunTimeException("传入的数据类型不一样");
}
TreeSet set = new TreeSet(new Comparator(){
@Override
public int compare(Object o1,Object o2){
if(o1 instanceof Employee && o1 instanceof Employee){
Employee e1 = (Employee)o1;
Employee e2 = (Employee)o2;
MyDate b1 = e1.getBirthday();
MyDate b2 = e2.getBirthday();
int minusYear = b1.getYear() - b2.getYear();
if(minusYear != 0 ){
return minusYear;
}
int minusMonth = b1.getMonth() - b2.getMonth();
if(minusMonth != 0 ){
return minusMonth;
}
return b1.getDay() - b2.getDay();
}
throw new RunTimeException("传入的数据类型不一样");
}
});
6 Map接口
key - value
x - y
-
HashMap
- 作为主要实现类
- 线程不安全的,效率高
- 能存储null的key和value
-
LinkedHashMap
-
保存在遍历map元素时,可以按照添加的顺序实现遍历,
原因:在原有的HashMap底层结构基础上,添加了一对指针,指向前后元素
对于频繁的遍历操作,此类执行效率高于HashMap
-
-
TreeMap
- 保证按照添加的key-value对进行排序,实现排序遍历。考虑key的自然排序和定制排序
- 底层使用红黑树
-
Hashtable
- 作为古老实现类
- 线程安全的,效率低
- 不能存储null的key和value
-
Properties
- 常用来处理配置文件。
- key和value都是String类型
HashMap的底层:
数组+链表(jdk 7及之前)
数组+链表+红黑树(jdk 8)
6.1 Map接口概述
6.2 Map结构理解
Map中的key:无序的,不可重复的。使用Set存储所有的key —> key所在的类要重写equald()和hashCode()
Map中的value:无序的,可重复的。使用Collection存储所有的value —> value所在的类要重写equald()
一个键值对:key-value构成了一个Entry对象
Map中的entry:无序的,不可重复的。使用Set存储所有的entry
6.3 常用方法
6.3.1 添加、删除、修改操作:
-
Object put(Object key,Object value):将指定key-value添加到(或修改)当前map对象中
map.put("AA",123); map.put("BB",45); map.put("CC",78); map.put("AA",12);//修改 map1.put("DD",3); map1.put("EE",6);
-
void putAll(Map m):将m中的所有key-value对存放到当前map中
map.putAll(map1);
-
Object remove(Object key):移除指定key的key-value对,并返回value
map.remove("CC")
-
void clear():清空当前map中的所有数据
map.clear();//{}
6.3.2 元素查询的操作:
-
Object get(Object key):获取指定key对应的value
map.get("CC");//78
-
boolean containsKey(Object key):是否包含指定的key
map.containsKey("CC");//true
-
boolean containsValue(Object value):是否包含指定的value
map.containsValue(12);//true
-
int size():返回map中key-value对的个数
map.size();
-
boolean isEmpty():判断当前map是否为空
map.isEmpty()//false
-
boolean equals(Object obj):判断当前map和参数对象obj是否相等
6.3.3 元视图操作的方法:
- Set keySet():返回所有key构成的Set集合
- Collection values():返回所有value构成的Collection集合
- Set entrySet():返回所有key-value对构成的Set集合
Map map = new HashMap();
//map.put(..,..)省略
System.out.println("map的所有key:");
Set keys = map.keySet();// HashSet
for (Object key : keys) {
System.out.println(key + "->" + map.get(key));
}
System.out.println("map的所有的value:");
Collection values = map.values();
Iterator iter = values.iterator();
while (iter.hasNext()) {
System.out.println(iter.next());
}
System.out.println("map所有的映射关系:");
// 映射关系的类型是Map.Entry类型,它是Map接口的内部接口
Set mappings = map.entrySet();
for (Object mapping : mappings) {
Map.Entry entry = (Map.Entry) mapping;
System.out.println("key是:" + entry.getKey() + ",value是:" + entry.getValue());
}
Set keySet = map.keySet();// HashSet
Iterator iter2 = keySet.iterator();
while (iter2.hasNext()) {
Object key = iter2.next();
Object value = map.get(key);
System.out.println("key是:" + key + ",value是:" + value);
}
6.4 Map实现类之一:HashMap
6.4.1 HashMap的存储结构
6.4.1.1 以jdk 7为例
Map map = new HashMap();
在实例化以后,底层创建了一个长度为16的一维数组Entry[] table。
map.put(key1 , value1);
- 首先,调用key1所在类的hashCode()计算key1的哈希值,此哈希值经过算法计算之后,得到Entry在数组中的存放位置
- 如果此位置上的数据为空,此时key1-value1【Entry】添加成功 ---- 情况1
- 如果此位置上的数据不为空(意味着此位置上存在一个或多个数据(以链表形式存在)),比较key1和已经存在的一个或多个数据的哈希值
- 如果key1的哈希值和已经存在的数据的哈希值都不相同,此时key1-value1【Entry】添加成功 ---- 情况2
- 如果key1的哈希值和已经存在的某一个数据的哈希值相同,继续比较:调用equals()方法比较
- 如果equals()返回false:此时key1-value1【Entry】添加成功 ---- 情况3
- 如果equals()返回true:使用value1替换相同key的value值【修改功能】
补充:
关于情况2和情况3:此时key1-value1【Entry】和原来数据以链表的方式存储
在不断的添加过程中:会涉及扩容问题,当超过临界值(且要存放的位置非空)
默认的扩容方式:扩容为原来容量的2倍,并将原来的数据复制过来
6.4.1.1 以jdk 8为例
jdk8相较于jdk底层实现方面的不同:
-
Map map = new HashMap();
底层摸鱼创建一个长度为16的数组
-
jdk8底层的数组:Node[],而非Entry[]
-
首次调用put()方法时,底层创建长度为16的数组
-
jdk7底层结构只有:数组+链表
jdk8底层结构:数组+链表+红黑树
当数组的某一个索引位置上的元素以链表形式存在的数据个数 > 8.且当前数组的长度 > 64时,此时此索引位置上的所有数据改为使用红黑树存储。
6.4.2 源码
6.4.2.1 jdk7
public class HashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
{
//HashMap的默认容量,16
static final int DEFAULT_INITIAL_CAPACITY = 16;
//HashMap的最大支持容量,2^30
static final int MAXIMUM_CAPACITY = 1 << 30;
//HashMap的默认加载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//
transient Entry<K,V>[] table;
//HashMap中存储的键值对的数量
transient int size;
//扩容的临界值,=容量*填充因子
int threshold;
//填充因子
final float loadFactor;
//HashMap扩容和结构改变的次数
transient int modCount;
//
static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE;
//构造器
//默认:16,0.75
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;//16
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
// Find a power of 2 >= initialCapacity
int capacity = 1;
while (capacity < initialCapacity)
capacity <<= 1;//16
this.loadFactor = loadFactor;//0.75
//临界值
//capacity * loadFactor = 12;影响扩容
threshold = (int)Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
table = new Entry[capacity];//Entry[16]
useAltHashing = sun.misc.VM.isBooted() &&
(capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
init();
}
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
public HashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
public HashMap(Map<? extends K, ? extends V> m) {
this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
putAllForCreate(m);
}
//put
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key);//计算当前key的哈希值
int i = indexFor(hash, table.length);//获得存放位置
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))) {
V oldValue = e.value;
e.value = value;//覆盖
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
//计算哈希值
transient boolean useAltHashing;
transient final int hashSeed = sun.misc.Hashing.randomHashSeed(this);
final int hash(Object k) {
int h = 0;
if (useAltHashing) {
if (k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}
h = hashSeed;
}
h ^= k.hashCode();//异或
// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
//利用哈希值得来位置
static int indexFor(int h, int length) {
return h & (length-1);
}
//add添加
//addEntry(hash, key, value, i);
void addEntry(int hash, K key, V value, int bucketIndex) {
if ((size >= threshold/* capacity * loadfactor = 12 */) && (null != table[bucketIndex])) {
resize(2 * table.length);//扩容为2倍
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
createEntry(hash, key, value, bucketIndex);//不需要扩容
}
//createEntry添加
//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++;
}
//扩容
void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
Entry[] newTable = new Entry[newCapacity];
boolean oldAltHashing = useAltHashing;
useAltHashing |= sun.misc.VM.isBooted() &&
(newCapacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
boolean rehash = oldAltHashing ^ useAltHashing;
transfer(newTable, rehash);
table = newTable;
threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
int hash;
/**
* Creates new entry.
*/
Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}
public final K getKey() {
return key;
}
public final V getValue() {
return value;
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry e = (Map.Entry)o;
Object k1 = getKey();
Object k2 = e.getKey();
if (k1 == k2 || (k1 != null && k1.equals(k2))) {
Object v1 = getValue();
Object v2 = e.getValue();
if (v1 == v2 || (v1 != null && v1.equals(v2)))
return true;
}
return false;
}
public final int hashCode() {
return (key==null ? 0 : key.hashCode()) ^
(value==null ? 0 : value.hashCode());
}
public final String toString() {
return getKey() + "=" + getValue();
}
/**
* This method is invoked whenever the value in an entry is
* overwritten by an invocation of put(k,v) for a key k that's already
* in the HashMap.
*/
void recordAccess(HashMap<K,V> m) {
}
/**
* This method is invoked whenever the entry is
* removed from the table.
*/
void recordRemoval(HashMap<K,V> m) {
}
}
}
6.4.2.2 jdk8
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
private static final long serialVersionUID = 362498820763181265L;
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
static final int MAXIMUM_CAPACITY = 1 << 30;
static final float DEFAULT_LOAD_FACTOR = 0.75f;
static final int TREEIFY_THRESHOLD = 8;
static final int UNTREEIFY_THRESHOLD = 6;
static final int MIN_TREEIFY_CAPACITY = 64;
transient Node<K,V>[] table;
transient Set<Map.Entry<K,V>> entrySet;
transient int size;
transient int modCount;
int threshold;
final float loadFactor;
//Node
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + "=" + value; }
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}
//构造器
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
}
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
}
//put
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
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;
}
//扩容
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve order
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
//变红黑树
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
else if ((e = tab[index = (n - 1) & hash]) != null) {
TreeNode<K,V> hd = null, tl = null;
do {
TreeNode<K,V> p = replacementTreeNode(e, null);
if (tl == null)
hd = p;
else {
p.prev = tl;
tl.next = p;
}
tl = p;
} while ((e = e.next) != null);
if ((tab[index] = hd) != null)
hd.treeify(tab);
}
}
}
6.4.3 面试题
6.4 Map实现类之二:LinkedHashMap
6.5 Map实现类之三:TreeMap
6.6 Map实现类之四:Hashtable
6.7 Map实现类之五:Properties
-
项目新建
-
try{ Properties pros = new Properties(); FileInputStream fis = new FileInputStream("jdbc.properties"); pros.load(fis);//加载流对应的文件 String name = pros.getPtoperty("name"); String password = pros.getPtoperty("password"); }catch(IOException e){ e.printStackTrace(); }finally{ if(fis != null){ try{ fis.close(); }catch(IOException e){ e.printStackTrace(); } } }
6.8 面试题
- HahsMap的底层实现原理
- HahsMap 和 Hashtable 的异同
- CurrentHashMap 与 Hashtable 的异同]
- ……………………
7 Collections工具类
7.1 常用方法
List dest = Arrays.asList(new Object[list.size()]);
Collections.copy(dest , list);
7.2 常用方法:同步控制
List list1 = Collections.synchronizedList(list);
8 练习
9 外加:Collection的Java8新特性(遍历):forEach()
默认方法
Collection coll = new ArrayList();
coll.add(1);
coll.add(2);
coll.add(3);
coll.add(4);
coll.forEach(System.out::println);