18.2.4 HashSet去重原理与TreeSet排序机制

1. HashSet去重原理

1.1 HashSet基本结构

HashSet基于HashMap实现,利用HashMap的key唯一性来保证元素不重复。

public class HashSet<E> extends AbstractSet<E> implements Set<E> {
    
    // 内部使用HashMap存储数据
    private transient HashMap<E,Object> map;
    
    // 所有value都使用这个虚拟对象
    private static final Object PRESENT = new Object();
    
    // 构造方法
    public HashSet() {
        map = new HashMap<>();
    }
    
    public HashSet(int initialCapacity) {
        map = new HashMap<>(initialCapacity);
    }
    
    public HashSet(int initialCapacity, float loadFactor) {
        map = new HashMap<>(initialCapacity, loadFactor);
    }
}

1.2 HashSet的核心操作

public class HashSetOperations {
    
    // 添加元素
    public boolean add(E e) {
        return map.put(e, PRESENT) == null;
    }
    
    // 删除元素
    public boolean remove(Object o) {
        return map.remove(o) == PRESENT;
    }
    
    // 检查是否包含元素
    public boolean contains(Object o) {
        return map.containsKey(o);
    }
    
    // 获取大小
    public int size() {
        return map.size();
    }
    
    // 清空集合
    public void clear() {
        map.clear();
    }
    
    // 判断是否为空
    public boolean isEmpty() {
        return map.isEmpty();
    }
}

1.3 HashSet去重机制详解

public class HashSetDeduplication {
    
    public void demonstrateDeduplication() {
        HashSet<String> set = new HashSet<>();
        
        // 添加重复元素
        System.out.println(set.add("apple"));   // true - 首次添加
        System.out.println(set.add("banana"));  // true - 首次添加
        System.out.println(set.add("apple"));   // false - 重复元素
        
        System.out.println("Set size: " + set.size()); // 2
        System.out.println("Set contents: " + set);    // [apple, banana]
    }
    
    // 自定义对象去重示例
    public void customObjectDeduplication() {
        HashSet<Person> personSet = new HashSet<>();
        
        Person p1 = new Person("张三", 25);
        Person p2 = new Person("李四", 30);
        Person p3 = new Person("张三", 25); // 与p1相同
        
        personSet.add(p1);
        personSet.add(p2);
        personSet.add(p3);
        
        System.out.println("Person set size: " + personSet.size()); // 取决于Person类的equals和hashCode实现
    }
    
    // 正确实现equals和hashCode的Person类
    static class Person {
        private String name;
        private int age;
        
        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }
        
        @Override
        public boolean equals(Object obj) {
            if (this == obj) return true;
            if (obj == null || getClass() != obj.getClass()) return false;
            
            Person person = (Person) obj;
            return age == person.age && Objects.equals(name, person.name);
        }
        
        @Override
        public int hashCode() {
            return Objects.hash(name, age);
        }
        
        @Override
        public String toString() {
            return "Person{name='" + name + "', age=" + age + "}";
        }
    }
}

1.4 HashSet去重原理深入分析

public class HashSetInternalMechanism {
    
    // 模拟HashSet的add方法内部逻辑
    public boolean simulateAdd(Object element) {
        // 1. 计算元素的hashCode
        int hash = element.hashCode();
        System.out.println("Element hashCode: " + hash);
        
        // 2. 根据hash值定位到HashMap的桶
        int bucketIndex = hash & (capacity - 1); // 简化的索引计算
        System.out.println("Bucket index: " + bucketIndex);
        
        // 3. 在桶中查找是否存在相同元素
        // 这里会调用equals方法进行比较
        for (Object existing : getBucketElements(bucketIndex)) {
            if (existing.equals(element)) {
                System.out.println("Element already exists, not added");
                return false; // 元素已存在,不添加
            }
        }
        
        // 4. 元素不存在,添加到集合中
        addToBucket(bucketIndex, element);
        System.out.println("Element added successfully");
        return true;
    }
    
    // 演示hash冲突情况下的去重
    public void demonstrateHashCollision() {
        HashSet<BadHashObject> set = new HashSet<>();
        
        BadHashObject obj1 = new BadHashObject("A", 1);
        BadHashObject obj2 = new BadHashObject("B", 2);
        BadHashObject obj3 = new BadHashObject("A", 1); // 与obj1相同
        
        set.add(obj1);
        set.add(obj2);
        set.add(obj3);
        
        System.out.println("Set size: " + set.size()); // 2,因为obj1和obj3相同
    }
    
    // 故意设计hash冲突的类
    static class BadHashObject {
        private String value;
        private int id;
        
        public BadHashObject(String value, int id) {
            this.value = value;
            this.id = id;
        }
        
        @Override
        public int hashCode() {
            return 1; // 所有对象都返回相同的hash值,制造冲突
        }
        
        @Override
        public boolean equals(Object obj) {
            if (this == obj) return true;
            if (obj == null || getClass() != obj.getClass()) return false;
            
            BadHashObject that = (BadHashObject) obj;
            return id == that.id && Objects.equals(value, that.value);
        }
        
        @Override
        public String toString() {
            return "BadHashObject{value='" + value + "', id=" + id + "}";
        }
    }
}

2. TreeSet排序机制

2.1 TreeSet基本结构

TreeSet基于红黑树实现,内部使用TreeMap存储数据,自动保持元素的有序性。

public class TreeSet<E> extends AbstractSet<E> implements NavigableSet<E> {
    
    // 内部使用NavigableMap(实际是TreeMap)
    private transient NavigableMap<E,Object> m;
    
    // 所有value都使用这个虚拟对象
    private static final Object PRESENT = new Object();
    
    // 构造方法
    public TreeSet() {
        this(new TreeMap<>());
    }
    
    public TreeSet(Comparator<? super E> comparator) {
        this(new TreeMap<>(comparator));
    }
    
    public TreeSet(Collection<? extends E> c) {
        this();
        addAll(c);
    }
    
    public TreeSet(SortedSet<E> s) {
        this(s.comparator());
        addAll(s);
    }
    
    TreeSet(NavigableMap<E,Object> m) {
        this.m = m;
    }
}

2.2 TreeSet的核心操作

public class TreeSetOperations {
    
    // 添加元素
    public boolean add(E e) {
        return m.put(e, PRESENT) == null;
    }
    
    // 删除元素
    public boolean remove(Object o) {
        return m.remove(o) == PRESENT;
    }
    
    // 检查是否包含元素
    public boolean contains(Object o) {
        return m.containsKey(o);
    }
    
    // 获取第一个元素
    public E first() {
        return m.firstKey();
    }
    
    // 获取最后一个元素
    public E last() {
        return m.lastKey();
    }
    
    // 获取小于指定元素的最大元素
    public E lower(E e) {
        return m.lowerKey(e);
    }
    
    // 获取大于指定元素的最小元素
    public E higher(E e) {
        return m.higherKey(e);
    }
    
    // 获取子集
    public SortedSet<E> subSet(E fromElement, E toElement) {
        return new TreeSet<>(m.subMap(fromElement, true, toElement, false));
    }
}

2.3 TreeSet排序机制详解

public class TreeSetSorting {
    
    // 自然排序示例
    public void naturalOrderSorting() {
        TreeSet<Integer> numbers = new TreeSet<>();
        
        // 添加元素(无序)
        numbers.add(5);
        numbers.add(2);
        numbers.add(8);
        numbers.add(1);
        numbers.add(9);
        numbers.add(3);
        
        System.out.println("TreeSet (natural order): " + numbers);
        // 输出: [1, 2, 3, 5, 8, 9] - 自动排序
    }
    
    // 自定义排序示例
    public void customComparatorSorting() {
        // 降序排列
        TreeSet<Integer> descendingNumbers = new TreeSet<>(Collections.reverseOrder());
        
        descendingNumbers.add(5);
        descendingNumbers.add(2);
        descendingNumbers.add(8);
        descendingNumbers.add(1);
        
        System.out.println("TreeSet (descending): " + descendingNumbers);
        // 输出: [8, 5, 2, 1]
        
        // 字符串长度排序
        TreeSet<String> stringsByLength = new TreeSet<>((s1, s2) -> {
            int lengthCompare = Integer.compare(s1.length(), s2.length());
            return lengthCompare != 0 ? lengthCompare : s1.compareTo(s2);
        });
        
        stringsByLength.add("apple");
        stringsByLength.add("banana");
        stringsByLength.add("cat");
        stringsByLength.add("dog");
        
        System.out.println("TreeSet (by length): " + stringsByLength);
        // 输出: [cat, dog, apple, banana]
    }
    
    // 自定义对象排序
    public void customObjectSorting() {
        TreeSet<Student> students = new TreeSet<>();
        
        students.add(new Student("张三", 85));
        students.add(new Student("李四", 92));
        students.add(new Student("王五", 78));
        students.add(new Student("赵六", 95));
        
        System.out.println("Students sorted by score:");
        for (Student student : students) {
            System.out.println(student);
        }
    }
    
    // 实现Comparable接口的Student类
    static class Student implements Comparable<Student> {
        private String name;
        private int score;
        
        public Student(String name, int score) {
            this.name = name;
            this.score = score;
        }
        
        @Override
        public int compareTo(Student other) {
            // 按分数降序排列
            int scoreCompare = Integer.compare(other.score, this.score);
            // 如果分数相同,按姓名升序排列
            return scoreCompare != 0 ? scoreCompare : this.name.compareTo(other.name);
        }
        
        @Override
        public String toString() {
            return "Student{name='" + name + "', score=" + score + "}";
        }
        
        @Override
        public boolean equals(Object obj) {
            if (this == obj) return true;
            if (obj == null || getClass() != obj.getClass()) return false;
            
            Student student = (Student) obj;
            return score == student.score && Objects.equals(name, student.name);
        }
        
        @Override
        public int hashCode() {
            return Objects.hash(name, score);
        }
    }
}

2.4 TreeSet内部红黑树机制

public class TreeSetInternalMechanism {
    
    // 模拟TreeSet的add方法内部逻辑
    public boolean simulateAdd(Comparable element, TreeNode root) {
        if (root == null) {
            // 空树,直接插入作为根节点
            root = new TreeNode(element);
            return true;
        }
        
        TreeNode current = root;
        TreeNode parent = null;
        int compareResult;
        
        // 查找插入位置
        while (current != null) {
            parent = current;
            compareResult = element.compareTo(current.value);
            
            if (compareResult < 0) {
                current = current.left;
            } else if (compareResult > 0) {
                current = current.right;
            } else {
                // 元素已存在,不插入
                return false;
            }
        }
        
        // 创建新节点并插入
        TreeNode newNode = new TreeNode(element);
        compareResult = element.compareTo(parent.value);
        
        if (compareResult < 0) {
            parent.left = newNode;
        } else {
            parent.right = newNode;
        }
        
        newNode.parent = parent;
        
        // 红黑树平衡调整(简化)
        fixAfterInsertion(newNode);
        
        return true;
    }
    
    // 红黑树节点
    static class TreeNode {
        Comparable value;
        TreeNode left;
        TreeNode right;
        TreeNode parent;
        boolean red = true; // 新节点默认为红色
        
        TreeNode(Comparable value) {
            this.value = value;
        }
    }
    
    // 插入后的平衡调整(简化版本)
    private void fixAfterInsertion(TreeNode node) {
        // 红黑树的平衡调整逻辑
        // 这里是简化版本,实际实现更复杂
        while (node != null && node != root && node.parent.red) {
            if (node.parent == node.parent.parent.left) {
                TreeNode uncle = node.parent.parent.right;
                if (uncle != null && uncle.red) {
                    // Case 1: 叔叔节点是红色
                    node.parent.red = false;
                    uncle.red = false;
                    node.parent.parent.red = true;
                    node = node.parent.parent;
                } else {
                    // Case 2 & 3: 叔叔节点是黑色或null
                    if (node == node.parent.right) {
                        node = node.parent;
                        rotateLeft(node);
                    }
                    node.parent.red = false;
                    node.parent.parent.red = true;
                    rotateRight(node.parent.parent);
                }
            } else {
                // 对称情况
                TreeNode uncle = node.parent.parent.left;
                if (uncle != null && uncle.red) {
                    node.parent.red = false;
                    uncle.red = false;
                    node.parent.parent.red = true;
                    node = node.parent.parent;
                } else {
                    if (node == node.parent.left) {
                        node = node.parent;
                        rotateRight(node);
                    }
                    node.parent.red = false;
                    node.parent.parent.red = true;
                    rotateLeft(node.parent.parent);
                }
            }
        }
        root.red = false; // 根节点始终为黑色
    }
    
    // 左旋转
    private void rotateLeft(TreeNode nod

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

Java面试圣经 文章被收录于专栏

Java面试圣经,带你练透java圣经

全部评论

相关推荐

评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务