本篇Java集合内容是根据b站韩顺平教学视频总结归纳得出的,不涉及泛型,包含一些源码的解读
目录
集合框架体系图
如图所示:图中,实线边框的是实现类,虚线边框的是抽象类,而点线边框的是接口
Collection接口是集合类的总接口,它没有直接实现的类,但却有两个继承的接口set和list
set中不能有重复的元素,list可以包含重复元素是一个有序的集合,提供了按索引访问的方式
Map是Java.util包中的另一个接口,它和Collection接口没有关系,是相互独立的,但是都属于集合类的一部分。Map包含了key-value对。Map不能包含重复的key,但是可以包含相同的value。
Iterator:所有的集合类,都实现了Iterator接口,这是一个用于遍历集合中元素的接口,主要包含以下三种方法:
1.hasNext()是否还有下一个元素。
2.next()返回下一个元素。
3.remove()删除当前元素。
后序也有通过Iterator来遍历集合元素的方式
集合和数组的区别
常用的集合分类
Collection接口
Collection接口常用方法
1.add() 添加元素
2.size() 获取集合中元素的个数
3.addAll() 把一个集合的元素添加到一个新的集合
4.isEmpty() 判断当前集合是否为空,判断的是集合中是否有元素
5.contains() 判断是否包含
6.containsAll() 判断集合coll1中是否包含coll集合中的所有元素
7.remove() 删除指定元素
8.removeAll() 删除当前集合中包含另一个集合中的所有元素
9.clear() 清空集合中的元素
public class Collection02 {
public static void main(String[] args) {
Collection col = new ArrayList();
//add() 添加元素
col.add("AA");
col.add(123);
col.add(new String("A"));
//size() 获取集合中元素的个数
System.out.println(col.size());
//addAll() 把一个集合的元素添加到一个新的集合
Collection coll = new ArrayList();
coll.add(456);
coll.addAll(col);
//isEmpty() 判断当前集合是否为空,判断的是集合中是否有元素
System.out.println(col.isEmpty());//返回true表示没有元素
//contains() 判断是否包含
System.out.println(col.contains(123));
//containsAll() 判断集合coll1中是否包含coll集合中的所有元素
System.out.println(coll.containsAll(coll));
//remove() 删除指定元素
coll.remove(123); //删除成功返回true,没找到返回false
System.out.println(coll);
//removeAll() 删除当前集合中包含另一个集合中的所有元素
coll.removeAll(col);
System.out.println(coll);
//clear() 清空集合中的元素
col.clear();
}
}
运行结果
3
false
true
true
[456, AA, A]
[456]
[]
10.retainAll() 获取两个集合的交集
11.equels() 比较两个集合是否相等
12.hashCode() 返回当前对象的哈希值
13.toArray() 集合转成数组
14.数组转成集合 Arrays.asList()
public class Collection02 {
public static void main(String[] args) {
Collection col = new ArrayList();
col.add("AA");
col.add(123);
col.add(new String("A"));
System.out.println(col.size());
Collection coll = new ArrayList();
coll.add(456);
coll.addAll(col);
System.out.println(col);
System.out.println(coll);
//retainAll() 获取两个集合的交集
coll.retainAll(col);
System.out.println(coll);
//equels() 比较两个集合是否相等
System.out.println(coll.equals(col));
//hashCode() 返回当前对象的哈希值
System.out.println(coll.hashCode());
//toArray() 集合转成数组
Object[] o = coll.toArray();
System.out.println(Arrays.toString(o));
//数组转成集合 Arrays.asList()
System.out.println(Arrays.asList(o));
}
}
运行结果
3
[AA, 123, A]
[456, AA, 123, A]
[AA, 123, A]
true
2032549
[AA, 123, A]
[AA, 123, A]
Collection的两种遍历方式
public class Collection02 {
public static void main(String[] args) {
Collection col = new ArrayList();
col.add("AA");
col.add(123);
col.add(new String("A"));
System.out.println(col.size());
//第一种迭代器的方式
Iterator iterator = col.iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
System.out.println( next);
}
System.out.println("=============");
//第二种增强for循环
for (Object obj:col) {
System.out.println(obj);
}
注意:
当退出 while 循环后 , 这时 iterator 迭代器,指向最后的元素
iterator.next();//这里会造成空指针异常
如果希望再次遍历,需要重置我们的迭代器
iterator = col.iterator();
迭代器运行图解
List和Set详解
List和Set的区别
List接口和常用方法
代码演示:
public class ListMethod {
public static void main(String[] args) {
List list = new ArrayList();//接口的多态
list.add("张三丰");
list.add("贾宝玉");
//void add(int index, Object ele);//在 index 位置插入 ele
//在 index = 1 的位置插入一个对象 原先位置上的对象往后平移一个位置
list.add(1,"韩顺平");
System.out.println("list=" + list);
// boolean addAll(int index, Collection eles):
// 从 index 位置开始将 eles 中的所有元素添加进来 加多个元素
List list2 = new ArrayList();
list2.add("jack");
list2.add("tom");
list.addAll(1, list2);
System.out.println("list=" + list);
// Object get(int index):获取指定 index 位置的元素
System.out.println(list.get(0));
// int indexOf(Object obj):返回 obj 在集合中首次出现的位置
System.out.println(list.indexOf("tom"));//2
// int lastIndexOf(Object obj):返回 obj 在当前集合从右往左查找的第一个对象
list.add("韩顺平");
System.out.println("list=" + list);
System.out.println(list.lastIndexOf("韩顺平"));
// Object remove(int index):移除指定 index 位置的元素,并返回此元素
System.out.println(list.remove(0));//输出移除的元素
System.out.println("list=" + list);
// Object set(int index, Object ele):
// 设置指定 index 位置的元素为 ele , 相当于是替换.
list.set(1, "jack");
System.out.println("list=" + list);
// List subList(int fromIndex, int toIndex):
// 返回从 fromIndex 到 toIndex 位置的子集合
// 注意返回的子集合 fromIndex <= subList < toIndex
// 这里(0,2)中不包括2 包括0 和 1
List returnlist = list.subList(0, 2);
System.out.println("returnlist=" + returnlist);
}
}
List的三种遍历方式
public class ListFor {
public static void main(String[] args) {
/*
三种遍历list的方式
1.迭代器
2.增强for循环
3.普通for循环
*/
//Vector LinkedList ArrayList都是实现List接口的子类
List list = new ArrayList();
//List list = new Vector();
//List list = new LinkedList();
list.add("jack");
list.add("tom");
list.add("鱼香肉丝");
list.add("北京烤鸭子");
//遍历
//1. 迭代器
Iterator iterator = list.iterator();
while(iterator.hasNext()){//快捷键是itit
Object next = iterator.next();
System.out.println(next);
}
System.out.println("==============");
//2.增强for循环
for (Object obj:list) {
System.out.println(obj);
}
//3.普通for循环
System.out.println("=============");
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));//通过get索引的方式获取每个元素
}
}
}
ArrayList底层结构和源码分析
1.ArrayList支持加入null 并且多个
2.ArrayList 是由数组来存储的
3.ArrayList 基本等同于Vector 除了ArrayList是线程不安全的 多线程不建议使用
初始化方式 | 容量 | 数量变化 |
List arrayList = new ArrayList(); | 初始数组容量为10,当真正对数组进行添加时,才真正分配容量 | 10->15->22->33->49->74->… |
List arrayList = new ArrayList(8); | 8 | 8->12->->18->27->… |
接下来我们看扩容的源码解读-----这里我们以第一种初始化方式举例
//1.创建一个arraylist对象
ArrayList arrayList = new ArrayList();
底层原理:调用无参构造器
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;//这是一个空数组 意味着最开始elementData.length =0
}
// private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
调用add方法
arrayList.add("test");
add方法底层原理
public boolean add(E e) {
// 确认elementData容量是否足够
ensureCapacityInternal(size + 1); // 第一次调用add()方法时,size=0
elementData[size++] = e;
return true;
}
先调用ensureCapacityInternal(int minCapacity) 方法,对数组容量进行检查,不够时则进行扩容。
private void ensureCapacityInternal(int minCapacity) {
// 如果elementData为"{}"即第一次调用add(E e),重新定义minCapacity的值,赋值为DEFAULT_CAPACITY=10
// 即第一次调用add(E e)方法时,定义底层数组elementData的长度为10
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
// 下面这个方法判断是否需要扩容
ensureExplicitCapacity(minCapacity);
}
ensureExplicitCapacity(minCapacity) 判断是否需要扩容
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// 第一次进入时,minCapacity=10,elementData.length=0,对数组进行扩容 --- 这一步还没有添加元素所以length = 0
//比如第二次进入时 mincapacity = 2 elementData = 10
// 之后再进入时,minCapacity=size+1,elementData.length=10(每次扩容后会改变),
// 因为这一个方法 ensureCapacityInternal(size + 1); 所以mincapacity = size + 1
// 需要minCapacity>elementData.length成立,才能扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
扩容具体方法
private void grow(int minCapacity) {
// 将数组长度赋值给oldCapacity
int oldCapacity = elementData.length;
// 将oldCapacity右移一位再加上oldCapacity,即相当于newCapacity=1.5oldCapacity(不考虑精度损失)
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 如果newCapacity还是小于minCapacity,直接将minCapacity赋值给newCapacity
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 特殊情况:newCapacity的值过大,直接将整型最大值赋给newCapacity,
// 即newCapacity=Integer.MAX_VALUE
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 将elementData的数据拷贝到扩容后的数组
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;
}
总结: 使用ArrayList()创建ArrayList对象时,不会定义底层数组的长度,当第一次调用add(E e) 方法时,初始化定义底层数组的长度为10,之后调用add(E e)时,如果需要扩容,则调用grow(int minCapacity) 进行扩容,长度为原来的1.5倍
Vector底层结构和源码分析
1.Vector底层也是一个对象数组
2.Vector是线程同步,即线程安全,Vector类的操作方法带有synchronized
3.开发中,需要线程同步时使用Vector
Vector扩容机制与Arraylist类似,但它是扩容两倍不是1.5倍,但是它可以指定扩容因子,也就是扩容倍数,而Arraylist不能
1、当使用的是无参构造函数创建Vector对象时,默认会初始化容量为10,每次扩容,容量 = 原容量 × 2
Vector vector = new Vector();
2、当使用的是一个参数的有参构造函数创建Vector对象时,初始化容量则为指定的长度,每次扩容,容量 = 原容量 × 2
Vector vector = new Vector(8);
3、当使用的是两个参数的有参构造函数创建Vector对象时,初始化容量则为指定的长度,每次扩容,容量 = 原容量 + 指定的扩容长度
Vector vector = new Vector(8,2);
LinkedList底层
LinkedList底层是一个双向链表,这与arraylist的存储数据方式就不同了,arraylist是通过动态数组来实现的,下面是图例演示
代码演示使用LinkedList增删改查
public class LinkedList01 {
public static void main(String[] args) {
LinkedList linkedList = new LinkedList();
linkedList.add(1);
linkedList.add(2);
linkedList.add(3);
System.out.println("linkedList=" + linkedList);
//演示一个删除结点的
//linkedList.remove(); // 这里默认删除的是第一个结点
linkedList.remove(2);
System.out.println("linkedList=" + linkedList);
//修改某个结点对象
linkedList.set(1, 999);
System.out.println("linkedList=" + linkedList);
//得到某个结点对象
//get(1) 是得到双向链表的第二个对象
Object o = linkedList.get(1);
System.out.println(o);//999
//因为 LinkedList 是 实现了 List 接口, 遍历方式
System.out.println("===LinkeList 遍历迭代器====");
Iterator iterator = linkedList.iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
System.out.println("next=" + next);
}
System.out.println("===LinkeList 遍历增强 for====");
for (Object o1 : linkedList) {
System.out.println("o1=" + o1);
}
System.out.println("===LinkeList 遍历普通 for====");
for (int i = 0; i < linkedList.size(); i++) {
System.out.println(linkedList.get(i));
}
}
}
Add方法源码解读
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;//last指向当前加入的元素
if (l == null)
first = newNode;//如果l是null说明当前是一个空链表,因此first就指向这个加入的元素
else
l.next = newNode;//如果l不是空说明当前链表不为空,那么first节点不用移动,而是把原先last的后面挂载上新数据
size++; //因此到这里就能明白为什么前面要有一个l = last的动作
modCount++;
}
remove方法源码解读
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
final E element = f.item;
final Node<E> next = f.next;
f.item = null;
f.next = null; // help GC
first = next;//移动first
if (next == null)
last = null;//next == null如果成立说明后续没有多的元素了,即删除的是最后一个元素
else
next.prev = null;//否则说明后续还有元素,那么next.prev就是当前删除元素的下一个元素的前一个指向置为null
size--;
modCount++;
return element;
}
总结与对比
底层结构 | 增删的效率 | 改查的效率 | |
Arraylist | 可变数组 | 较低,因为涉及数组扩容 | 较高,索引查找 |
linkedlist | 双向链表 | 较高 | 较低 |
Set接口介绍
1.不能存放重复元素,只能有一个null
2.Set是无序的,添加和取出顺序不一致,没有索引
3.Set的实现类主要有HashSet和TreeSet
4.Set常用方法和Collection一样
5.Set的遍历方式---->迭代器,增强for循环,不能使用索引
//方式一使用迭代器
System.out.println("=====使用迭代器===="); Iterator iterator = set.iterator();
while (iterator.hasNext()) { Object obj = iterator.next();
System.out.println("obj=" + obj);
}
//方式 2: 增强 for
System.out.println("=====增强 for====");
for (Object o : set) { System.out.println("o=" + o);
}
Set接口实现类HashSet
HashSet实际上是HashMap(源码了解)
源码解读
1、执行HashSet()
public HashSet() {
map = new HashMap<>();
}
2、执行 add()
public boolean add(E e) {
return map.put(e, PRESENT)==null;//PRESENT =private static final Object PRESENT = new Object();
}
3、执行 put(),该方法会执行 hash(key)得到key对应的hash值
public V put(K key, V value) {//这个key就是我们传入的数据,value是PRESENT ,不会改变,用于占位
return putVal(hash(key), key, value, false, true);
}
4、执行 putVal
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;//定义辅助变量
//table就是HashMap的一个数组,类型就是Node[]
//if语句表示如果当前table 是null,或者 大小=0
/就是第一次扩容,到16个空间
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//(1)根据key,得到hash去计算该key应该存放到table表的哪个索引位置
//并且把这个位置的对象赋给 P
//(2)判断p是否为null
//(2.1)如果p 为null,表示还没有存放元素,就创建一个Node
//(2.2)就放在该位置tab[i] = newNode(hash, key, value, null);
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;//局部辅助变量
//如果当前索引位置对应的链表的第一个元素和准备添加的key的hash值一样
//并且满足 下面两个条件之一:
//(1)准备加入的key和 p指向的Node节点的key是同一个对象
//(2)p指向的Node结点的key的equals()和准备加入的key比较后相同
//就不能加入
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//再判断 p 是不是一颗红黑树
//如果是一颗红黑树,就调用putTreeVal进行添加
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
//如果table对应索引位置已经是一个链表,就使用for循环进行比较
//(1) 依次和该链表的每一个元素进行比较后,如果都不相同,就加入到该链表的最后
// 注意在把元素添加到链表后应该立即进行判断该链表是否达到8个结点
// 如果达到,则调用treeifyBin()对当前链表进行红黑树化
// 在转化为红黑树的时候要进行判断,判断条件
// if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
// resize();
// 条件成立先table扩容
// 当条件不成立的时候才进行转化为红黑树
//(2)如果有相同的情况,就直接break
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;
//在这里真正实现扩容
//size 是我们每加入一个结点Node(k,v,h,next)
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
总结HashSet的执行机制
1.添加一个元素时,先获得它的hash值,这里的hash值着hashcode值不一样,然后转为索引值
2.找到存储数据表table,看这个索引位置是否有元素
3.如果没有直接加入
4.如果有就调用equals比较,相同就放弃添加,不相同就加在最后面
5.Java8中如果table的一条链表元素超过8个,且table元素的大小超过64就会红黑树化
注意点 -- Tips
set.add(new Dog("tom"));//OK
set.add(new Dog("tom"));//Ok 这两个是不同对象
set.add(new String("hsp"));//ok
set.add(new String("hsp"));//加入不了.
class Dog { //定义了 Dog 类
private String name;
public Dog(String name) {
this.name = name;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
'}';
}
}
这里我们需要了解为什么同样是new 一个是自定义的Dog类添加成功,另一个String类却没有成功,这里需要看String的源码进行分析
String源码分析
下面是String的hashCode的源码,首先是h被hash赋值了,而hash默认是0,这里的value数组其实就是String的字符数组。两个String对象值一样的话,value数组必然也是一样的。后面没啥好看的了,h是0,value数组完全相等,那么经过if语句里的一通操作,返回的h值还是一样的。
private final char value[];
private int hash; // Default to 0
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
我们可以得出结论:只要两个String对象的值相等,那么他们的hashCode一定是相等的。
LinkedHashSet
LinkedHashSet底层是维护了一个hash表和双向链表,它是按照hashcode值来决定存储位置,因此让其看起来是取出和加入的顺序一致,其同样不允许添加相同元素
这里当我们添加的元素是自定义的类时,我们可以重写其equals方法来判断符合哪些条件时判断两者为同一对象,下面进行代码演示
//这里是重写car的equals方法,当车的名字和价钱一致时认为是同一数据,就不重复添加
public class LinkedHashSetExercise {
public static void main(String[] args) {
LinkedHashSet linkedHashSet = new LinkedHashSet();
linkedHashSet.add(new Car("奥拓", 1000));//OK
linkedHashSet.add(new Car("奥迪", 300000));//OK
linkedHashSet.add(new Car("法拉利", 10000000));//OK
linkedHashSet.add(new Car("奥迪", 300000));//加入不了
linkedHashSet.add(new Car("保时捷", 70000000));//OK
linkedHashSet.add(new Car("奥迪", 300000));//加入不了
System.out.println("linkedHashSet=" + linkedHashSet);
}
}
class Car {
private String name;
private double price;
public Car(String name, double price) {
this.name = name;
this.price = price;
}
@Override
public String toString() {
return "Car{" +
"name='" + name + '\'' +
", price=" + price +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Car car = (Car) o;
return Double.compare(car.price, price) == 0 &&
Objects.equals(name, car.name);
}
@Override
public int hashCode() {
return Objects.hash(name, price);
}
}
Map接口介绍
1.Map和Collection并列存在,用于保存具有映射关系的数据,key-value
2.Map中的key不允许重复,原因和hashset一样,前面分析过源码
3.map中的value可以重复
4.map的key可以为null,value也可以为null,注意key为null只能有一个,value为null可以有多个
5.map中的key和value可以是任何引用类型的数据,会封装到hashmap$Node对象中
public class Map01 {
public static void main(String[] args) {
// 解读 Map 接口实现类的特点, 使用实现类 HashMap
//1. Map 与 Collection 并列存在。用于保存具有映射关系的数据:Key-Value(双列元素)
//2. Map 中的 key 和 value 可以是任何引用类型的数据,会封装到 HashMap$Node 对象中
//3. Map 中的 key 不允许重复,原因和 HashSet 一样,前面分析过源码. //4. Map 中的 value 可以重复
//5. Map 的 key 可以为 null, value 也可以为 null ,注意 key 为 null
// 只能有一个,value 为 null ,可以多个
//6. 常用 String 类作为 Map 的 key
//7. key 和 value 之间存在单向一对一关系,即通过指定的 key 总能找到对应的 value
Map map = new HashMap();
map.put("no1", "韩顺平");//k-v
map.put("no2", "张无忌");//k-v
map.put("no1", "张三丰");//当有相同的 k , 就等价于替换.
map.put("no3", "张三丰");//k-v
map.put(null, null); //k-v
map.put(null, "abc"); //等价替换
map.put("no4", null); //k-v
map.put("no5", null); //k-v
map.put(1, "赵敏");//k-v
map.put(new Object(), "金毛狮王");//k-v
map.put(new Object(), "金毛狮王");//true
// 通过 get 方法,传入 key ,会返回对应的 value
System.out.println(map.get("no2"));//张无忌
System.out.println("map=" + map);
}
}
Map接口常用方法
put:添加数据
remove:根据键删除映射关系
get:根据键获取值
size:获取元素个数
isEmpty:判断个数是否为 0
clear:清除 k-v
containsKey:查找键是否存在
public class MapMethod01 {
public static void main(String[] args) {
//演示 map 接口常用方法
Map map = new HashMap();
map.put("邓超", new Book("", 100));//OK
map.put("邓超", "孙俪");//替换-> 一会分析源码
map.put("王宝强", "马蓉");//OK
map.put("宋喆", "马蓉");//OK
map.put("刘令博", null);//OK
map.put(null, "刘亦菲");//OK
map.put(null, null);//OK
map.put("鹿晗", "关晓彤");//OK
map.put("hsp", "hsp 的老婆");
System.out.println("map=" + map);
// remove:根据键删除映射关系
map.remove(null);//如果有多个key为null 会一并删除
System.out.println("map=" + map);
// get:根据键获取值
Object val = map.get("鹿晗");
System.out.println("val=" + val);
// size:获取元素个数
System.out.println("k-v=" + map.size());
// isEmpty:判断个数是否为 0
System.out.println(map.isEmpty());//F
// clear:清除 k-v
map.clear();//全部删除
System.out.println("map=" + map);
// containsKey:查找键是否存在
System.out.println("结果=" + map.containsKey("hsp"));//t
}
}
class Book {
private String name;
private int num;
public Book(String name, int num) {
this.name = name;
this.num = num;
}
}
Map遍历方式
public class MapFor {
public static void main(String[] args) {
Map map = new HashMap();
map.put("邓超", "孙俪");
map.put("王宝强", "马蓉");
map.put("宋喆", "马蓉");
map.put("刘令博", null);
map.put(null, "刘亦菲");
map.put("鹿晗", "关晓彤");
//第一组: 先取出 所有的 Key , 通过 Key 取出对应的 Value
Set keyset = map.keySet(); //是set类型 有增强for循环和迭代器两种方式 这一步将所有的key放到keyset
//1.增强for循环
System.out.println("第一种方式");
for (Object key : keyset) { //将所有的key依次赋给key 再调用map方法 通过key取出value
System.out.println(key + " - " + map.get(key));
}
System.out.println();
//2.通过迭代器
System.out.println("第二种方式");
Iterator iterator = keyset.iterator();
while (iterator.hasNext()) {
Object next = iterator.next();//这个next是k-v 中的key
System.out.println(next + " - " + map.get(next));
}
System.out.println("====================");
//第二组方式 把所有的values取出
Collection values = map.values(); //是collection对象 有两种方式遍历
for (Object obj : values) {
System.out.println(obj);
}
System.out.println();
//迭代器,同Collection遍历
//第三组,使用EntrySet来获取 k-v
Set entrySet = map.entrySet();//类型是EntrySet<Map.Entry<K,V>>
//1.增强for循环
System.out.println("使用Entry的增强for循环");
for (Object entry : entrySet) {
//将entry转成Map.Entry
Map.Entry m = (Map.Entry) entry;
System.out.println(m.getKey() + " - " + m.getValue());
}
//2.迭代器
System.out.println("使用EntrySet迭代器遍历");
Iterator iterator1 = entrySet.iterator();
while (iterator1.hasNext()) {
Object next = iterator1.next();
System.out.println(next.getClass());//HashMap$Node---实现Map.Entry
//向下转型
Map.Entry m = (Map.Entry) next;
System.out.println(m.getKey() + " - " + m.getValue());
}
}
}
HashMap小结
HashMap底层机制和源码分析
代码演示
public class HashMapSource1 {
public static void main(String[] args) {
HashMap map = new HashMap();
map.put("java", 10);//ok
map.put("php", 10);//ok map.put("java", 20);//替换 value
System.out.println("map=" + map);//
/*解读 HashMap 的源码+图解
1. 执行构造器 new HashMap()
初始化加载因子 loadfactor = 0.75 HashMap$Node[] table = null
2. 执行 put 调用 hash 方法,计算 key 的 hash 值 (h = key.hashCode()) ^ (h >>> 16) public V put(K key, V value) {//K = "java" value = 10
return putVal(hash(key), key, value, false, true);
}
3. 执行 putVal
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;//辅助变量
//如果底层的 table 数组为 null, 或者 length =0 , 就扩容到 16
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//取出 hash 值对应的 table 的索引位置的 Node, 如果为 null, 就直接把加入的 k-v
//, 创建成一个 Node ,加入该位置即可if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null); else {
Node<K,V> e; K k;//辅助变量
// 如果 table 的索引位置的 key 的 hash 相同和新的 key 的 hash 值相同,
// 并 满足(table 现有的结点的 key 和准备添加的 key 是同一个对象 || equals 返回真)
// 就认为不能加入新的 k-v if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k)))) e = p;
else if (p instanceof TreeNode)//如果当前的 table 的已有的 Node 是红黑树,就按照红黑树的方式处
理
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);
//加入后,判断当前链表的个数,是否已经到 8 个,到 8 个,后
//就调用 treeifyBin 方法进行红黑树的转换
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash);
break;
}
if (e.hash == hash && value ((k = e.key) == key || (key != null && key.equals(k))))
//如果在循环比较过程中,发现有相同,就 break,就只是替换
break;
p = e;
}
}
if (e != null) { // existing mapping for key V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value; //替换,key 对应 value afterNodeAccess(e);
return oldValue;
}
}
++modCount;//每增加一个 Node ,就 size++
if (++size > threshold[12-24-48])//如 size > 临界值,就扩容
resize();
afterNodeInsertion(evict);
return null;
}
5. 关于树化(转成红黑树)
//如果 table 为 null ,或者大小还没有到 64,暂时不树化,而是进行扩容.
//否则才会真正的树化 -> 剪枝
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();
}
*/
}
}
HashTable介绍
HashMap和HashTable比较
TreeSet
public class TreeSet_ {
public static void main(String[] args) {
//解读
//1. 当我们使用无参构造器,创建 TreeSet 时,仍然是无序的
//2. 这里希望添加的元素,按照字符串大小来排序
//3. 使用 TreeSet 提供的一个构造器,可以传入一个比较器(匿名内部类)
// 并指定排序规则
//4. 简单看看源码
//解读
//TreeSet treeSet = new TreeSet();
TreeSet treeSet = new TreeSet(new Comparator() {
@Override
//return调用字符串的compareTo方法进行字符串比较
public int compare(Object o1, Object o2) {
return ((String) o1).length() - ((String) o2).length();
}
});
//解读
/*
1. 构造器把传入的比较器对象,赋给了 TreeSet 的底层的 TreeMap 的属性 this.comparator
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
2. 在 调用 treeSet.add("tom"), 在底层会执行到
if (cpr != null) {//cpr 就是我们的匿名内部类(对象)
do {
parent = t;
//动态绑定到我们的匿名内部类(对象)compare
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else //如果相等,即返回 0,这个 Key 就没有加入
return t.setValue(value);
} while (t != null);
}
*/
//添加数据.
treeSet.add("jack");
treeSet.add("tom");//3
treeSet.add("sp");
treeSet.add("a");
treeSet.add("abc");//3 不会添加进去 因为修改了compare方法,abc和tom长度相同 返回0
System.out.println("treeSet=" + treeSet);//不做处理是无序输出
}
}
TreeMap
public class TreeMap_ {
public static void main(String[] args) {
//TreeMap treeMap = new TreeMap();
TreeMap treeMap = new TreeMap(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
return ((String) o1).length()-((String) o2).length();
}
});
//使用默认的构造器,创建 TreeMap, 是无序的(也没有排序)
/*
要求:按照传入的 k(String) 的大小进行排序
*/
treeMap.put("123",null);//key不能是null
treeMap.put("jack", "杰克");
treeMap.put("tom", "汤姆");
treeMap.put("kristina", "克瑞斯提诺");
treeMap.put("smith", "斯密斯");
treeMap.put("hsp", "韩顺平");//如果把return改成String o1.length那么就会替换汤姆
System.out.println("treemap=" + treeMap);
}
}
Map 接口实现类-Properties
1.Properties类继承了Hashtable类并实现了map接口,也使用一种键值对的方式存储数据
2.properties还可以用于从xxx.properties文件中,加载数据到properties类对象,并进行读取和修改,一般xxx.properties为配置文件(IO流中涉及)
3.使用方式与hashtable基本一致
public class HashTable {
public static void main(String[] args) {
//解读
//1. Properties 继承 Hashtable
//2. 可以通过 k-v 存放数据,当然 key 和 value 不能为 null
//增加
Properties properties = new Properties();
//properties.put(null, "abc");//抛出 空指针异常
//properties.put("abc", null); //抛出 空指针异常
properties.put("john", 100);//k-v
//底层初始化一个数组 Hashtable$Entry 是 Entry是Hashtable的内部类 初始化长度是11 临界值是11*0.75 = 8
properties.put("lucy", 100);
properties.put("lic", 100);
properties.put("lic", 88);//如果有相同的 key , value 被替换
System.out.println("properties=" + properties);
//通过 k 获取对应值
System.out.println(properties.get("lic"));//88
//删除
properties.remove("lic");
System.out.println("properties=" + properties);
//修改
properties.put("john", "约翰");
System.out.println("properties=" + properties);
Hashtable hashtable = new Hashtable();
hashtable.put("jack",123);
System.out.println(hashtable);
hashtable.remove("jack");
System.out.println(hashtable);
/**
* 扩容机制
* 当达到临界值的时候是在原来的基础上*2 + 1
* 例如第一次是11 达到8之后 就是 11*2 + 1 = 23
*
*/
}
}
总结:开发中如何选择集合类
1.判断存储类型,是一组对象(单列)还是键值对(双列)
2.一组对象(单列)Collection接口
允许重复:List
增删多:LinkedList(双向链表,增加删除快)
改查多:Arraylist
不允许重复:Set
无序:HashSet
排序:TreeSet
插入和取出顺序一致:LinkedHashSet,底层维护数组和双向链表
3.一组键值对(双列)Map
键无序:HashMap
键排序:TreeSet
键插入和取出顺序一致:LinkedHashMap
读取文件:Properties