JavaEE - 集合 - Map集合

集合

集合是Java API所提供的一系列类,可以用于动态存放多个对象 (集合只能存对象)

集合类全部支持泛型,是一种数据安全的用法。

集合和数组的区别:

  1. 长度区别:数组固定、集合可变
  2. 内容区别:
    • 数组可以是基本类型,也可以是引用类型
    • 集合只能是引用类型
  3. 元素内容:
    • 数组只能存储同一种类型
    • 集合可以存储不同类型(集合一般也只存储同一种类型)

集合框架结构图

JavaEE - 集合 - Map集合_第1张图片


Map

Map用于保存具有映射关系的数据,Map里保存着两组数据:key和value。Map 提供 key 到 value 的映射,你可以通过“键”查找“值”。

  1. HashMap 接口实现类 ,没有同步, 线程不安全
    • LinkedHashMap 双向链表和哈希表实现
  2. Hashtable 接口实现类,同步,线程安全
    • Properties
  3. TreeMap 接口实现类,红黑树对所有的key进行排序
  4. ConcurrentHashMap

Map集合的方法

方法 描述
void clear() 移除该Map中的所有映射(清空)
boolean containsKey(Object key) 查询Map中是否包含指定key,如果包含则返回true
boolean containsValue(Object value) 查询Map中是否包含指定value,如果包含则返回true
V put(K key, V value) 将指定value与此映射中的指定key相关联。如果已经存在key,则旧value将被替换。(可用于添加、替换)
void putAll(Map m) 将所有映射从指定映射m复制到此映射
V get(Object key) 返回指定key所对应的value,如Map中不包含key则返回null
V getOrDefault(Object key, V defaultValue) 通过key获取value,如果key不存在,返回默认值
boolean isEmpty() 查询Map是否为空,如果空则返回true
V putIfAbsent(K key,V value) 如果指定的key没有对应的value(或value为null )将其与给定的value关联并返回null ,否则返回当前value。
V remove(Object key) 如果key存在,则删除指定key的映射。并返回与key关联的value,如果key 不存在,则返回null
boolean remove(Object key,Object value) 仅当key与value匹配时,才删除指定key的条目,并返回true
Collection values() 返回该Map里所有value组成的Collection
Set keySet() 返回该Map中所有key所组成的set集合
Set> entrySet() 返回Map中所包含的键值对所组成的Set集合,每个集合元素都是Map.Entry对象(Entry是Map的内部类)。
int size() 返回该Map里的键值对的个数。

HashMap

HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。

  • HashMap继承自AbstractMap类,实现了 Map 接口,根据键的 HashCode 值存储数据,具有很快的访问速度,不支持线程同步。(如果需要同步,可以用 Collections的synchronizedMap方法使HashMap具有同步的能力,或者使用ConcurrentHashMap)

    public class HashMap<K,V> extends AbstractMap<K,V>
        implements Map<K,V>, Cloneable, Serializable {
    
  • HashMap最多只有一个key值为null,但可以有无数多个value值为null。

  • HashMap 是无序的,即不会记录插入的顺序。

  • HashMap底层数组长度必须为2的幂,这样做是为了hash准备,默认为16。

//创建HashMap对象
HashMap<String, Integer> map = new HashMap<>();
//添加
map.put("樱泽墨", 17);
map.put("更科瑠夏", 19);
map.put("一之濑千鹤", 19);
//将指定值与此映射中的指定键相关联。如果映射先前包含键的映射,则旧值将被替换。
//替换(返回原来的value) 返回与key关联的旧值,如果没有key的映射,则为null
Integer put = map.put("樱泽墨", 16);
System.out.println("返回与key关联的旧值:" + put);//17
//替换
map.replace("更科瑠夏", 17);//通过key替换value
map.replace("一之濑千鹤", 19, 18);//通过key+value替换value
//获取(通过key获取value)
Integer integer = map.get("樱泽墨");//返回指定Key映射到的值,如果此映射不包含Key的映射,则返回null 。
System.out.println("通过key获取value:" + integer);//16
//获取(通过key获取value,如果key不存在,返回默认值)
Integer orDefault = map.getOrDefault("哈尔卡拉", 666);
System.out.println("通过key获取value,如果key不存在,返回默认:" + orDefault);//666
System.out.println("判断此集合中是否包含某个Key:" + map.containsKey("樱泽墨"));//true
System.out.println("判断此集合中是否包含某个Value:" + map.containsValue(18));//true
System.out.println("判断此集合中是否没有元素:" + map.isEmpty());//true-没有元素 false-有元素
//将新集合中所有的元素添加到现有集合中
HashMap<String, Integer> newMap = new HashMap<>();
newMap.put("a", 10);
newMap.put("bb", 20);
map.putAll(newMap);
System.out.println(map);//{樱泽墨=16, bb=20, a=10, 更科瑠夏=17, 一之濑千鹤=18}
//添加
//如果集合中有key,就返回value,不替换原来的值
//如果集合中没有key,就返回null并添加
Integer putIfAbsent = map.putIfAbsent("中野三玖", 17);
System.out.println(putIfAbsent);//null
System.out.println(map);//{樱泽墨=16, bb=20, a=10, 更科瑠夏=17, 中野三玖=17, 一之濑千鹤=18}
//删除
map.remove("bb");//通过key删除映射关系
map.remove("a", 10);//通过key+value删除映射关系
System.out.println("获取映射关系的个数:" + map.size());//7
System.out.println(map);//{樱泽墨=16, 更科瑠夏=17, 中野三玖=17, 一之濑千鹤=18}
//获取所有的value值
Collection<Integer> values = map.values();
//集合-->数组-->字符串
System.out.println(Arrays.toString(values.toArray()));//[16, 17, 17, 18]
//keySet()遍历:将map集合中所有的key获取出,存放在Set集合中,遍历Set依次获取出key,就能获取到对应value
Set<String> keySet = map.keySet();
for (String key : keySet) {
    Integer value = map.get(key);//返回指定Key映射到的值
    System.out.println(key + " -- " + value);
}
//樱泽墨 -- 16
//更科瑠夏 -- 17
//中野三玖 -- 17
//一之濑千鹤 -- 18
//entrySet()遍历:将map集合中所有的映射关系(Entry)获取出,存放在Set集合中,遍历Set依次获取出Entry
Set<Entry<String, Integer>> set = map.entrySet();
for (Entry<String, Integer> entry : set) {
    String key = entry.getKey();
    Integer value = entry.getValue();
    System.out.println(key + " -- " + value);
}
//清空集合
map.clear();
System.out.println(map);// {}

LinkedHashMap

LinkedHashMap = HashMap + 双向链表

LinkedHashMap继承自HashMap,所以LinkedHashMap拥有HashMap的所有特性。

LinkedHashMap使用双向链表来维护键值对的次序,迭代顺序与键值对的插入顺序保持一致,性能低于HashMap

public class LinkedHashMap<K,V>
    extends HashMap<K,V>
    implements Map<K,V>
{...}

Hashtable

HashTable是较为远古的使用Hash算法的容器结构,现在基本已被淘汰,单线程转为使用HashMap,多线程使用ConcurrentHashMap。

  • Hashtable与 HashMap类似,继承自Dictionary类,不允许记录的键或者值为null

  • 很多方法都是用synchronized修饰,支持线程同步( 线程安全 ),即任一时刻只有一个线程能写Hashtable,导致了 Hashtable在写入时会比较慢

  • HashTable底层数组长度可以为任意值,造成了hash算法散射不均匀,容易造成hash冲突,默认为11

public class Hashtable<K,V>
    extends Dictionary<K,V>
    implements Map<K,V>, Cloneable, java.io.Serializable {...}

Properties

Properties 类是Java 语言的配置文件所使用的类。 Xxx.properties 为Java 语言常见的配置文件,以key=value 的 键值对的形式进行存储值,key值不能重复

  • 继承了Hashtable 类,以Map 的形式进行放置值,put(key,value)、get(key)
public class Properties extends Hashtable<Object,Object> {...}

Properties的使用

src/DBConfig.properties

username = Miku
password = 12345

Test.java

public class Test {
    public static void main(String[] args) throws IOException {
    //创建Properties的对象
    Properties p = new Properties();
    //把配置文件加载到p对象中
    p.load(Test01.class.getClassLoader().getResourceAsStream("DBConfig.properties"));
    //load()从输入字节流中读取属性列表(键值对)
    //getClassLoader()返回此对象表示的类或接口的类加载器
    //getResourceAsStream()用于读取资源的输入流,如果找不到资源,则为null

    //获取配置文件中数据
    String username = p.getProperty("username");
    //getProperty()在此属性列表中搜索具有指定键的属性。如果未找到将返回null
    String password =  p.getProperty("password");
    System.out.println(username + " -- " + password);//Miku -- 12345
    }
}

TreeMap

TreeMap实现了红黑树的结构,形成了一颗二叉树。能够把保存的记录根据键排序,默认是按键值的升序排序,也可以指定排序的比较器。

  • TreeMap继承于AbstractMap,实现了Map, Cloneable, NavigableMap, Serializable接口。

    public class TreeMap<K,V>
        extends AbstractMap<K,V>
        implements NavigableMap<K,V>, Cloneable, java.io.Serializable {...}
    

特点:

  • 不允许出现重复的key;
  • key不可以为null(若要允许空值可通过自定义比较器来创建TreeMap),value可以为null;
  • 可以对元素进行排序;
  • 无序集合(插入和遍历顺序不一致);

TreeMap特殊方法

方法 描述
Map.Entry firstEntry() 返回最小key所对应的键值对,如Map为空,则返回null
Map.Entry lastEntry() 返回最大key所对应的键值对,如Map为空,则返回null
Object firstKey() 返回最小key,如果为空,则返回null
Object lastKey() 返回最大key,如果为空,则返回null
Map.Entry higherEntry(0bject key) 返回位于key后一位的键值对,如果为空,则返回null
Map.Entry lowerEntry(0bject key) 返回位于key前一位的键值对,如果为空,则返回null
Object lowerKey (object key) 返回位于key前一位key值,如果为空,则返回null
TreeMap<String, Integer> treeMap = new TreeMap<>();
treeMap.put("c", 21);
treeMap.put("f", 10);
treeMap.put("a", 28);
treeMap.put("d", 8);
treeMap.put("b", 21);
Set<Entry<String, Integer>> entrySet = treeMap.entrySet();
for (Entry<String, Integer> entry : entrySet) {
    String key = entry.getKey();
    Integer value = entry.getValue();
    System.out.println(key + " -- " + value);
}
//a -- 28
//b -- 21
//c -- 21
//d -- 8
//f -- 10
Entry<String, Integer> firstEntry = treeMap.firstEntry();
System.out.println("返回最小key所对应的键值对:" + firstEntry);//a=28
Entry<String, Integer> lastEntry = treeMap.lastEntry();
System.out.println("返回最大key所对应的键值对:" + lastEntry);//f=10
System.out.println("返回最小key:" + treeMap.firstKey());//a
System.out.println("返回最大key:" + treeMap.lastKey());//f
Entry<String, Integer> higherEntry = treeMap.higherEntry("b");
System.out.println("返回位于b后一位的键值对:" + higherEntry);//c=21
Entry<String, Integer> lowerEntry = treeMap.lowerEntry("c");
System.out.println("返回位于c前一位的键值对:" + lowerEntry);//b=21

TeeMap使用内置排序接口

需求:学生对象存在TreeMap key的位置,用年龄排序

Student.java

//实现Comparable接口
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 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;}
	@Override
	public String toString() {
		return "Student [name=" + name + ", age=" + age + "]";
	}
    //重写compareTo()方法
	@Override
	public int compareTo(Student o) {
		return this.age-o.age;
	}
}

Test.java

TreeMap<Student, String> map = new TreeMap<>();
map.put(new Student("中野三玖", 17), "12138");
map.put(new Student("中野二乃", 18), "12137");
map.put(new Student("中野四叶", 16), "12139");
Set<Entry<Student, String>> entrySet = map.entrySet();
for (Entry<Student, String> entry : entrySet) {
    Student key = entry.getKey();
    String value = entry.getValue();
    System.out.println(key + " -- " + value);
}
//Student [name=中野四叶, age=16] -- 12139
//Student [name=中野三玖, age=17] -- 12138
//Student [name=中野二乃, age=18] -- 12137

TreeMap使用外置排序接口

需求:学生对象存在TreeMap key的位置,先按照名字长度排序,长度一致按照年龄排序

TreeMap<Student, String> map = new TreeMap<>(new Comparator<Student>() {
    @Override
    public int compare(Student o1, Student o2) {
        if (o1.getName().equals(o2.getName()) && o1.getAge() == o2.getAge()) {
            return 0;
        }
        if (o1.getName().length() != o2.getName().length()) {
            return o1.getName().length() - o2.getName().length();
        }
        if (o1.getAge() != o2.getAge()) {
            return o1.getAge() - o2.getAge();
        }
        return 1;
    }
});
map.put(new Student("椎名真白", 18), "12137");
map.put(new Student("更科瑠夏", 17), "12138");
map.put(new Student("樱泽墨", 16), "12139");
Set<Entry<Student, String>> entrySet = map.entrySet();
for (Entry<Student, String> entry : entrySet) {
    Student key = entry.getKey();
    String value = entry.getValue();
    System.out.println(key + " -- " + value);
}
//Student [name=樱泽墨, age=16] -- 12139
//Student [name=更科瑠夏, age=17] -- 12138
//Student [name=椎名真白, age=18] -- 12137

ConcurrentHashMap

ConcurrentHashMap是HashMap的一个线程安全的、支持高效并发的版本。

  • ConcurrentHashMap不同于HashMap,它既不允许key值为null,也不允许value值为null。
  • 在默认理想状态下,ConcurrentHashMap可以支持16个线程执行并发写操作及任意数量线程的读操作。
  • 在ConcurrentHashMap中,线程对映射表做读操作时,一般情况下不需要加锁就可以完成,对容器做结构性修改的操作(比如,put操作、remove操作等)才需要加锁。
public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
    implements ConcurrentMap<K,V>, Serializable {...}

高效并发机制是通过以下保证的

  • 通过锁分段技术保证并发环境下的写操作;
  • 通过 HashEntry的不变性、Volatile变量的内存可见性和加锁重读机制保证高效、安全的读操作;
  • 通过不加锁和加锁两种方案控制跨段操作的的安全性。

ConcurrentHashMap读操作不需要加锁在于以下三点:

  • 用HashEntery对象的不变性来降低读操作对加锁的需求;
  • 用Volatile变量协调读写线程间的内存可见性;
  • 若读时发生指令重排序现象,则加锁重读;

ConcurrentHashMap 采用了分段锁技术,其中 Segment 继承于 ReentrantLock。不会像 HashTable 那样不管是 put 还是 get 操作都需要做同步处理,理论上 ConcurrentHashMap 支持 CurrencyLevel (Segment 数组数量)的线程并发。每当一个线程占用锁访问一个 Segment 时,不会影响到其他的 Segment。

注意

  1. Collection 与 Map的区别

    Collection 存单个值,可以获取迭代器进行遍历
    Map存两个值(Key-Value),不可以获取迭代器,不能遍历(Map可以间接遍历)

  2. 理解Set为什么是无序

    无序:存入顺序和取出顺序不一致,无序不等于随机

  3. ArrayList 与 LinkedList的区别

    使用上的区别:

    LinkedList添加了
    队列模式-先进先出(removeFirst())
    栈模式-先进后出(removeLast())

    效率上的区别:由于ArrayList底层数据结构是一维数组,LinkedList底层数据结构是双向链表

    添加 - 不扩容的情况:ArrayList快
    添加 - 扩容的情况:LinkedList快
    注意: 工作中常用ArrayList,因为很多需求都需要使用查询功能,ArrayList查询更快

集合总结

数据结构 是否有序 线程安全 能否存null
ArrayList 数组 有序 不安全
LinkedList 链表 有序 不安全
Vector 数组 有序 安全
HashSet 哈希表 无序且唯一 不安全
LinkedHashSet 链表+哈希表 有序且唯一 不安全
TreeSet 二叉树(红黑树) 自动排序且唯一 不安全 不能
HashMap 哈希表 无序且key唯一 不安全 key最多一个为null
value可以多个为null
LinkedHashMap 链表+哈希表 有序且key唯一 不安全 key最多一个为null
value可以多个为null
Hashtable 哈希表 无序且key唯一 安全,效率低 不能
ConcurrentHashMap 哈希表 无序且key唯一 安全,效率高 不能
TreeMap 二叉树(红黑树) 对Key排序且key唯一 不安全 key不能为null
(可通过自定义比较器)

ArrayXxx:底层数据结构是数组,查询快,增删慢
LinkedXxx:底层数据结构是链表,查询慢,增删快
HashXxx:底层数据结构是哈希表。依赖两个方法:hashCode()和equals()
TreeXxx:底层数据结构是二叉树。两种方式排序:自然排序和比较器排序

你可能感兴趣的:(JavaEE,java,javaee,集合,map)