JAVA集合框架(四)
文章目录
五. Map接口和实现类
1. Map集合概述
Map接口的特点:
- 用于存储任意键值对(Key-Value)
- 键:无序、无下标、不允许重复(唯一)
- 值:无序、无下标、允许重复
方法:
- V put(K key,V value) //将对象存入到集合中,关联键值。key重复则覆盖原值。
- Object get (Object key) //根据键获取对应的值。
- keySet //返回所有key。
- Collection values() //返回包含所有值的Collection集合。
- Set<Map. Entry<K, V>> //键值匹配的Set集合。
2. Map接口使用
以HashMap的实例:
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
public class Application {
public static void main(String[] args) {
//Map接口的使用
//特点:(1)存储键值对(2)键不能重复,值可重复(3)无序
Map<String, String> map = new HashMap<>();
//添加元素
map.put("cn","中国");
map.put("uk","英国");
map.put("us","美国");
map.put("ca","加拿大");
System.out.println("元素个数"+map.size());
System.out.println(map);
//删除
map.remove("us");
System.out.println(map);
//遍历 1.keyset方法
System.out.println("-----keyset方法-----");
//Set<String> keyset = map.keySet();
//for (String s : keyset) { 可缩写为:for (String s : map.keySet()) {
for (String s : map.keySet()) {
System.out.println(s+"-->"+map.get(s));
}
//使用entrySet方法 效率更高
System.out.println("-----entrySet方法-----");
// Set<Map.Entry<String, String>> entries = map.entrySet();
// for (Map.Entry<String, String> entry : entries) { 可缩写为
for (Map.Entry<String, String> entry : map.entrySet()) {
System.out.println(entry);
}
//判断
System.out.println("含有键uk吗:"+map.containsKey("uk"));
System.out.println("含有值 中国吗:"+map.containsValue("中国"));
System.out.println("是否为空:"+map.isEmpty());
}
}
---------------结果---------------
元素个数4
{uk=英国, cn=中国, us=美国, ca=加拿大}
{uk=英国, cn=中国, ca=加拿大}
-----keyset方法-----
uk-->英国
cn-->中国
ca-->加拿大
-----entrySet方法-----
uk=英国
cn=中国
ca=加拿大
含有键uk吗:true
含有值 中国吗:true
是否为空:false
Map集合的实现类
HashMap[重点]
JDK1. 2版本,线程不安全,运行效率快;允许用null 作为key或是value。
实例:
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class Application {
public static void main(String[] args) {
//HashMap集合的使用
//存储结构:哈希表(数组+链表+红黑树)
//使用key的hashcode和equals判断重复
HashMap<Person, String> hashmap = new HashMap<>();
//添加元素
Person p1 = new Person("张三", 20);
Person p2 = new Person("李四", 19);
Person p3 = new Person("王五", 18);
hashmap.put(p1,"北京");
hashmap.put(p2,"上海");
hashmap.put(p3,"张家口");
hashmap.put(new Person("王五", 18),"石家庄");
System.out.println("元素个数:"+hashmap.size());
System.out.println(hashmap);
//删除
hashmap.remove(p3);
System.out.println(hashmap);
//遍历 1. keySet
System.out.println("-----keySet方法-----");
for (Person person : hashmap.keySet()) {
System.out.println(person+"-->"+hashmap.get(person));
}
System.out.println("-----entrySet方法-----");
Set<Map.Entry<Person, String>> entries = hashmap.entrySet();
for (Map.Entry<Person, String> entry : entries) {
System.out.println(entry.getKey()+"-->"+entry.getValue());
}
//判断
System.out.println("包含键p1吗:"+hashmap.containsKey(p1));
System.out.println("包含值张家口吗:"+hashmap.containsValue("张家口"));
System.out.println("是否为空:"+hashmap.isEmpty());
}
}
---------------结果---------------
元素个数:3
{[name=王五 age=18]=石家庄, [name=张三 age=20]=北京, [name=李四 age=19]=上海}
{[name=张三 age=20]=北京, [name=李四 age=19]=上海}
-----keySet方法-----
[name=张三 age=20]-->北京
[name=李四 age=19]-->上海
-----entrySet方法-----
[name=张三 age=20]-->北京
[name=李四 age=19]-->上海
包含键p1吗:true
包含值张家口吗:false
是否为空:false
import java.util.Objects;
public class Person {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "[name="+name+" age="+age+"]";
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Person)) return false;
Person person = (Person) o;
return getAge() == person.getAge() && getName().equals(person.getName());
}
@Override
public int hashCode() {
return Objects.hash(getName(), getAge());
}
}
HashMap源码简单分析
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 -->默认初始大小=16 (1左移4位=2^4)
static final int MAXIMUM_CAPACITY = 1 << 30; --> 最大容量
static final float DEFAULT_LOAD_FACTOR = 0.75f; --> 默认加载因子 当添加元素的量大于容量的75%则扩容
static final int MIN_TREEIFY_CAPACITY = 64;–↓
static final int TREEIFY_THRESHOLD = 8;–>当数组长度大于64,链表长度大于8,则将该链表替换成红黑树,来提高运行效率
static final int UNTREEIFY_THRESHOLD = 6;–>当树的元素个数小于6时,将树调整回链表
transient Node<K,V>[ ] table; -->哈希表中的数组
size;–>元素个数
当创建hashmap之后没有添加元素,table=null,size=0。目的是为了节省空间。
//构造方法
//只有赋值加载因子,没有其他赋值
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
//用来计算位置
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
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;//执行resize()后n=16,且没有元素
if ((p = tab[i = (n - 1) & hash]) == null)//根据hash来找到位置
tab[i] = newNode(hash, key, value, null);
else {...}//暂时不解读
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)//当数据量达到当前容量的75%时,扩容为当前容量的二倍
resize();
afterNodeInsertion(evict);
return null;
}
//第一次执行的时候
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;//table=null;oldTab就=null;
int oldCap = (oldTab == null) ? 0 : oldTab.length;//给oldCap赋值0
int oldThr = threshold;//threshold没有赋值=0,oldThr=0
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;//给newCap赋值16
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);//加载因子*默认初始大小=12
}
if (newThr == 0) {... }//不满足,也不用看
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;//把新建的newTabl赋值给table,也就是添加第一个元素时,大小初始化为16
if (oldTab != null) {...}//后面的不分析
}
return newTab;
}
总结:
(1)HashMap刚创建时,table是null,为了节省空间,当添加第一个元素是,table容量调整为16
(2)当元素个数大于阈值(16*0.75=12)时,会进行扩容,扩容后大小为原来的2倍。目的是减少调整元素的个数。
(3) jdk1.8当每个链表长度大于8,并且元素个数大于等于64时,会调整为红黑树,目的提高执行效率
(4) jdk1.8当链表长度小于6时,调整成链表
(5)jdk1.8以前,链表时头插入,jdk1.8以后时是尾插入
HashSet其实用的是HashMap来实现的
HashTable(了解)
现已基本不用
- JDK1. 0版本,线程安全,运行效率慢;不允许null作为key或是value。
Properties(以后讲)
- Hashtable的子类,要求key和value都是String。通常用于配置文件的读取。
TreeMap
- 实现了SortedMap接口(是Map的子接口),可以对key自动排序。
import java.util.*;
public class Application {
public static void main(String[] args) {
//定制比较
TreeMap<Person, String> treeMap = new TreeMap<>(new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
int n1=o1.getName().compareTo(o2.getName());
int n2=o1.getAge()-o2.getAge();
return n1==0?n2:n1;
}
});
//添加元素
Person p1 = new Person("张三", 20);
Person p2 = new Person("李四", 20);
Person p3 = new Person("王五", 20);
treeMap.put(p1,"北京");
treeMap.put(p2,"张家口");
treeMap.put(p3,"石家庄");
System.out.println("元素个数为:"+treeMap.size());
System.out.println(treeMap);
//删除
treeMap.remove(p3);
System.out.println(treeMap);
//遍历
System.out.println("-----keySet-----");
for (Person person : treeMap.keySet()) {
// System.out.println(person.getName()+"-->"+person.getAge());
System.out.println(person+"-->"+treeMap.get(person));
}
System.out.println("-----entrySet-----");
for (Map.Entry<Person, String> entry : treeMap.entrySet()) {
System.out.println(entry);
}
//判断
System.out.println(treeMap.containsKey(p2));
System.out.println(treeMap.containsValue("张家口"));
System.out.println(treeMap.isEmpty());
}
}
---------------结果---------------
元素个数为:3
{[name=张三 age=20]=北京, [name=李四 age=20]=张家口, [name=王五 age=20]=石家庄}
{[name=张三 age=20]=北京, [name=李四 age=20]=张家口}
-----keySet-----
[name=张三 age=20]-->北京
[name=李四 age=20]-->张家口
-----entrySet-----
[name=张三 age=20]=北京
[name=李四 age=20]=张家口
true
true
false
import java.util.Objects;
public class Person implements Comparable<Person>{
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "[name="+name+" age="+age+"]";
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Person)) return false;
Person person = (Person) o;
return getAge() == person.getAge() && getName().equals(person.getName());
}
@Override
public int hashCode() {
return Objects.hash(getName(), getAge());
}
@Override
public int compareTo(Person o) {
int n1=this.name.compareTo(o.getName());
int n2=this.age-o.getAge();
return n1==0?n2:n1;
}
}
Colletions工具类
- 概念:集合工具类,定义了除了存取以外的集合常用方法。
方法:
- public static void reverse(List<?> list) //反转集合中元素的顺序
- public static void shuffle(List<?> list) //随机重置集合元素的顺序
- public static void sort(List list) //升序排序(元素类型必须实现
Comparable接口)
实例:
import java.util.*;
public class Application {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
list.add(30);
list.add(20);
list.add(40);
list.add(5);
System.out.println("排序之前:"+list);
Collections.sort(list);
System.out.println("排序之后:"+list);
//binarySearch 二分查找
System.out.println("30所在的下标为:"+Collections.binarySearch(list, 30));
//copy复制
ArrayList<Integer> list1 = new ArrayList<>();
for (int i = 0; i < list.size(); i++) {
list1.add(0);
}
Collections.copy(list1,list);
System.out.println("复制后:"+list1);
//reverse反转
Collections.reverse(list1);
System.out.println("反转后"+list1);
//打乱
Collections.shuffle(list);
System.out.println("打乱后"+list);
//补充:list转成数组
Integer[] array = list.toArray(new Integer[6]);
//new Integer[0]给的长度小于list长度时,长度随list,大于list时,随给的长度
System.out.println("数组长度:"+array.length);
System.out.println("集合变数组:"+Arrays.toString(array));
//数组转成集合-->受限集合:不能添加和删除
String[] str={"张三","李四","王五"};
List<String> list2 = Arrays.asList(str);
System.out.println("数组变集合:"+list2);
//把基本类型数组转成集合时,需要修改为包装类
int[] num={10,20,30,40};
List<int[]> ints = Arrays.asList(num);//类型为int[]
System.out.println("错误示范:"+ints);
//应该改成:
Integer[] num2={10,20,30,40};
List<Integer> list3 = Arrays.asList(num2);
System.out.println("list3:"+list3);
}
}
---------------结果---------------
排序之前:[30, 20, 40, 5]
排序之后:[5, 20, 30, 40]
30所在的下标为:2
复制后:[5, 20, 30, 40]
反转后[40, 30, 20, 5]
打乱后[5, 40, 20, 30]
数组长度:6
集合变数组:[5, 40, 20, 30, null, null]
数组变集合:[张三, 李四, 王五]
错误示范:[[I@2503dbd3]
list3:[10, 20, 30, 40]
集合总结
集合的概念:
- 对象的容器,和数组类似,定义了对多个对象进行操作的常用方法。
List集合:
- 有序、有下标、元素可以重复。 (ArrayList、 LinkedList、 Vector)
Set集合:
- 无序、无下标、元素不可重复。(HashSet、 TreeSet)
Map集合:
- 存储一对数据,无序、无下标,键不可重复,值可重复。(HashMap、 HashTable、 TreeMap )
Collections:
- 集合工具类,定义了除了存取以外的集合常用方法。