java HashSet、TreeSet、HashMap

HashSet直接继承自Set接口;TreeSet是继承自SortedSet接口,SortedSet继承自Set接口。

HashSet

特征
(1)存储时的顺序和取出来的顺序不同(无序指的是没有下标)

(2)不可重复;

(3)放到HashSet集合中的元素实际上是放到HashMap集合的key上边了。

例:

public class A{
    public static void main(String[] args) {
        Set<String> hs = new HashSet<>();
        hs.add("a");
        hs.add("1");
        hs.add("10");
        hs.add("3");
        hs.add("b");
        hs.add("g");
        System.out.println(hs); // [a, 1, b, 3, g, 10]
        for (String item : hs) {
            System.out.println(item);
        }
    }
}

TreeSet

特征
(1)无序,不可重复的,存储的元素会自动按照大小进行排序,称为可排序集合;

public class A{
    public static void main(String[] args) {
        Set<String> hs = new TreeSet<>();
        hs.add("a");
        hs.add("z");
        hs.add("h");
        hs.add("k");
        hs.add("y");
        hs.add("b");
        System.out.println(hs); // [a, b, h, k, y, z]
        for (String item : hs) {
            System.out.println(item);
        }
    }
}

Map<K,V>

HashSet和TreeSet底层都是Map,下边记录下Map接口。

特点

(1)以键值对(key、value)形式存储数据,key和value都是引用类型,都是存储对象引用,key起主导作用,value只是key的附属品。
(2)Map和Collection没有继承关系。
(3)key特点:无序(为什么无序下边put原理可以明白)、不可重复(不可重复原因equals方法作为保证,详情查看下边put原理)。

Map接口常用方法

(1)void clear( )
清空Map集合。

public class A{
    public static void main(String[] args) {
        Map<String, String> map = new HashMap<>();
        map.put("name", "a");
        map.put("age", "20");
        map.put("address", "shandong");
        map.clear(); // 清空集合
        System.out.println(map.size()); // 0
    }
}

(2)boolean containsKey( Object key )
判断Map集合中是否包含某个key。
containsKey() 方法内部会调用equals() 来判断是否相等

public class A{
    public static void main(String[] args) {
        Map<String, String> map = new HashMap<>();
        map.put("name", "a");
        map.put("age", "20");
        map.put("address", "shandong");
        System.out.println(map.containsKey("name")); // true
        System.out.println(map.containsKey(new String("name"))); // true
    }
}

(3)boolean containsValue( Object value )
判断Map集合中是否包含某个value。
containsValue() 方法内部会调用equals() 来判断是否相等

public class A{
    public static void main(String[] args) {
        Map<String, String> map = new HashMap<>();
        map.put("name", "a");
        map.put("age", "20");
        map.put("address", "shandong");
        System.out.println(map.containsValue("20")); // true
        System.out.println(map.containsValue(new String("20"))); // true
    }
}

(4)V get( Object key )
向Map集合中添加键值对。
get() 方法获取一个不存在的key返回null

public class A{
    public static void main(String[] args) {
        Map<String, String> map = new HashMap<>();
        map.put("name", "a");
        map.put("age", "20");
        map.put("address", "shandong");
        System.out.println(map.get("address")); // "shandong"
        System.out.println(map.get(new String("0"))); // null
    }
}

(5)boolean isEmpty()
判断Map集合中元素个数是否为0。

public class A{
    public static void main(String[] args) {
        Map<String, String> map = new HashMap<>();
        map.put("name", "a");
        map.put("age", "20");
        map.put("address", "shandong");
        System.out.println(map.isEmpty()); // false
    }
}

(6)Set<K> keySet()
获取Map集合中的所有key(所有的键是一个set集合)。

public class A{
    public static void main(String[] args) {
        Map<String, String> map = new HashMap<>();
        map.put("name", "a");
        map.put("age", "20");
        map.put("address", "shandong");
        Set<String> k = map.keySet();
        System.out.println(k); // ["address", "name", "age"]
    }
}

(7)Set<Map.Entry<K, V>> entrySet()
Map.Entry<K, V> 就是一个静态内部类,Set集合中存储的类型是Map.Entry类型。

public class A{
    public static void main(String[] args) {
        Map<String, String> map = new HashMap<>();
        map.put("name", "a");
        map.put("age", "20");
        map.put("address", "shandong");
        // 把Map集合转Set集合,里边的元素为Map.Entry类型
        Set<Map.Entry<String, String>> set = map.entrySet();
        // 方法一(效率较高,因为会直接从Node对象中获取key和value):
        for (Map.Entry<String, String> item : set) {
            System.out.println(item.getKey()+"="+item.getValue());
        }
        // 方法二:
        // 遍历Set集合,每次取出来的是一个Node对象(在源码中Node是一个类)
        Iterator<Map.Entry<String, String>> it = set.iterator();
        while (it.hasNext()) {
            Map.Entry<String, String> res = it.next();
            String k = res.getKey();
            String v = res.getValue();
            System.out.println(k+"="+v);
        }
    }
}

(8)V put(K key, V value)
向Map集合中添加键值对。

public class A{
    public static void main(String[] args) {
        Map<String, String> map = new HashMap<>();
        map.put("name", "a");
        map.put("age", "20");
        map.put("address", "shandong");
        System.out.println(map.size()); // 3
    }
}

(9)V remove(Object key)
通过key删除键值对,返回的是被删除的value。

public class A{
    public static void main(String[] args) {
        Map<String, String> map = new HashMap<>();
        map.put("name", "a");
        map.put("age", "20");
        map.put("address", "shandong");
        String v = map.remove(new String("name"));
        System.out.println(v); // "a"
        System.out.println(map); // {address=shandong, age=20}
    }
}

(10)int size()
获取Map集合中键值对的个数。

public class A{
    public static void main(String[] args) {
        Map<String, String> map = new HashMap<>();
        map.put("name", "a");
        map.put("age", "20");
        map.put("address", "shandong");
        System.out.println(map.size()); // 3
    }
}

(11)Collection<V> values()
获取Map集合中键值对的个数。

public class A{
    public static void main(String[] args) {
        Map<String, String> map = new HashMap<>();
        map.put("name", "a");
        map.put("age", "20");
        map.put("address", "shandong");
        Collection<String> vs = map.values();
        System.out.println(vs); // ["shandong", "a", "20"]
    }
}

遍历Map集合常用方法

(1)通过keySet获取所有的key,然后遍历key获取value值(下边方式遍历效率较低,通常我们会采用:entrySet( ) 方法 

public class A{
    public static void main(String[] args) {
        Map<String, String> map = new HashMap<>();
        map.put("name", "a");
        map.put("age", "20");
        map.put("address", "shandong");
        Set<String> k = map.keySet();
        // 方法一:
        for (String item : k) {
            System.out.println(map.get(item));
        }
        // 方法二:
        Iterator<String> it = k.iterator();
        while (it.hasNext()) {
            String res = it.next();
            System.out.println(res);
        }
    }
}

Hash表的数据结构(散列表)

1、哈希表的数据结构:

它是一个数组和单向链表的结合体。
数组:在查询方面效率较高,随机增删方面效率较低。
单链表:在随机增删方面效率较高,在查询检索方面效率较低。
哈希表将以上的两种数据结构融合在一起,充分发挥了它们的优点。

HashMap源码:
底层实际就是一个数组,

静态内部类HashMap.Node

map.put(k, v)、map.get(k)实现原理:

put() 方法实现原理:

get() 方法实现原理:

为什么哈希表的随机增删,以及查询效率都很高?

增删是在单向链表上完成的,而查询也不需要都扫描,只需要部分扫描。

总结

(1)通过上边示意图,HashMap集合中的key,会先后调用两个方法,一个方法是hashCode,一个方法是equals,且两个方法都需要重写。

(2)同一个单向列表上,所有节点的 hash 值 相同,因为它们的数组下标是一样的,但是,同一链表上k与k的equals方法返回的肯定是false了。

(3)哈希表HashMap使用不当时,无法发挥性能!
假设将所有的hashCode()方法返回的值固定为某个值,那么会导致底层哈希表变为一个纯单向链表啦。这种情况我们称为:散列分布不均匀。

什么是散列分布均匀:
假设有100个元素,10个单向链表,那么每一个单向链表上有10个节点,这是最好的:是散列分布均匀。

假设所有的hashCode()方法返回值都不一样,会有什么问题?
这样会导致底层哈希表称为一个纯一维数组了,没有链表的概念了,也是散列分布不均匀。

(4)HashMap集合的默认初始化容量为16,默认加载因子为:0.75。

默认加载因子:
当HashMap底层数组的容量超出75%的时候,此时数组会自动扩容。

(5)HashMap集合的初始化容量为16,如果你觉着不够用,那么自己定义的容量必须为2的倍数,这也是官方推荐的,这是因为达到散列均匀,为了提高HashMap集合的存储效率,所以必须是2的倍数。

(6)如果一个类的equals() 方法重写了,那么hashCode() 方法必须要重写(原因如下),equals()方法返回true,那么hashCode()返回值必须一样,因为equals() 方法返回true,表示两个对象相同,在同一个单链表上比较的,对于同一个单链表上的节点来说,他们的哈希值都是相同的,所以hashCode() 方法的返回值也应该相同。

下边Student类中,只重写了equals方法,分别把stu和stu1添加到HashSet集合中,按道理元素个数应该是1,因为Studente的quals方法重写了,但是结果却返回2,原因:在new HashSet时,底层其实是new HashMap,等同于向HashMap中添加元素,而向HashMap添加元素,第一步会调用hashCode方法返回哈希值,但是Student类没有重写hashCode方法,所以返回的哈希值不一样,此时会Student类会被放到不同的一个单向链表中,所以size为2,解决此问题也很简单,还需要重写hashCode方法。

public class Person {
    public static void main(String[] args) {
        Student stu = new Student("lxc");
        Student stu1 = new Student("lxc");
        Set<Student> students = new HashSet<>();
        students.add(stu);
        students.add(stu1);
        System.out.println(students.size()); // 2
    }

}
class Student {
    private String name;
    public Student(String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return Objects.equals(name, student.name);
    }
}

解决:

public class Person {
    public static void main(String[] args) {
        Student stu = new Student("lxc");
        Student stu1 = new Student("lxc");
        Set<Student> students = new HashSet<>();
        students.add(stu);
        students.add(stu1);
        System.out.println(students.size()); // 1
    }

}
class Student {
    private String name;
    public Student(String name) {
        this.name = name;
    }
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return Objects.equals(name, student.name);
    }
    @Override
    public int hashCode() {
        return Objects.hash(name);
    }
}

(7)结论:
放在HashMap 的key部分的,以及放在HashSet集合中的元素,都需要重写hashCode和equals方法。

(8)在JDK8中,如果哈希表中单向链表上的元素超过8个,单向链表这种数据结构会变为红黑树数据结构,当红黑树上的节点小于6时,会重新把红黑树变为单向链表,这个改进为性能考虑。

HashMap线程不安全

多线程并发会造成之前put进去的值,get时不是之前的值。

解决方案:

* 方案一:Collections.synchronizedMap(new HashMap<>());
* 方案二:new ConcurrentHashMap();

实际开发建议用 ConcurrentHashMap 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值