目录
集合框架体系图
集合的理解和好处
Collection接口和常用方法
Coollection接口实现类的特点
Collection接口和常用方法
Collection接口遍历元素方式
Collection接口遍历元素方式1-使用Iterator(迭代器)
Collection接口遍历元素方式2-For循环增强
Collection接口遍历元素方式3-普通for循环
List接口
List接口基本介绍
List接口的常用方法
ArrayList的注意事项
ArrayList的底层操作机制源码分析
Vector底层结构和源码刨析
LinkedList底层结构
ArrayList 和 LinkedList比较
如何选择ArrayList 和 LinkedList:
Set接口和常用方法
Set接口基本介绍
Set接口的常用方法
Set接口的遍历方式
Set接口实现类-HashSet
HashSet底层机制说明
HashSet快速入门案例
LinkedHashSet
LinkedHashSet的全面说明
LinkedHashSet底层源码刨析
Map接口
Map体系的继承图
Map接口实现类的特点【很实用】
HashMap的key和Value源码刨析
Map接口的常用方法
Map接口遍历方法
HashMap小结
HashMap底层机制及源码刨析
扩容机制[和HashSet相同]
Hashtable 和 HashMap对比
Map接口实现类-Properties
总结-开发中如何选择集合实现类
Collections工具类
分析HashSet和TreeSet 分别如何实现去重的
集合框架体系图
单例集合
双例集合
集合的理解和好处
前面我们保存多个数据使用的是数组,那么数组有不足的地方,我们分析一下。
- 数组
- 长度开始时必须指定,而且一旦指定,不能更改
- 保存的必须为同一类型的元素
- 集合
- 可以动态保存任意多个对象,使用比较方便!
- 提供一系列方便的操作对象的方法:add,remove,set,get等
Collection接口和常用方法
Coollection接口实现类的特点
public interface Collection
- collection实现子类可以存放多个元素,每个元素可以是Object
- 有些Collection的实现类,有些是有序的(List),有些是无序的(Set)
- Collection接口没有直接的实现子类,是通过它的子接口Set和List来实现的
Collection接口和常用方法
Collection接口常用方法,以实现子类Array'List来演示
- add:添加单个元素
- remove:删除指定元素
- contains:查找元素是否存在
- size:获取元素个数
- isEmpty:判断是否为空
- clear:清空
- addAll:添加多个元素
- containsAll:查找多个元素是否都存在
- removeAll:删除多个元素
- 说明: 以ArrayList来演示
package com.hspedu.collection_;
import java.util.ArrayList;
import java.util.List;
public class CollectionMethod {
@SuppressWarnings({"all"})
public static void main(String[] args) {
List list = new ArrayList();
// add:添加单个元素
list.add("jack");
list.add(10);//list.add(new Integer(10))
list.add(true);
System.out.println("list=" + list);
// remove:删除指定元素
//list.remove(0);//删除第一个元素
list.remove(true);//指定删除某个元素
System.out.println("list=" + list);
// contains:查找元素是否存在
System.out.println(list.contains("jack"));//T
// size:获取元素个数
System.out.println(list.size());//2
// isEmpty:判断是否为空
System.out.println(list.isEmpty());//F
// clear:清空
list.clear();
System.out.println("list=" + list);
// addAll:添加多个元素
ArrayList list2 = new ArrayList();
list2.add("红楼梦");
list2.add("三国演义");
list.addAll(list2);
System.out.println("list=" + list);
// containsAll:查找多个元素是否都存在
System.out.println(list.containsAll(list2));//T
// removeAll:删除多个元素
list.add("聊斋");
list.removeAll(list2);
System.out.println("list=" + list);//[聊斋]
// 说明:以ArrayList实现类来演示.
}
}
Collection接口遍历元素方式
Collection接口遍历元素方式1-使用Iterator(迭代器)
基本介绍
- Iterator对象称为迭代器,主要用于遍历Collection 集合中的元素
- 所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了Iterator接口的对象,即可以返回一个迭代器。
- Iterator 仅用于遍历集合,Iterator 本身并不存放对象。
迭代器的执行原理
Iterator应用案例
package com.hspedu.collection_;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class CollectionIterator {
@SuppressWarnings({"all"})
public static void main(String[] args) {
Collection col = new ArrayList();
col.add(new Book("三国演义", "罗贯中", 10.1));
col.add(new Book("小李飞刀", "古龙", 5.1));
col.add(new Book("红楼梦", "曹雪芹", 34.6));
//System.out.println("col=" + col);
//现在老师希望能够遍历 col集合
//1. 先得到 col 对应的 迭代器
Iterator iterator = col.iterator();
//2. 使用while循环遍历
// while (iterator.hasNext()) {//判断是否还有数据
// //返回下一个元素,类型是Object
// Object obj = iterator.next();
// System.out.println("obj=" + obj);
// }
//老师教大家一个快捷键,快速生成 while => itit
//显示所有的快捷键的的快捷键 ctrl + j
while (iterator.hasNext()) {
Object obj = iterator.next();
System.out.println("obj=" + obj);
}
//3. 当退出while循环后 , 这时iterator迭代器,指向最后的元素
// iterator.next();//NoSuchElementException
//4. 如果希望再次遍历,需要重置我们的迭代器
iterator = col.iterator();
System.out.println("===第二次遍历===");
while (iterator.hasNext()) {
Object obj = iterator.next();
System.out.println("obj=" + obj);
}
}
}
class Book {
private String name;
private String author;
private double price;
public Book(String name, String author, double price) {
this.name = name;
this.author = author;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
@Override
public String toString() {
return "Book{" +
"name='" + name + ''' +
", author='" + author + ''' +
", price=" + price +
'}';
}
}
Collection接口遍历元素方式2-For循环增强
介绍
增强for循环,可以代替Iterator迭代器,特点:增强for就是简化版的iterator本质一样。只能用于遍历集合或数组。
语法
for(元素类型 元素名 :集合名或数组名){
访问元素
}
Collection接口遍历元素方式3-普通for循环
- 语法
for(int i=0;i
Object object=list.get(i);
System.out.println(object);
}
List接口
List接口基本介绍
- List接口是 Collection 接口的子接口
- List集合类中元素有序(即添加顺序和取出顺序一致),且可重复。
- List集合中的每个元素都有其对应的顺序索引,即支持索引。
- List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素。
- Jdk API中List接口的实现类常用的有:ArrayList, LinkedList和Vector。
package com.hspedu.list_;
import java.util.ArrayList;
import java.util.List;
public class List_ {
@SuppressWarnings({"all"})
public static void main(String[] args) {
//1. List集合类中元素有序(即添加顺序和取出顺序一致)、且可重复 [案例]
List list = new ArrayList();
list.add("jack");
list.add("tom");
list.add("mary");
list.add("hsp");
list.add("tom");
System.out.println("list=" + list);
//2. List集合中的每个元素都有其对应的顺序索引,即支持索引
// 索引是从0开始的
System.out.println(list.get(3));//hsp
//3.
}
}
List接口的常用方法
- List集合里添加了一些根据索引来操作集合元素的方法
- List接口是 Collection 接口的子接口
- List集合类中元素有序(即添加顺序和取出顺序一致),且可重复。
- List集合中的每个元素都有其对应的顺序索引,即支持索引。
- List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素。
- Jdk API中List接口的实现类常用的有:ArrayList, LinkedList和Vector。
package com.hspedu.list_;
import java.util.ArrayList;
import java.util.List;
public class List_ {
@SuppressWarnings({"all"})
public static void main(String[] args) {
//1. List集合类中元素有序(即添加顺序和取出顺序一致)、且可重复 [案例]
List list = new ArrayList();
list.add("jack");
list.add("tom");
list.add("mary");
list.add("hsp");
list.add("tom");
System.out.println("list=" + list);
//2. List集合中的每个元素都有其对应的顺序索引,即支持索引
// 索引是从0开始的
System.out.println(list.get(3));//hsp
//3.
}
}
List接口的常用方法
- List集合里添加了一些根据索引来操作集合元素的方法
package com.hspedu.list_;
import java.util.ArrayList;
import java.util.List;
public class ListMethod {
@SuppressWarnings({"all"})
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位置的元素
//说过
// 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位置的元素,并返回此元素
list.remove(0);
System.out.println("list=" + list);
// Object set(int index, Object ele):设置指定index位置的元素为ele , 相当于是替换.
list.set(1, "玛丽");
System.out.println("list=" + list);
// List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex位置的子集合
// 注意返回的子集合 fromIndex <= subList < toIndex
List returnlist = list.subList(0, 2);
System.out.println("returnlist=" + returnlist);
b
}
}
ArrayList的注意事项
- permits all elements,including null,ArrayList 可以加入null,并且多个
- ArrayList 是由数组来实现数据存储的
- ArrayList 基本等同于Vector,除了ArrayList是线程不安全(执行效率高)看源码。在多线程情况下,不建议使用ArrayList
ArrayList的底层操作机制源码分析
- ArrayList中维护了一个Obejct类型的数组elementData. transient Obect[] elementData; //trnsient表示瞬间,短暂的,表示该属性不会被序列化
- 当创建对象时,如果使用的是无参构造器,则初始elementData容量为0(Jdk7是10)
- 当添加元素时:先判断是否需要扩容,如果需要扩容,则调用grow方法,否则直接添加元素到合适位置
- 如果使用的是无参构造器,如果第一次添加,需要扩容的话,则扩容elementData为10,如果需要再次扩容的话,则扩容elementData为1.5倍。
- 如果使用的是指定容量capacity的构造器,则初始elementData容量为capacity
- 如果使用的是指定容量capacity的构造器,如果需要扩容,则直接扩容elementData为1.5倍。
- ArrayList中维护了一个Obejct类型的数组elementData. transient Obect[] elementData; //trnsient表示瞬间,短暂的,表示该属性不会被序列化
- 当创建对象时,如果使用的是无参构造器,则初始elementData容量为0(Jdk7是10)
- 当添加元素时:先判断是否需要扩容,如果需要扩容,则调用grow方法,否则直接添加元素到合适位置
- 如果使用的是无参构造器,如果第一次添加,需要扩容的话,则扩容elementData为10,如果需要再次扩容的话,则扩容elementData为1.5倍。
- 如果使用的是指定容量capacity的构造器,则初始elementData容量为capacity
- 如果使用的是指定容量capacity的构造器,如果需要扩容,则直接扩容elementData为1.5倍。
Vector底层结构和源码刨析
Vector的基本介绍
- Vector类的定义说明
- Vector底层也是一个对象数组 , protected Object[] elementData;
- Vector 是线程同步的,即线程安全,Vectot类的操作方法带有synchronized
- 在开发中,需要线程同步安全时,考虑使用Vector
package com.hspedu.list_;
import java.util.Vector;
@SuppressWarnings({"all"})
public class Vector_ {
public static void main(String[] args) {
//无参构造器
//有参数的构造
Vector vector = new Vector(8);
for (int i = 0; i < 10; i++) {
vector.add(i);
}
vector.add(100);
System.out.println("vector=" + vector);
//1. new Vector() 底层
}
}
Vector和ArrayList的比较
LinkedList底层结构
LinkedList的全面说明
- LinkedList底层实现了双向链表和双端队列特点
- 可以添加任意元素(元素可以重复),包括null
- 线程不安全,没有实现同步
LinkedList的底层操作机制
- LinkedList底层维护了一个双向链表
- LinkedList中维护了两个属性first和last分别指向 首节点和尾节点
- 每个节点(Node对象),里面又维护了prev,next,item三个属性。其中通过prev指向前一个,通过next指向后一个节点。最终实现双向链表
- 所以LinkedList的元素添加和删除,不是通过数组完成的,相对来说效率较高。
- 模拟一个简单的双向链表
package com.hspedu.list_;
public class LinkedList01 {
public static void main(String[] args) {
//模拟一个简单的双向链表
Node jack = new Node("jack");
Node tom = new Node("tom");
Node hsp = new Node("老韩");
//连接三个结点,形成双向链表
//jack -> tom -> hsp
jack.next = tom;
tom.next = hsp;
//hsp -> tom -> jack
hsp.pre = tom;
tom.pre = jack;
Node first = jack;//让first引用指向jack,就是双向链表的头结点
Node last = hsp; //让last引用指向hsp,就是双向链表的尾结点
//演示,从头到尾进行遍历
System.out.println("===从头到尾进行遍历===");
while (true) {
if(first == null) {
break;
}
//输出first 信息
System.out.println(first);
first = first.next;
}
//演示,从尾到头的遍历
System.out.println("====从尾到头的遍历====");
while (true) {
if(last == null) {
break;
}
//输出last 信息
System.out.println(last);
last = last.pre;
}
//演示链表的添加对象/数据,是多么的方便
//要求,是在 tom --------- 老韩直接,插入一个对象 smith
//1. 先创建一个 Node 结点,name 就是 smith
Node smith = new Node("smith");
//下面就把 smith 加入到双向链表了
smith.next = hsp;
smith.pre = tom;
hsp.pre = smith;
tom.next = smith;
//让first 再次指向jack
first = jack;//让first引用指向jack,就是双向链表的头结点
System.out.println("===从头到尾进行遍历===");
while (true) {
if(first == null) {
break;
}
//输出first 信息
System.out.println(first);
first = first.next;
}
last = hsp; //让last 重新指向最后一个结点
//演示,从尾到头的遍历
System.out.println("====从尾到头的遍历====");
while (true) {
if(last == null) {
break;
}
//输出last 信息
System.out.println(last);
last = last.pre;
}
}
}
//定义一个Node 类,Node 对象 表示双向链表的一个结点
class Node {
public Object item; //真正存放数据
public Node next; //指向后一个结点
public Node pre; //指向前一个结点
public Node(Object name) {
this.item = name;
}
public String toString() {
return "Node name=" + item;
}
}
LinkedList增删改查案例
package com.hspedu.list_;
import java.util.Iterator;
import java.util.LinkedList;
@SuppressWarnings({"all"})
public class LinkedListCRUD {
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));
}
//老韩源码阅读.
}
}
ArrayList 和 LinkedList比较
如何选择ArrayList 和 LinkedList:
- 如果我们改查的操作多,选择ArrayList
- 如果我们增删的操作多,选择LinkedList
- 一般来说,在程序中,80%-90%都是查询,因此大部分情况下会选择ArrayList
- 在一个项目中,根据业务灵活选择,也可能这样,一个模块使用的是ArrayList,另外一个模块是LinkedList。
Set接口和常用方法
Set接口基本介绍
- 无序(添加和取出的顺序不一致),没有索引
- 不允许重复元素,所以做多包含一个null
- 注意:取出的顺序的顺序虽然不是添加的顺序,但是他是固定的
Set接口的常用方法
Set接口基本介绍
- 无序(添加和取出的顺序不一致),没有索引
- 不允许重复元素,所以做多包含一个null
- 注意:取出的顺序的顺序虽然不是添加的顺序,但是他是固定的
Set接口的常用方法
和List接口一样,Set接口也是Collextion'的子接口,因此,常用方法和Collection接口一样。
Set接口的遍历方式
同Collection的遍历方式一样,因为Set接口是Collection接口的子接口。
- 可以使用迭代器
- 增强for
- 不能使用普通for循环来遍历
Set接口实现类-HashSet
- HashSet实现了Set接口
- HashSet实际上是HashMap,看下源码。
public HashSet(){
map=new HashMap<>();
}
- 可以存放null值,但是只能由一个null
- HashSet不保证元素是有序的,取决于hash后,再确定索引的结郭。
- 不能有重复元素/对象. 在前面Set 接口使用已经讲过
package com.hspedu.set_;
import java.util.HashSet;
@SuppressWarnings({"all"})
public class HashSet01 {
public static void main(String[] args) {
HashSet set = new HashSet();
//说明
//1. 在执行add方法后,会返回一个boolean值
//2. 如果添加成功,返回 true, 否则返回false
//3. 可以通过 remove 指定删除哪个对象
System.out.println(set.add("john"));//T
System.out.println(set.add("lucy"));//T
System.out.println(set.add("john"));//F
System.out.println(set.add("jack"));//T
System.out.println(set.add("Rose"));//T
set.remove("john");
System.out.println("set=" + set);//3个
//
set = new HashSet();
System.out.println("set=" + set);//0
//4 Hashset 不能添加相同的元素/数据?
set.add("lucy");//添加成功
set.add("lucy");//加入不了
set.add(new Dog("tom"));//OK
set.add(new Dog("tom"));//Ok
System.out.println("set=" + set);
//在加深一下. 非常经典的面试题.
//看源码,做分析, 先给小伙伴留一个坑,以后讲完源码,你就了然
//去看他的源码,即 add 到底发生了什么?=> 底层机制.
set.add(new String("hsp"));//ok
set.add(new String("hsp"));//加入不了.
System.out.println("set=" + set);
}
}
class Dog { //定义了Dog类
private String name;
public Dog(String name) {
this.name = name;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + ''' +
'}';
}
}
HashSet底层机制说明
- 分析HashSet底层是HashMap,HashMap底层是(数组+单向链表+红黑树)
- HashSet 底层是 HashMap
- 添加一个元素时,先得到hash值 -会转成->索引值
- 找到存储数据table,看到这个索引位置是否已经存放的有元素
- 如果没有,直接加入
- 如果有 ,调用equals 比较,如果相同,就放弃添加,如果不相同,则添加到最后
- 在Java8中,如果一条链表的元素个数到达TREEIFY_THRESHOLD(默认是8),并且table的大小 >=MIN_TREEIFY_CAPACITY(默认64)就会进行树化(红黑树)。
package com.hspedu.set_;
@SuppressWarnings({"all"})
public class HashSetStructure {
public static void main(String[] args) {
//模拟一个HashSet的底层 (HashMap 的底层结构)
//1. 创建一个数组,数组的类型是 Node[]
//2. 有些人,直接把 Node[] 数组称为 表
Node[] table = new Node[16];
//3. 创建结点
Node john = new Node("john", null);
table[2] = john;
Node jack = new Node("jack", null);
john.next = jack;// 将jack 结点挂载到john
Node rose = new Node("Rose", null);
jack.next = rose;// 将rose 结点挂载到jack
Node lucy = new Node("lucy", null);
table[3] = lucy; // 把lucy 放到 table表的索引为3的位置.
System.out.println("table=" + table);
}
}
class Node { //结点, 存储数据, 可以指向下一个结点,从而形成链表
Object item; //存放数据
Node next; // 指向下一个结点
public Node(Object item, Node next) {
this.item = item;
this.next = next;
}
}
HashSet快速入门案例
package com.hspedu.set_;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Objects;
import java.util.Set;
@SuppressWarnings({"all"})
public class HashSetExercise {
public static void main(String[] args) {
HashSet hashSet = new HashSet();
hashSet.add(new Employee("milan", 18));//ok
hashSet.add(new Employee("smith", 28));//ok
hashSet.add(new Employee("milan", 18));//加入不成功.
//回答,加入了几个? 3个
System.out.println("hashSet=" + hashSet);
}
}
//创建Employee
class Employee {
private String name;
private int age;
public Employee(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + ''' +
", age=" + age +
'}';
}
public void setAge(int age) {
this.age = age;
}
//如果name 和 age 值相同,则返回相同的hash值
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
return age == employee.age &&
Objects.equals(name, employee.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
LinkedHashSet
LinkedHashSet的全面说明
- LinkedHashSet 是HashSet 的子类
- LinkedHashSet 底层是一个 LinkedHashMap,底层维护了一个数组 +双向链表
- LinkedHashSet 根据元素的 hashCode 值来决定元素的存储位置,同时使用链表维护元素的次序(图),这使得元素看起来是以插入顺序保存的。
- LinkedHashSet 不允许添加重复元素
- LinkedHashSet 是HashSet 的子类
- LinkedHashSet 底层是一个 LinkedHashMap,底层维护了一个数组 +双向链表
- LinkedHashSet 根据元素的 hashCode 值来决定元素的存储位置,同时使用链表维护元素的次序(图),这使得元素看起来是以插入顺序保存的。
- LinkedHashSet 不允许添加重复元素
LinkedHashSet底层源码刨析
package com.hspedu.set_;
import java.util.LinkedHashSet;
import java.util.Set;
@SuppressWarnings({"all"})
public class LinkedHashSetSource {
public static void main(String[] args) {
//分析一下LinkedHashSet的底层机制
Set set = new LinkedHashSet();
set.add(new String("AA"));
set.add(456);
set.add(456);
set.add(new Customer("刘", 1001));
set.add(123);
set.add("HSP");
System.out.println("set=" + set);
//1. LinkedHashSet 加入顺序和取出元素/数据的顺序一致
//2. LinkedHashSet 底层维护的是一个LinkedHashMap(是HashMap的子类)
//3. LinkedHashSet 底层结构 (数组table+双向链表)
//4. 添加第一次时,直接将 数组table 扩容到 16 ,存放的结点类型是 LinkedHashMap$Entry
//5. 数组是 HashMap$Node[] 存放的元素/数据是 LinkedHashMap$Entry类型
}
}
class Customer {
private String name;
private int no;
public Customer(String name, int no) {
this.name = name;
this.no = no;
}
}
Map接口
Map体系的继承图
Map接口实现类的特点【很实用】
- 注意:这里讲的是JDK8的Map接口特点
package com.hspedu.map_;
import java.util.HashMap;
import java.util.Map;
@SuppressWarnings({"all"})
public class Map_ {
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
// 通过get 方法,传入 key ,会返回对应的value
System.out.println(map.get("no2"));//张无忌
System.out.println("map=" + map);
}
}
HashMap的key和Value源码刨析
package com.hspedu.map_;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
@SuppressWarnings({"all"})
public class MapSource_ {
public static void main(String[] args) {
Map map = new HashMap();
map.put("no1", "韩顺平");//k-v
map.put("no2", "张无忌");//k-v
map.put(new Car(), new Person());//k-v
//老韩解读
//1. k-v 最后是 HashMap$Node node = newNode(hash, key, value, null)
//2. k-v 为了方便程序员的遍历,还会 创建 EntrySet 集合 ,该集合存放的元素的类型 Entry, 而一个Entry
// 对象就有k,v EntrySet> 即: transient Set> entrySet;
//3. entrySet 中, 定义的类型是 Map.Entry ,但是实际上存放的还是 HashMap$Node
// 这时因为 static class Node implements Map.Entry
//4. 当把 HashMap$Node 对象 存放到 entrySet 就方便我们的遍历, 因为 Map.Entry 提供了重要方法
// K getKey(); V getValue();
Set set = map.entrySet();
System.out.println(set.getClass());// HashMap$EntrySet
for (Object obj : set) {
//System.out.println(obj.getClass()); //HashMap$Node
//为了从 HashMap$Node 取出k-v
//1. 先做一个向下转型
Map.Entry entry = (Map.Entry) obj;
System.out.println(entry.getKey() + "-" + entry.getValue() );
}
Set set1 = map.keySet();
System.out.println(set1.getClass());
Collection values = map.values();
System.out.println(values.getClass());
}
}
class Car {
}
class Person{
}
Map接口的常用方法
- put:添加
- remove:根据键删除映射关系
- get:革命家获取值
- size:获取元素个数
- isEmpty:判断个数是否为0
- clear:清除
- containsKey:查找键是否存在
package com.hspedu.map_;
import java.util.HashMap;
import java.util.Map;
@SuppressWarnings({"all"})
public class MapMethod {
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("鹿晗", "关晓彤");//OK
map.put("hsp", "hsp的老婆");
System.out.println("map=" + map);
// remove:根据键删除映射关系
map.remove(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接口遍历方法
package com.hspedu.map_;
import java.util.*;
@SuppressWarnings({"all"})
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();
//(1) 增强for
System.out.println("-----第一种方式-------");
for (Object key : keyset) {
System.out.println(key + "-" + map.get(key));
}
//(2) 迭代器
System.out.println("----第二种方式--------");
Iterator iterator = keyset.iterator();
while (iterator.hasNext()) {
Object key = iterator.next();
System.out.println(key + "-" + map.get(key));
}
//第二组: 把所有的values取出
Collection values = map.values();
//这里可以使用所有的Collections使用的遍历方法
//(1) 增强for
System.out.println("---取出所有的value 增强for----");
for (Object value : values) {
System.out.println(value);
}
//(2) 迭代器
System.out.println("---取出所有的value 迭代器----");
Iterator iterator2 = values.iterator();
while (iterator2.hasNext()) {
Object value = iterator2.next();
System.out.println(value);
}
//第三组: 通过EntrySet 来获取 k-v
Set entrySet = map.entrySet();// EntrySet>
//(1) 增强for
System.out.println("----使用EntrySet 的 for增强(第3种)----");
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 的 迭代器(第4种)----");
Iterator iterator3 = entrySet.iterator();
while (iterator3.hasNext()) {
Object entry = iterator3.next();
//System.out.println(next.getClass());//HashMap$Node -实现-> Map.Entry (getKey,getValue)
//向下转型 Map.Entry
Map.Entry m = (Map.Entry) entry;
System.out.println(m.getKey() + "-" + m.getValue());
}
}
}
package com.hspedu.map_;
import java.util.*;
@SuppressWarnings({"all"})
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();
//(1) 增强for
System.out.println("-----第一种方式-------");
for (Object key : keyset) {
System.out.println(key + "-" + map.get(key));
}
//(2) 迭代器
System.out.println("----第二种方式--------");
Iterator iterator = keyset.iterator();
while (iterator.hasNext()) {
Object key = iterator.next();
System.out.println(key + "-" + map.get(key));
}
//第二组: 把所有的values取出
Collection values = map.values();
//这里可以使用所有的Collections使用的遍历方法
//(1) 增强for
System.out.println("---取出所有的value 增强for----");
for (Object value : values) {
System.out.println(value);
}
//(2) 迭代器
System.out.println("---取出所有的value 迭代器----");
Iterator iterator2 = values.iterator();
while (iterator2.hasNext()) {
Object value = iterator2.next();
System.out.println(value);
}
//第三组: 通过EntrySet 来获取 k-v
Set entrySet = map.entrySet();// EntrySet>
//(1) 增强for
System.out.println("----使用EntrySet 的 for增强(第3种)----");
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 的 迭代器(第4种)----");
Iterator iterator3 = entrySet.iterator();
while (iterator3.hasNext()) {
Object entry = iterator3.next();
//System.out.println(next.getClass());//HashMap$Node -实现-> Map.Entry (getKey,getValue)
//向下转型 Map.Entry
Map.Entry m = (Map.Entry) entry;
System.out.println(m.getKey() + "-" + m.getValue());
}
}
}
HashMap小结
- Map接口的常用实现类:HashMap, Hashtable和Properties。
- HashMap 是Map 接口使用频率最高的实现类
- HashMap 是key-val 对的方式来存储数据
- Key 不能重复 但是值可以重复,允许使用null和null值。
- 如果添加相同的key,则会覆盖原理的key-val,等同于修改(key不会替换,val会替换)
- 与HashSet一样,不保证映射的顺序,因为底层是以hash表的方式来存储的
- HashMap没有实现同步,因此线程不安全的。
HashMap底层机制及源码刨析
扩容机制[和HashSet相同]
- HashMap底层维护了Node类型的数组table,默认为null
- 当创建对象时,将加载因子(loadfactor)初始化为0.75
- 当添加key-val时,通过key的哈希值得到在table的索引。然后判断该索引处是否有元素,如果没有元素直接添加。如果该索引处有元素,继续判断元素的key是否准备加入的key相等,如果相等,则直接替换val;如果不相等需要哦按段是树结构还是链表结构,做出相应处理。如果添加时发现容量不够,则需要扩容。
- 第1次添加,则需要扩容table容量为16,临界值(threshold)为12
- 以后再扩容,则需要扩容table容量为原来的2倍,临界值为原来的2倍,即24,依次类推
- 在Java8中,如果一条链表的元素个数超过TREEIFY_THRESHOLD(默认是8),并且table的大小 >=MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树)。
package com.hspedu.map_;
import java.util.HashMap;
@SuppressWarnings({"all"})
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);//
}
}
Map接口实现类- Hashtable
HashTable的基本介绍
- 存放的元素是键值对:即K-V
- hashtable的键和值都不能为null
- hashTable 使用方法基本上和HashMap一样
- hashtable 是线程安全的,hashMap 是线程不安全的
package com.hspedu.map_;
import java.util.Hashtable;
@SuppressWarnings({"all"})
public class HashTableExercise {
public static void main(String[] args) {
Hashtable table = new Hashtable();//ok
table.put("john", 100); //ok
//table.put(null, 100); //异常 NullPointerException
//table.put("john", null);//异常 NullPointerException
table.put("lucy", 100);//ok
table.put("lic", 100);//ok
table.put("lic", 88);//替换
table.put("hello1", 1);
table.put("hello2", 1);
table.put("hello3", 1);
table.put("hello4", 1);
table.put("hello5", 1);
table.put("hello6", 1);
System.out.println(table);
//简单说明一下Hashtable的底层
//1. 底层有数组 Hashtable$Entry[] 初始化大小为 11
//2. 临界值 threshold 8 = 11 * 0.75
//3. 扩容: 按照自己的扩容机制来进行即可.
//4. 执行 方法 addEntry(hash, key, value, index); 添加K-V 封装到Entry
//5. 当 if (count >= threshold) 满足时,就进行扩容
//5. 按照 int newCapacity = (oldCapacity << 1) + 1; 的大小扩容.
}
}
Hashtable 和 HashMap对比
Map接口实现类-Properties
基本介绍
- Properties类继承自Hashtable类并且实现了Map接口,也是使用一种键值对的形式来保存数据
- 他的使用特点和Hashtable类似
- Properties 还可以用于 从 xxx.properties 文件中,加载数据到Properties类对象,并进行读取和修改
- 说明:工作后 xxx.properties 文件通常作为配置文件,这个知识点在IO流举例。
package com.hspedu.map_;
import java.util.Properties;
@SuppressWarnings({"all"})
public class Properties_ {
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
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);
}
}
总结-开发中如何选择集合实现类
在开发中,选择什么集合实现类,注意取决于业务操作特点,然后根据集合实现类特性进行选择,分析如下:
- 先判断存储的类型(一组对象【单列】或一组键值对【双列】)
- 一组对象:Collection接口
允许重复:List
增删多:LinkedList[底层维护了一个双向链表]
改查多:ArrayList[底层维护 Object类型的可变数组]
不允许重复:Set
无序: HashSet [底层是HashMap,维护了一个哈希表 即(数组+链表+红黑树)]
排序:TreeSet
插入和取出顺序一致:LinkedHashSet ,维护数组+双向链表
- 一组键值对: Map
键无序: HashMap[底层是:哈希表 jdk7:数组+链表,jdk8:数组+链表+红黑树]
键排序: TreeMap
键插入和取出顺序一致:LinkedHashMap
读取文件 Properties
Collections工具类
package com.hspedu.collections_;
import java.util.*;
@SuppressWarnings({"all"})
public class Collections_ {
public static void main(String[] args) {
//创建ArrayList 集合,用于测试.
List list = new ArrayList();
list.add("tom");
list.add("smith");
list.add("king");
list.add("milan");
list.add("tom");
// reverse(List):反转 List 中元素的顺序
Collections.reverse(list);
System.out.println("list=" + list);
// shuffle(List):对 List 集合元素进行随机排序
// for (int i = 0; i < 5; i++) {
// Collections.shuffle(list);
// System.out.println("list=" + list);
// }
// sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序
Collections.sort(list);
System.out.println("自然排序后");
System.out.println("list=" + list);
// sort(List,Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
//我们希望按照 字符串的长度大小排序
Collections.sort(list, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
//可以加入校验代码.
return ((String) o2).length() - ((String) o1).length();
}
});
System.out.println("字符串长度大小排序=" + list);
// swap(List,int, int):将指定 list 集合中的 i 处元素和 j 处元素进行交换
//比如
Collections.swap(list, 0, 1);
System.out.println("交换后的情况");
System.out.println("list=" + list);
//Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
System.out.println("自然顺序最大元素=" + Collections.max(list));
//Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素
//比如,我们要返回长度最大的元素
Object maxObject = Collections.max(list, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
return ((String)o1).length() - ((String)o2).length();
}
});
System.out.println("长度最大的元素=" + maxObject);
//Object min(Collection)
//Object min(Collection,Comparator)
//上面的两个方法,参考max即可
//int frequency(Collection,Object):返回指定集合中指定元素的出现次数
System.out.println("tom出现的次数=" + Collections.frequency(list, "tom"));
//void copy(List dest,List src):将src中的内容复制到dest中
ArrayList dest = new ArrayList();
//为了完成一个完整拷贝,我们需要先给dest 赋值,大小和list.size()一样
for(int i = 0; i < list.size(); i++) {
dest.add("");
}
//拷贝
Collections.copy(dest, list);
System.out.println("dest=" + dest);
//boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换 List 对象的所有旧值
//如果list中,有tom 就替换成 汤姆
Collections.replaceAll(list, "tom", "汤姆");
System.out.println("list替换后=" + list);
}
}



