Map接口

概述

在JDK1.2之前使用的是Dictionary抽象类(字典 = 映射)。JDK1.2之后,才有了Map(映射)接口。
有时Map也被称之为字典,索引。

  1. 将键映射到值的对象。(我们可以根据键快速查找到关联的值)
  2. Map中键是唯一的(不包含重复的键)
  3. 每个键最多只能映射一个值

Map的API

Map<String, String> map = new HashMap<>();
map.put("刘强东", "章泽天");
map.put("文章", "马伊利");
map.put("王宝强", "马蓉");
map.put("贾乃亮", "李小璐");
增(改):
	V put(K key, V value)
	System.out.println(map.put("罗志祥","周扬青"))  //null
	System.out.println(map.put("王宝强",null))   // 马蓉
	System.out.println(map);
	
	void putAll(Map<? extends K, ? extends V> m)
	Map<String, String> m = new HashMap<>();
	map.put("罗志祥","周扬青");
	map.put("王宝强",null);
	map.putAll(m);
	System.out.println(map);  //   王宝强 = null
删:
	void clear()
	V remove(Object key)
	System.out.println(map.remove("刘强东")); //章泽天
	System.out.println(map.remove("罗志祥"));  // 不存在返回null。但是返回null不一定不存在,可能value值就等于null。所以API提供了containsKey()这个方法。
	System.out.println(map);
查:
	V get(Object key)
	System.out.println(map.get("文章"));  // 马伊利
    System.out.println(map.get("罗志祥"));  //不存在返回 null。同remove,返回null不能就说其不存在
	
	boolean containsKey(Object key)
	map.put("王宝强", null);
	System.out.println(map.get("王宝强") != null);  // false
	System.out.println(map.get("罗志祥") != null);  // false
	System.out.println(map.containsKey("王宝强"));  // true
	System.out.println(map.containsKey("罗志祥"));  // false
	
	boolean containsValue(Object value)
	System.out.println(map.containsValue("李小璐"));  //true
    System.out.println(map.containsValue("周扬青"));  //false
    
获取集合属性:
	boolean isEmpty()
	int size()
遍历:
	Set<Map.Entry<K,V>> entrySet()  // entry 键值对的英文  最适用于遍历键值对
//Map.Entry<K,V>
//	K getKey()
//	V getValue() : 
//	V setValue(V value)
	Set<Map.Entry<String, String>> entries = map.entrySet();
	for(Map.Entry<String, String> entry : entries) {
		String key = entry.getKey();
		String value = entry.getValue();
		System.out.println(key + "=" + value);
	}
	
	Set<K> keySet() // 最适用于遍历键
	Set<String> keys = map.keySet();
	for(String key : keys) {
		String value = map.get(key);
		System.out.println(key + "=" + value);
	}
	
	Collection<V> values()  // 最适用于遍历值 
	//为什么values返回一个collection而不是set? 因为value可以重复  
	Collection<String> values = map.values();
	for(String value : values) {
		System.out.println(value);  //不能根据 值 --> 键
	}

Map的子类

HashMap(JDK中是数组+链表的方式实现的,但也可以用其他方法实现)

特性:
  1. 底层是哈希表
  2. 并允许null键和null值
  3. 不保证映射顺序,不保证顺序是恒久不变的
  4. 不同步的
    实现了Map接口所有可选的操作
    与JDK1.0的Hashtable很像。
HashMap<String, String> map = new HashMap<>();
map.put("刘强东", "章泽天");
map.put("文章", "马伊利");
map.put("王宝强", "马蓉");
map.put("贾乃亮", "李小璐");
map.put(null, "Allen");
map.put("Andrew", null);
System.out.println(map);  //输出是无序的。允许null值和null键
构造方法(与HashSet相同)
HashMap():初始容量默认为16, 加载因子默认为0.75f
HashMap(int initialCapacity): 指定初始容量, 加载因子默认为0.75f
HashMap(int initialCapacity, float loadFactor): 指定初始容量,指定加载因子
HashMap(Map<? extends K,? extends V> m)
HashMap与Hashtable区别

共同点:底层数据结构都是哈希表
不同点:HashMap的键和值可以为null;Hashtable不可以;HashMap是不同步的,Hashtable是同步的

Hashtable<String, String> table = new Hashtable<>();
map.put("刘强东", "章泽天");
map.put("文章", "马伊利");
map.put("王宝强", "马蓉");
map.put("贾乃亮", "李小璐");
System.out.println(table); // 底层是哈希表,输出无序

//添加null键
table.put(null, "Allen"); //不允许,抛出NullPointerException
//添加null值
table.put("Andrew", null); //不允许,抛出NullPointerException
HashMap的子类LinkedHashMap
概述:
  1. HashMap的子类
  2. 底层数据结构是哈希表和双向链表,具有可预知的迭代顺序
  3. 哈希表保证了键的唯一性
  4. 双向链表定义了迭代顺序 = 插入顺序
  5. 不同步
LinkedHashMap<String, String> map = new LinkedHashMap<>();  //与性能良好的LRU算法的结构类似,但是不能直接用在LRU算法中,原因见最下面两个方法。没有改变它的迭代顺序。
map.put("刘强东", "章泽天");
map.put("文章", "马伊利");
map.put("王宝强", "马蓉");
map.put("贾乃亮", "李小璐");
map.put(null, "Allen");
map.put("Andrew", null);
System.out.println(map);

map.get("贾乃亮");  
System.out.println(map); // 输出不变
map.put("王宝强", null); 
System.out.println(map); // 输出不变

TreeMap

概述
  1. 底层数据结构是红黑树
  2. 如果创建对象时,没有传入 Comparator 对象,键将按自然顺序进行排序。
  3. 如果创建对象时,传入了 Comparator 对象,键将按 Comparator 进行排序。
  4. 不同步
构造方法(TreeSet一样)
TreeMap():键将按自然顺序进行排序, 要求键必须实现Comparable接口
TreeMap(Comparator<? super K> comparator):键将按Comparator进行排序
练习
存储自定义Student对象
Student s1 = new Student("Allen", 20);
Student s2 = new Student("Beyonce", 20);
Student s3 = new Student("Catalina", 20);
Student s4 = new Student("Diana", 20);

TreeMap<String, Student> map = new TreeMap<>();
map.put("Allen", s1);
map.put("Beyonce", s2);
map.put("Catalina", s3);
map.put("Diana", s4);
System.out.println(map);  // 成功输出,因为使用无参构造方法时,只需要键实现了Comparable接口。此处的键是String,已经实现了Comparable接口。
System.out.println(map.size()); // 4

// 键为自定义类型,则必须实现Comparable接口或者构造方法传入comparator
TreeMap<Student, String> map = new TreeMap<>();
map.put(s1, "Allen");
map.put(s2, "Beyonce");
map.put(s3, "Catalina");
map.put(s4, "Diana");
System.out.println(map);
System.out.println(map.size());

// 构造方法传入Comparator匿名内部类
TreeMap<Student, String> map = new TreeMap<>(new Comparator<Student>() {
	@Override
    public int compare(Student s1, Student s2) {
       int cmp = s1.getName().compareTo(s2.getName());
       cmp = cmp != 0 ? cmp : s1.getAge() - s2.getAge();
        return cmp;
    }
});

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 +
                '}';
    }
    @Override
    public int compareTo(Student s1) {
    	int cmp = name.compareTo(s1.getName());
    	cmp = cmp != 0 ? cmp : age - s1.getAge();
    	return cmp;
    }
底层是红黑树,特有API(TreeSet的底层数据结构TreeMap所以相似)
TreeMap<Character, String> map = new TreeMap<>();
map.put('A', "Allen");
map.put('B', "Beyonce");
map.put('C', "Catalina");
map.put('D', "Diana");
map.put('F', "Frances");
map.put('M', "Maria");
map.put('N', "Nika");
map.put('T', "Taylor");

K ceilingKey(K key)
System.out.println(map.ceilingKey('D')); // D
System.out.println(map.ceilingKey('E')); // F
System.out.println(map.ceilingKey('X')); // null

K floorKey(K key)
System.out.println(map.floorKey('D')); // D
System.out.println(map.floorKey('E')); // D
System.out.println(map.floorKey('0')); // null

K higherKey(K key)
System.out.println(map.higherKey('D')); // F
System.out.println(map.higherKey('E')); // F
System.out.println(map.higherKey('X')); // null

K lowerKey(K key)
System.out.println(map.lowerKey('D')); // C
System.out.println(map.lowerKey('E')); // D
System.out.println(map.lowerKey('0')); // null


K firstKey()
K lastKey()
System.out.println(map.firstKey());  //A
System.out.println(map.lastKey());  //T
System.out.println(map);  //{A=Allen, B=Beyonce, C=...}

Map.Entry<K,V> pollFirstEntry()
Map.Entry<K,V> pollLastEntry()
System.out.println(map.pollFirstKey());  //A=Allen
System.out.println(map.pollLastKey());  //T=Taylor

NavigableMap<K,V> subMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive)
//{A=Allen, B=Beyonce, C=Catalina, D=Diana, F=Frances, M=Maria, N=Nika, T=Taylor}
NavigableMap<Character, String> subMap = map.subMap('D', false, 'N', true);
System.out.println(subMap);  // {F=Frances, M=Maria, N=Nika}
subMap.pollFirstEntry();
System.out.println(subMap); // {M=Maria, N=Nika}
System.out.println(map); // {A=Allen, B=Beyonce, C=Catalina, D=Diana, M=Maria, N=Nika, T=Taylor}

练习

// "aababcabcdabcde",获取字符串中每一个字符出现的次数要求结果:a(5)b(4)c(3)d(2)e(1)
思路1:如果只是小写英文字母
1. 创建大小为26int数组,索引为0的位置记录'a'出现的次数..
2. 遍历字符串,获取每一个字符,讲对应索引位置的值+1
3. 遍历数组,拼接字符串
思路2:如果为任意字符
1. 创建TreeMap<Character, Integer> map对象
2. 遍历字符串,获取每一个字符,如果存在对应的字符则+1,不存在则设置为1
3. 遍历TreeMap,拼接字符串

public class Ex1 {
    public static void main(String[] args) {
        String s = "aababcabcdabcde";
        TreeMap<Character, Integer> map = new TreeMap<>();
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            if (map.containsKey(c)) map.put(c, map.get(c) + 1);
            else map.put(c, 1);
        }
        StringBuilder sb = new StringBuilder();
        Set<Map.Entry<Character, Integer>> entries = map.entrySet();
        for (Map.Entry<Character, Integer> e : entries) {
            sb.append(e.getKey()).append("(").append(e.getValue()).append(")");
        }
        System.out.println(sb);
    }
}
/*给定一个整数数组和一个目标值,找出数组中和为目标值的两个数, 返回它们的索引。
   你可以假设每个输入只对应一种答案,且同样的元素不能被重复利用。

比如:nums = [2, 7, 11, 15], target = 9.
因为 nums[0] + nums[1] = 2 + 7 = 9. 所以返回 [0, 1].
问题:数组中可以有重复的元素吗?
    不能有重复的元素, 否则答案不唯一。 */

//时间复杂度:O(n)
//空间复杂度:O(n)
public class Ex2 {
    public static void main(String[] args) {
        int[] nums = {2, 7, 11, 15};
        System.out.println(Arrays.toString(twoSum(nums, 9)));  //数组输出语句
    }
    public static int[] twoSum (int[] arr, int target) {
        HashMap<Integer, Integer> map = new HashMap<>();
        for (int i = 0; i < arr.length; i++) {
            if (map.containsKey(target - arr[i])) {
                int j = map.get(target - arr[i]);  // j < i
                return new int[] {j,i};
            } else {
                map.put(arr[i], i);
            }
        }
        return null;
    }
}
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/*
给定一个字符串数组 words 和一个字符串 chars. 如果一个字符串能被 chars 里面的字符组成,那么这个字符串就是"好"的(chars里面每个字符只能使用一次)。
求:words 里面所有好的字符串的字符总个数。
比如:
words = ["cat", "bt", "hat", "tree"]
chars = "atach"
好的字符串有 "cat", "hat", 3 + 3 = 6.
输出:6
 */
public class HomeWork2 {
    public int countCharacters(String[] words, String chars) {
        Map<Character, Integer> map = createMap(chars);
        int sum = 0;
        for(String s : words) {
            Map<Character, Integer> m = createMap(s);
            if(canGenerate(m, map)) sum += s.length();
        }
        return sum;
    }
    private boolean canGenerate(Map<Character, Integer> m, Map<Character, Integer> map) {
        Set<Map.Entry<Character, Integer>> entries = m.entrySet();
        for(Map.Entry<Character, Integer> entry : entries) {
            Character c = entry.getKey();
            int cnt = entry.getValue();
            if(!map.containsKey(c) || map.get(c) < cnt) return false;
        }
        return true;
    }
    private Map<Character, Integer> createMap(String chars) {
        Map<Character, Integer> map = new HashMap<>();
        for (int i = 0; i < chars.length(); i++) {
            char c = chars.charAt(i);
            if(map.containsKey(c)) map.put(c, map.get(c) + 1);
            else map.put(c, 1);
        }
        return map;
    }
//    public static int charSum(String[] words, String chars) {
//        HashSet<Character> set = new HashSet<>();
//        int sum = 0;
//        int flag = 0;
//        for (int i = 0; i < chars.length(); i++) {
//            set.add(chars.charAt(i));
//        }
//        for (int i = 0; i < words.length; i++) {
//            for (int j = 0; j < words[i].length(); j++) {
//                if(!set.contains(words[i].charAt(j))) {
//                    flag = 1;
//                    break;
//                }
//            }
//            if(flag == 0)
//                sum += words[i].length();
//            else
//                flag = 0;
//        }
//        return sum;
//    }
    public static void main(String[] args) {
        String[] words = {"cat", "bt", "hat", "tree"};
        String chars = "atach";
        int sum = charSum(words, chars);
        System.out.println(sum);
    }
}

Properties(属性集) — 一般作为配置文件存在

概述
  1. Hashtable<Object, Object> 的子类 Hashtable 1.0就提供,泛型是1.5提供。所以设计有点问题
  2. Properties类表示了一个可持久的属性集
  3. Properties可以把数据保存到流中,或者从流中加载数据。保证其可持久
  4. Properties中每个键及其对应值都是一个字符串(所以不要使用Hashtable定义的方法添加键值对,因为可能插入不是String类型的键值对。如果一个Properties中有非String的键值对,这样的Properties是“不安全的”,调用store或者save方法会失败)
API
():
Object setProperty(String key, String value)  //properties提供方法,可以装入获取String类型的数据
Properties info = new Properties();
info.setProperty("刘强东", "章泽天");
info.setProperty("文章", "马伊利");
info.setProperty("王宝强", "马蓉");
info.setProperty("贾乃亮", "李小璐");
System.out.println(info.setProperty("罗志祥", "周扬青"));  // null
System.out.println(info.setProperty("王宝强", ""));  // 马蓉 。注:继承与Hashtable所以不能存null

删:
Object remove(Object key) // Hashtable提供的

查:
String getProperty(String key) // 避免使用Hashtable的get带来的强转麻烦,所以properties提供该方法
System.out.println(info.getProperty("刘强东"));  // 章泽天
System.out.println(info.getProperty("罗志祥"));  // null

String getProperty(String key, String defaultValue)
System.out.println(info.getProperty("刘强东", "default"));  // 如果不存在,返回default,不返回null
System.out.println(info.getProperty("罗志祥", "default"));

遍历:
Set<String> stringPropertyNames() ---> keySet()
Set<String> names = info.stringPropertyNames();
for (String name : names) {
	String value = info.getProperty(name);
	System.out.println(name + "=" + value);
}

流相关:
// 注意事项:字节流默认使用 ISO08869-1 字符编码。
void store(OutputStream out, String comments)  //不常用
void load(InputStream inStream)  //不常用

Properties info = new Properties();
info.setProperty("刘强东", "章泽天");
info.setProperty("文章", "马伊利");
info.setProperty("王宝强", "马蓉");
info.setProperty("贾乃亮", "李小璐");
1.
void store(Writer writer, String comments)
try (Writer writer = new FileWriter("info.properties")) { // 使用IDEA默认的编码表
	info.store(writer,"...") //comments是说明
} catch (IOException e) {
	e.printStackTrace();
}
void load(Reader reader)
try (Reader reader = new FileReader("info.properties")) {
	info.load(reader);
} catch (IOException e) {
	e.printStackTrace();
}
System.out.println(info);

2. // 默认使用UTF-8字符编码
void storeToXML(OutputStream os, String comment)
try (OutputStream out = new FileOutputStream("info.xml")) {
	info.storeToXML(out, "...");
} catch (IOException e) {
	e.printStackTrace();
}
void loadFromXML(InputStream in)
try (InputStream in = new FileInputStream("info.xml")) {
	info.loadFromXML(in);
} catch (IOException e) {
	e.printStackTrace();
}
System.out.println(info);
练习
// 请设计一个猜数字小游戏,可以试玩5次。试玩结束之后,给出提示:游戏试玩结束,请付费。
Properties info = new Properties();
try (Reader reader = new FileReader("info.properties")) {
	info.load(reader);  //读取properties文件内容
	int count = Integer.parseInt(info.getProperity("count"));  //获取属性count的值,强转为int
	if (count < 5) {
		GuessNumberGame.start();
		info.setProperty("count", ++count + "");  // 设置info内容
		try (Writer writer = new FileWriter("info.properties")) {  //try可以嵌套,可以被外层的catch捕获。
			info.store(writer,"...");  // 写入
		}
	} else {
		System.out.println("游戏试玩结束,请付费");
	}
} catch (IOException e) {
	e.printStackTrace();
}

class GuessNumberGame {
	private GuessNumberGame(){  // 因为这个类只有一个静态方法,所以为了不让用户创建这个类,可以把构造方法写成private
	}
	public static void start() {
		Random random = new Random();
		Scanner sc = new Scanner(System.in);
		int r = random.nextInt(100) + 1;
		while (true) {
			System.out.println("请输入您要猜的数字:");
            int n = sc.nextInt();
            if (n < r) System.out.println("您猜的数字" + n + "小了");
            else if (n > r) System.out.println("您猜的数字" + n + "大了");
            else {
                System.out.println("恭喜您,猜中了!");
                break;
            }
		}
	}
}
/*给定两个字符串s和t, 它们只包含小写字母。字符串t是由s中的字母随机打乱之后,在随机的一个位置添加一个字母生成的。请找出那个添加的字母。
比如:
s = "abcd"
t = "baedc"
思路1:统计字符串s中每个字符出现的次数arr1(数组)
      统计字符串t中每个字符出现的次数arr2(数组)
      遍历数组,找到arr2[i]比arr1[1]大的索引。a + i
思路2:
    a ^ a = 0
    0 ^ a = a
    a ^ b = b ^ a */
public static char findTheDifference(String s, String t) {  // 思路二做法。异或性质
	int xor = 0;
	for (int i = 0; i < s.length(); i++) xor ^= s.charAt(i);
	for (int i = 0; i < s.length(); i++) xor ^= s.charAt(i);
	return (char) xor;
}

Map小结

Map
    |-- HashMap
        |-- LinkedHashMap
    |-- Hashtable
        |-- Properties
    |-- TreeMap
Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐