
特点:无序,无下标,由键值对构成(key - value)key不可重复,value可以重复。
详细可以查阅API
1. clear() 移除所有的键值对
2. entrySet()返回映射中包含的映射关系的Set视图
3. keySet()返回所有键的Set视图
4. put(K key,V value) 插入数据
5. remove()移除某个映射关系
6. size() 查看size
7. value()获取value的Collection集合
2.简单的使用
public static void main(String[] args) {
Map<String, String> map = new HashMap<String, String>();
map.put("china", "中国");
map.put("uk", "英国");
System.out.println(map.size());
System.out.println(map.toString());
System.out.println(map.remove("uk"));
//1.打印所有的键
Set<String> keyset = map.keySet();
for(String str : keyset){
System.out.println(str + "===" + map.get(str));
}
//2.获取所有的映射关系 :效率要高一点
//Entry表示一对映射关系
Set<Map.Entry<String, String>> kvset = map.entrySet();
for(Map.Entry<String , String> entry : kvset){
System.out.println(entry.getKey() + "==" + entry.getValue());
}
//3.判断
System.out.println(map.containsKey("uk"));
System.out.println(map.isEmpty());
}
3.HashMap
3.1简单应用
jdk1.2版本,线程不安全,运行效率高,允许用null作为key或者是value
创建一个Student类
public class Student {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public static void main(String[] args) {
HashMap<Student, String> hashmap = new HashMap<Student, String>();
//1. 添加元素
Student stu1 = new Student("sun", 18);
Student stu2 = new Student("chen",19);
Student stu3 = new Student("wang",20);
hashmap.put(stu1, "qingdao");
hashmap.put(stu2, "shandong");
hashmap.put(stu3,"hangzhou");
System.out.println(hashmap.size() + "元素个数");
System.out.println(hashmap.toString());
//新创建 stu4
Student stu4 = new Student("sun",18);
hashmap.put(stu4,"nanjing");
System.out.println(hashmap.size());
//可以发现 能够添加stu4
}
能够添加重复元素,是由于HashMap默认的评判key是否相同的依据并不是,Student中的姓名和年龄。
这时候只需要重写Student中的equals和hashCode方法就可以使得,stu4不作为一个新的key插入了,注意,这时候stu4会覆盖掉stu1。
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Student)) return false;
Student student = (Student) o;
return age == student.age &&
Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
3.2源码阅读
注:
- HashMap加载因子为0.75,见3.2.1
- 添加第一个元素时,会创建一个长度为16的数组见3.2.2
- JDK1.7及之前,存储结构由数组+链表组成,JDK1.8之后由数组+链表+红黑树构成(当链表长度>=8 && 数组长度 >= 64,链表会自动调整为红黑树)
- JDK1.8当链表长度小于6的时候,存储结构会调整成为
- JDK1.8之前链表的插入语方式为头插入,之后尾插入方式
/**
* Constructs an empty HashMap with the default initial capacity
* (16) and the default load factor (0.75).
*/
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
loadFactor为hashmap的加载因子,这里的加载因子是0.75,就是说,当存放元素超过,当前总容量的75%。就会扩容。
具体这个值的来源,来自于开发时的合理选取,综合了诸多因素,若太小就会频繁扩容,若太大后期可能会频繁冲突之类的。
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
static final int MAXIMUM_CAPACITY = 1 << 30;
初始容量16,1左移四位。最大容量2^30。
3.2.3存储结构,数组加链表结构数组的定义
transient Node<K,V>[] table;
Node的定义
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
//后面省略
}
显然 Node是一个单向链表
3.2.4put方法put()函数内部内容
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
putVal()
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict)
先不管内部内容如何,我们可以看到 putVal()一共由五个参数,第一个为哈希值。哈希值的计算来自于hash()函数,找到hash()函数,内容如下所示。
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
如果key是空直接返回0,如果不是,h = key.hashCode()) ^ (h >>> 16),此处。
计算完成后得出的值即为hashcode的值。相当于是 低16位和高16位来一个异或运算。通常16位已经足够满足我们的需要。工程师们(开发者)认为,这样做会使得哈希表分布更加的均匀,减少散列冲突。
回到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;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length; //第一次添加要扩容 扩到16
if ((p = tab[i = (n - 1) & hash]) == null) //根据哈希值计算位置
tab[i] = newNode(hash, key, value, null);
如果我当前是第一次添加数据,则 我的第一个i的计算就是 (16 - 1)& hash也就是:
此处,i = (n - 1) & hash一定是0-15之间,如果计算出的位置是空的,就直接插入。如果不是null则
else {
Node<K,V> e; K k;
//此处就要开始看equals了
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//再往下就要上链表了
//再往下就上红黑树了
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;
}
}
关于扩容,初始的数组大小为16,当进行到13个数据的时候就会进行扩容处理。
++modCount;
if (++size > threshold) //即将搞到13,马上resize()
resize();
afterNodeInsertion(evict);
return null;
}
会进入到 resize() 内
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) { //判断是不是抄过最大
threshold = Integer.MAX_VALUE;
return oldTab;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY) //新的容量时旧的容量左移一位,成为32
newThr = oldThr << 1; // double threshold
}
//之后会创建新的数组,容量为32,然后再传回去使用
4.TreeMap
存储结构,是一个红黑树。
实现了SortedMap接口(Map的子接口),可以对key自动排序,key需要实现Comparable接口。
注意的点主要是,“key”需要继承Comparable接口
拿上述,Student举例
package main.java.day19;
import java.util.Objects;
public class Student implements Comparable<Student>{
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Student)) return false;
Student student = (Student) o;
return age == student.age &&
Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public int compareTo(Student o) {
//先比较姓名
int n1 = this.getName().compareTo(o.getName());
//比较年龄
int n2 = this.getAge() - o.getAge();
return n1 == 0 ? n2 : n1;
}
}
主函数代码
public static void main(String[] args) {
TreeMap<Student, String> treeMap = new TreeMap<Student, String>();
Student stu1 = new Student("sun", 18);
Student stu2 = new Student("chen",19);
Student stu3 = new Student("wang",20);
treeMap.put(stu1, "beijing");
treeMap.put(stu2, "shanghai");
treeMap.put(stu3, "hangzhou");
System.out.println(treeMap.size() + "这是元素个数");
System.out.println(treeMap.toString());
// 删除
treeMap.remove(stu1);
System.out.println(treeMap.size());
//遍历
//使用KeySet
Set<Student> students = treeMap.keySet();
for (Student student : students){
System.out.println(student.toString() + "-----" + treeMap.get(student));
}
//使用entry
Set<Map.Entry<Student, String>> entrySet = treeMap.entrySet();
for (Map.Entry entry : entrySet){
System.out.println(entry.getKey() + "----" + entry.getValue());
}
利用比较器更换比较规则,比如我们现在想先比较年龄再比较姓名
注意比较器的优先级是高于元素自然排序的优先级的。
示例代码如下:
public static void main(String[] args) {
//定义比较器
Comparator<Student> comparator = new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
int n1 = o1.getAge() - o2.getAge();
int n2 = o1.getName().compareTo(o2.getName());
return n1 == 0 ? n2 : n1;
}
};
//这里将定义好的比较器,传给我们定义的TreeSet
TreeMap<Student, String> treeMap = new TreeMap<Student, String>(comparator);
Student stu1 = new Student("sun", 18);
Student stu2 = new Student("chen",19);
Student stu3 = new Student("wang",20);
treeMap.put(stu1, "beijing");
treeMap.put(stu2, "shanghai");
treeMap.put(stu3, "hangzhou");
System.out.println(treeMap.size() + "这是元素个数");
System.out.println(treeMap.toString());
// 删除
treeMap.remove(stu1);
System.out.println(treeMap.size());
//遍历
//使用KeySet
Set<Student> students = treeMap.keySet();
for (Student student : students){
System.out.println(student.toString() + "-----" + treeMap.get(student));
}
//使用entry
Set<Map.Entry<Student, String>> entrySet = treeMap.entrySet();
for (Map.Entry entry : entrySet){
System.out.println(entry.getKey() + "----" + entry.getValue());
}
}
5.Properties
- 存储属性名或属性值
- 属性名和属性值都是字符串类型
- 没有泛型
- 和流有关系
这里与流相关的内容之后再进行补充
5.1简单的应用public static void main(String[] args) {
//创建集合
Properties properties = new Properties();
//添加元素
properties.setProperty("name", "sun");
properties.setProperty("age", "20");
System.out.println(properties.size());
System.out.println(properties);
properties.remove("age");
System.out.println(properties);
//遍历 使用keySet和EntrySet
//还可以使用stringPropertyNames,返回所有的属性名
Set<String> proStrings = properties.stringPropertyNames();
for (String str : proStrings){
System.out.println(str + "--" + properties.get(str));
}
System.out.println(properties.containsKey("age"));
}
5.2补充System.getProperties()函数
//补充System.getProperties();
//此函数为获取与系统相关的所有属性
Properties properties1 = System.getProperties();
Set<String> proTrings1 = properties1.stringPropertyNames();
for(String string : proTrings1){
System.out.println(string + "-----" + properties1.getProperty(string));
}
代码执行完之后的结果,如下所示:
会输出系统的相关属性。
Map的主要内容基本上就这么多。
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)