MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

巧妙运用Java Map接口实现复杂业务逻辑

2021-11-191.9k 阅读

Java Map接口基础

Map接口概述

在Java编程中,Map接口是Java集合框架的重要组成部分。它提供了一种将键(key)映射到值(value)的存储方式,一个Map不能包含重复的键,每个键最多映射到一个值。Map接口定义了一些操作,用于插入、删除和查询键值对。它和Collection接口是Java集合框架中的两大主要接口类型,Collection主要用于存储单个元素,而Map用于存储键值对这种关联数据。

Map接口有多种实现类,常见的如HashMapTreeMapLinkedHashMap等。每种实现类都有其特点,在不同的应用场景下有着不同的性能表现和适用范围。

Map接口的常用方法

  1. put(K key, V value):将指定的键值对插入到Map中。如果Map中之前包含了该键的映射关系,则旧值会被替换。例如:
import java.util.HashMap;
import java.util.Map;

public class MapPutExample {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();
        map.put("one", 1);
        map.put("two", 2);
        Integer oldValue = map.put("two", 22);
        System.out.println("Old value for key 'two': " + oldValue);
    }
}

在上述代码中,首先向HashMap中插入了两个键值对,然后再次插入键为two的键值对,此时旧值2会被22替换,并返回旧值2。

  1. get(Object key):根据指定的键获取对应的值。如果Map中不存在该键,则返回null。示例如下:
import java.util.HashMap;
import java.util.Map;

public class MapGetExample {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();
        map.put("one", 1);
        Integer value = map.get("one");
        System.out.println("Value for key 'one': " + value);
        Integer nonExistentValue = map.get("three");
        System.out.println("Value for non - existent key 'three': " + nonExistentValue);
    }
}

这里获取了存在键one对应的值1,以及不存在键three对应的值null

  1. containsKey(Object key):判断Map中是否包含指定的键。返回一个布尔值,若包含则为true,否则为false
import java.util.HashMap;
import java.util.Map;

public class MapContainsKeyExample {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();
        map.put("one", 1);
        boolean hasKey = map.containsKey("one");
        System.out.println("Map contains key 'one': " + hasKey);
        boolean hasNonExistentKey = map.containsKey("two");
        System.out.println("Map contains non - existent key 'two': " + hasNonExistentKey);
    }
}
  1. size():返回Map中键值对的数量。
import java.util.HashMap;
import java.util.Map;

public class MapSizeExample {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();
        map.put("one", 1);
        map.put("two", 2);
        int size = map.size();
        System.out.println("Size of the map: " + size);
    }
}
  1. keySet():返回一个包含Map中所有键的Set集合。通过遍历这个Set可以获取Map中的所有键。
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class MapKeySetExample {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();
        map.put("one", 1);
        map.put("two", 2);
        Set<String> keySet = map.keySet();
        for (String key : keySet) {
            System.out.println("Key: " + key);
        }
    }
}
  1. values():返回一个包含Map中所有值的Collection集合。遍历该集合可以获取Map中的所有值。
import java.util.HashMap;
import java.util.Map;
import java.util.Collection;

public class MapValuesExample {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();
        map.put("one", 1);
        map.put("two", 2);
        Collection<Integer> values = map.values();
        for (Integer value : values) {
            System.out.println("Value: " + value);
        }
    }
}
  1. entrySet():返回一个包含Map中所有键值对的Set集合,集合中的元素类型为Map.EntryMap.Entry是一个内部接口,它包含了获取键和值的方法。通过遍历entrySet可以同时获取键和值。
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class MapEntrySetExample {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();
        map.put("one", 1);
        map.put("two", 2);
        Set<Map.Entry<String, Integer>> entrySet = map.entrySet();
        for (Map.Entry<String, Integer> entry : entrySet) {
            System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
        }
    }
}

利用Map实现数据分组

简单数据分组场景

在实际业务中,经常会遇到需要对数据进行分组的情况。例如,有一组学生成绩数据,需要按照成绩等级进行分组。假设成绩在90分及以上为A等级,80 - 89分为B等级,70 - 79分为C等级,60 - 69分为D等级,60分以下为E等级。

import java.util.*;

public class StudentGradeGrouping {
    public static void main(String[] args) {
        List<Integer> grades = Arrays.asList(85, 92, 78, 65, 58, 88, 72, 95);
        Map<String, List<Integer>> gradeGroupMap = new HashMap<>();

        for (int grade : grades) {
            String gradeLevel = getGradeLevel(grade);
            if (!gradeGroupMap.containsKey(gradeLevel)) {
                gradeGroupMap.put(gradeLevel, new ArrayList<>());
            }
            gradeGroupMap.get(gradeLevel).add(grade);
        }

        for (Map.Entry<String, List<Integer>> entry : gradeGroupMap.entrySet()) {
            System.out.println(entry.getKey() + "等级的成绩: " + entry.getValue());
        }
    }

    private static String getGradeLevel(int grade) {
        if (grade >= 90) {
            return "A";
        } else if (grade >= 80) {
            return "B";
        } else if (grade >= 70) {
            return "C";
        } else if (grade >= 60) {
            return "D";
        } else {
            return "E";
        }
    }
}

在上述代码中,首先定义了一个成绩列表grades,然后创建了一个Map,键为成绩等级,值为对应等级的成绩列表。通过遍历成绩列表,根据成绩获取对应的等级,并将成绩添加到相应等级的列表中。最后遍历Map,输出每个等级及其对应的成绩。

复杂对象数据分组

当处理更复杂的对象数据时,Map同样可以发挥作用。例如,假设有一个员工类Employee,包含员工姓名、部门和薪资信息。现在需要按照部门对员工进行分组,并计算每个部门的总薪资。

import java.util.*;

class Employee {
    private String name;
    private String department;
    private double salary;

    public Employee(String name, String department, double salary) {
        this.name = name;
        this.department = department;
        this.salary = salary;
    }

    public String getName() {
        return name;
    }

    public String getDepartment() {
        return department;
    }

    public double getSalary() {
        return salary;
    }
}

public class EmployeeDepartmentGrouping {
    public static void main(String[] args) {
        List<Employee> employees = Arrays.asList(
                new Employee("Alice", "HR", 5000.0),
                new Employee("Bob", "IT", 6000.0),
                new Employee("Charlie", "HR", 5500.0),
                new Employee("David", "Finance", 7000.0),
                new Employee("Eve", "IT", 6500.0)
        );

        Map<String, List<Employee>> departmentEmployeeMap = new HashMap<>();
        Map<String, Double> departmentTotalSalaryMap = new HashMap<>();

        for (Employee employee : employees) {
            String department = employee.getDepartment();
            if (!departmentEmployeeMap.containsKey(department)) {
                departmentEmployeeMap.put(department, new ArrayList<>());
            }
            departmentEmployeeMap.get(department).add(employee);

            if (!departmentTotalSalaryMap.containsKey(department)) {
                departmentTotalSalaryMap.put(department, 0.0);
            }
            departmentTotalSalaryMap.put(department, departmentTotalSalaryMap.get(department) + employee.getSalary());
        }

        for (Map.Entry<String, List<Employee>> entry : departmentEmployeeMap.entrySet()) {
            System.out.println(entry.getKey() + "部门的员工: " + entry.getValue());
        }

        for (Map.Entry<String, Double> entry : departmentTotalSalaryMap.entrySet()) {
            System.out.println(entry.getKey() + "部门的总薪资: " + entry.getValue());
        }
    }
}

在这个例子中,创建了两个Map,一个用于按照部门分组员工,另一个用于计算每个部门的总薪资。通过遍历员工列表,将员工添加到相应部门的列表中,并累加每个部门的薪资。最后分别输出每个部门的员工列表和总薪资。

Map在缓存中的应用

简单缓存实现

在很多应用场景中,为了提高系统性能,会使用缓存来减少对数据库或其他慢速资源的访问。可以利用Map简单地实现一个缓存。例如,假设有一个方法用于从数据库中获取用户信息,现在希望通过缓存来提高获取速度。

import java.util.HashMap;
import java.util.Map;

class User {
    private int id;
    private String name;

    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }
}

public class UserCache {
    private static Map<Integer, User> userCache = new HashMap<>();

    public static User getUserFromDB(int id) {
        // 模拟从数据库获取用户信息
        System.out.println("从数据库获取用户信息,id: " + id);
        return new User(id, "User" + id);
    }

    public static User getUser(int id) {
        if (userCache.containsKey(id)) {
            System.out.println("从缓存中获取用户信息,id: " + id);
            return userCache.get(id);
        } else {
            User user = getUserFromDB(id);
            userCache.put(id, user);
            return user;
        }
    }

    public static void main(String[] args) {
        User user1 = getUser(1);
        User user2 = getUser(1);
        User user3 = getUser(2);
    }
}

在上述代码中,定义了一个userCache用于缓存用户信息。getUser方法首先检查缓存中是否存在指定id的用户,如果存在则直接返回缓存中的用户,否则从数据库获取并将其添加到缓存中。通过这种方式,对于相同id的用户后续请求可以直接从缓存中获取,提高了性能。

缓存过期策略

实际应用中,缓存数据通常需要设置过期时间,以保证数据的时效性。可以通过结合Map和时间戳来实现简单的缓存过期策略。

import java.util.HashMap;
import java.util.Map;

class ExpirableCacheValue {
    private long expirationTime;
    private Object value;

    public ExpirableCacheValue(long expirationTime, Object value) {
        this.expirationTime = expirationTime;
        this.value = value;
    }

    public boolean isExpired() {
        return System.currentTimeMillis() > expirationTime;
    }

    public Object getValue() {
        return value;
    }
}

public class ExpirableUserCache {
    private static Map<Integer, ExpirableCacheValue> userCache = new HashMap<>();
    private static final long DEFAULT_EXPIRATION_TIME = 60 * 1000; // 60秒

    public static User getUserFromDB(int id) {
        // 模拟从数据库获取用户信息
        System.out.println("从数据库获取用户信息,id: " + id);
        return new User(id, "User" + id);
    }

    public static User getUser(int id) {
        if (userCache.containsKey(id)) {
            ExpirableCacheValue cacheValue = userCache.get(id);
            if (!cacheValue.isExpired()) {
                System.out.println("从缓存中获取用户信息,id: " + id);
                return (User) cacheValue.getValue();
            } else {
                userCache.remove(id);
            }
        }
        User user = getUserFromDB(id);
        long expirationTime = System.currentTimeMillis() + DEFAULT_EXPIRATION_TIME;
        userCache.put(id, new ExpirableCacheValue(expirationTime, user));
        return user;
    }

    public static void main(String[] args) {
        User user1 = getUser(1);
        try {
            Thread.sleep(70 * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        User user2 = getUser(1);
    }
}

在这个改进的缓存实现中,ExpirableCacheValue类用于封装缓存值及其过期时间。getUser方法在获取缓存值时,先检查缓存值是否过期,若未过期则返回,否则从缓存中移除并重新从数据库获取。通过这种方式,实现了简单的缓存过期策略,保证了缓存数据的时效性。

利用Map解决复杂业务中的唯一性和关联性问题

唯一性保证

在某些业务场景中,需要保证某些数据的唯一性。例如,在一个在线商城系统中,每个商品都有一个唯一的商品编号。可以利用Map的键唯一性特点来实现商品编号的唯一性验证。

import java.util.HashMap;
import java.util.Map;

class Product {
    private int productId;
    private String productName;

    public Product(int productId, String productName) {
        this.productId = productId;
        this.productName = productName;
    }

    public int getProductId() {
        return productId;
    }

    public String getProductName() {
        return productName;
    }
}

public class ProductUniquenessCheck {
    public static void main(String[] args) {
        Map<Integer, Product> productMap = new HashMap<>();
        Product product1 = new Product(1, "手机");
        Product product2 = new Product(2, "电脑");
        Product product3 = new Product(1, "平板"); // 尝试添加重复id的产品

        productMap.put(product1.getProductId(), product1);
        productMap.put(product2.getProductId(), product2);
        Product existingProduct = productMap.put(product3.getProductId(), product3);

        if (existingProduct != null) {
            System.out.println("产品编号已存在,无法添加产品: " + product3.getProductName());
        } else {
            System.out.println("产品添加成功: " + product3.getProductName());
        }
    }
}

在上述代码中,通过productMapput方法来添加产品。如果添加的产品编号已经存在,put方法会返回之前对应的产品,通过检查返回值可以判断产品编号是否唯一。

复杂关联性维护

在一些复杂业务中,数据之间存在复杂的关联关系。例如,在一个社交网络系统中,用户之间存在关注关系,一个用户可以关注多个其他用户,同时也会有多个用户关注自己。可以利用Map来维护这种复杂的关联关系。

import java.util.*;

class UserProfile {
    private String userId;
    private String name;

    public UserProfile(String userId, String name) {
        this.userId = userId;
        this.name = name;
    }

    public String getUserId() {
        return userId;
    }

    public String getName() {
        return name;
    }
}

public class SocialNetwork {
    private Map<String, Set<String>> followMap = new HashMap<>();

    public void followUser(String followerId, String followeeId) {
        if (!followMap.containsKey(followerId)) {
            followMap.put(followerId, new HashSet<>());
        }
        followMap.get(followerId).add(followeeId);

        if (!followMap.containsKey(followeeId)) {
            followMap.put(followeeId, new HashSet<>());
        }
        followMap.get(followeeId).add(followerId);
    }

    public Set<String> getFollowers(String userId) {
        return followMap.getOrDefault(userId, Collections.emptySet());
    }

    public Set<String> getFollowing(String userId) {
        return followMap.getOrDefault(userId, Collections.emptySet());
    }

    public static void main(String[] args) {
        SocialNetwork socialNetwork = new SocialNetwork();
        socialNetwork.followUser("user1", "user2");
        socialNetwork.followUser("user1", "user3");
        socialNetwork.followUser("user2", "user3");

        Set<String> user1Followers = socialNetwork.getFollowers("user1");
        Set<String> user1Following = socialNetwork.getFollowing("user1");

        System.out.println("user1的粉丝: " + user1Followers);
        System.out.println("user1关注的人: " + user1Following);
    }
}

在这个社交网络的示例中,followMap用于维护用户之间的关注关系。followUser方法实现了用户之间的相互关注逻辑,getFollowersgetFollowing方法分别用于获取指定用户的粉丝和其关注的人。通过MapSet的结合使用,有效地维护了这种复杂的社交网络关联关系。

Map在数据分析与统计中的应用

数据频次统计

在数据分析中,经常需要统计数据的出现频次。例如,在一个文本处理应用中,需要统计每个单词在文本中出现的次数。

import java.util.HashMap;
import java.util.Map;
import java.util.StringTokenizer;

public class WordFrequencyCounter {
    public static void main(String[] args) {
        String text = "this is a sample text. this text is for testing word frequency.";
        Map<String, Integer> wordFrequencyMap = new HashMap<>();
        StringTokenizer tokenizer = new StringTokenizer(text, " .,");

        while (tokenizer.hasMoreTokens()) {
            String word = tokenizer.nextToken();
            if (!wordFrequencyMap.containsKey(word)) {
                wordFrequencyMap.put(word, 1);
            } else {
                wordFrequencyMap.put(word, wordFrequencyMap.get(word) + 1);
            }
        }

        for (Map.Entry<String, Integer> entry : wordFrequencyMap.entrySet()) {
            System.out.println("单词 '" + entry.getKey() + "' 出现的次数: " + entry.getValue());
        }
    }
}

在上述代码中,通过StringTokenizer将文本分割成单词,然后利用Map来统计每个单词的出现次数。如果Map中不存在该单词,则将其计数设为1,否则将计数加1。最后遍历Map输出每个单词及其出现次数。

高级数据分析场景

在更复杂的数据分析场景中,Map也能发挥重要作用。例如,在一个销售数据分析系统中,有销售记录包含产品名称、销售地区和销售额信息。现在需要统计每个地区每种产品的总销售额。

import java.util.*;

class SaleRecord {
    private String product;
    private String region;
    private double amount;

    public SaleRecord(String product, String region, double amount) {
        this.product = product;
        this.region = region;
        this.amount = amount;
    }

    public String getProduct() {
        return product;
    }

    public String getRegion() {
        return region;
    }

    public double getAmount() {
        return amount;
    }
}

public class SalesDataAnalysis {
    public static void main(String[] args) {
        List<SaleRecord> saleRecords = Arrays.asList(
                new SaleRecord("ProductA", "Region1", 100.0),
                new SaleRecord("ProductB", "Region1", 150.0),
                new SaleRecord("ProductA", "Region2", 200.0),
                new SaleRecord("ProductB", "Region2", 250.0),
                new SaleRecord("ProductA", "Region1", 120.0)
        );

        Map<String, Map<String, Double>> regionProductSalesMap = new HashMap<>();

        for (SaleRecord record : saleRecords) {
            String region = record.getRegion();
            String product = record.getProduct();
            double amount = record.getAmount();

            if (!regionProductSalesMap.containsKey(region)) {
                regionProductSalesMap.put(region, new HashMap<>());
            }
            Map<String, Double> productSalesMap = regionProductSalesMap.get(region);
            if (!productSalesMap.containsKey(product)) {
                productSalesMap.put(product, amount);
            } else {
                productSalesMap.put(product, productSalesMap.get(product) + amount);
            }
        }

        for (Map.Entry<String, Map<String, Double>> regionEntry : regionProductSalesMap.entrySet()) {
            String region = regionEntry.getKey();
            System.out.println("地区: " + region);
            Map<String, Double> productSalesMap = regionEntry.getValue();
            for (Map.Entry<String, Double> productEntry : productSalesMap.entrySet()) {
                System.out.println("产品: " + productEntry.getKey() + ", 总销售额: " + productEntry.getValue());
            }
        }
    }
}

在这个销售数据分析的例子中,使用了一个嵌套的Map结构。外层Map的键是地区,值是一个内层Map,内层Map的键是产品名称,值是该地区该产品的总销售额。通过遍历销售记录,累加每个地区每种产品的销售额,最后输出每个地区及其包含产品的销售数据统计结果。

不同Map实现类在复杂业务中的选择

HashMap的特点与适用场景

HashMapMap接口最常用的实现类之一。它基于哈希表实现,具有较高的插入、查询和删除效率,平均时间复杂度为O(1)。但是在最坏情况下,例如哈希冲突严重时,时间复杂度会退化为O(n)。

HashMap适用于大多数需要快速查找和插入的场景,比如前面提到的缓存场景、数据分组场景等。在缓存场景中,由于通常需要快速地根据键获取缓存值,HashMap的快速查找特性可以很好地满足需求。在数据分组场景中,需要频繁地插入键值对(如将员工分组到不同部门),HashMap的快速插入性能也能提高程序效率。

TreeMap的特点与适用场景

TreeMap是基于红黑树实现的Map。它的特点是按键的自然顺序或自定义顺序对键值对进行排序。这使得TreeMap适用于需要按键排序的场景。

例如,在一个学生成绩管理系统中,如果需要按照学生的成绩排名来展示学生信息,可以使用TreeMap。将学生成绩作为键,学生信息作为值,这样TreeMap会自动按照成绩对学生信息进行排序。

import java.util.Map;
import java.util.TreeMap;

class Student implements Comparable<Student> {
    private String name;
    private int score;

    public Student(String name, int score) {
        this.name = name;
        this.score = score;
    }

    public String getName() {
        return name;
    }

    public int getScore() {
        return score;
    }

    @Override
    public int compareTo(Student other) {
        return Integer.compare(other.score, this.score);
    }
}

public class StudentScoreRanking {
    public static void main(String[] args) {
        Map<Student, String> studentRankingMap = new TreeMap<>();
        studentRankingMap.put(new Student("Alice", 85), "详情信息1");
        studentRankingMap.put(new Student("Bob", 90), "详情信息2");
        studentRankingMap.put(new Student("Charlie", 80), "详情信息3");

        for (Map.Entry<Student, String> entry : studentRankingMap.entrySet()) {
            System.out.println("学生: " + entry.getKey().getName() + ", 成绩: " + entry.getKey().getScore() + ", 详情: " + entry.getValue());
        }
    }
}

在上述代码中,Student类实现了Comparable接口,定义了按照成绩从高到低的比较逻辑。TreeMap会根据这个比较逻辑对学生信息进行排序并展示。

LinkedHashMap的特点与适用场景

LinkedHashMapHashMap的一个子类,它在保持HashMap高性能的同时,还维护了插入顺序或访问顺序。

如果在缓存场景中,希望按照访问顺序淘汰缓存数据(即最近最少使用,LRU策略),LinkedHashMap是一个很好的选择。可以通过重写removeEldestEntry方法来实现LRU缓存。

import java.util.LinkedHashMap;
import java.util.Map;

public class LRUCache<K, V> extends LinkedHashMap<K, V> {
    private final int capacity;

    public LRUCache(int capacity) {
        super(capacity, 0.75f, true);
        this.capacity = capacity;
    }

    @Override
    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        return size() > capacity;
    }

    public static void main(String[] args) {
        LRUCache<Integer, String> cache = new LRUCache<>(3);
        cache.put(1, "A");
        cache.put(2, "B");
        cache.put(3, "C");
        cache.get(2);
        cache.put(4, "D");

        for (Map.Entry<Integer, String> entry : cache.entrySet()) {
            System.out.println(entry.getKey() + ": " + entry.getValue());
        }
    }
}

在上述代码中,LRUCache继承自LinkedHashMap,通过设置accessOrdertrue来启用访问顺序。removeEldestEntry方法定义了缓存淘汰策略,当缓存大小超过设定容量时,淘汰最久未使用的元素。通过这种方式,LinkedHashMap实现了LRU缓存策略,适用于需要按照访问顺序管理数据的场景。

在实际复杂业务中,需要根据具体的业务需求和数据特点来选择合适的Map实现类,以充分发挥Map接口在实现复杂业务逻辑中的优势。