【Java编程的逻辑】Map和Set

2024-09-07 15:18
文章标签 java 逻辑 编程 set map

本文主要是介绍【Java编程的逻辑】Map和Set,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

HashMap

Map有键和值的概念。一个键映射到一个值,Map按照键存储和访问值,键不能重复。

HashMap实现了Map接口。

基本原理

HashMap的基本实现原理:内部有一个哈希表,即数组table,每个元素table[i]指向一个单向链表,根据键存取值,用键算出hash值,取模得到数组中的索引位置index,然后操作table[index]指向的单向链表。
存取的时候依据键的hash值,只在对应的链表中操作,不会访问别的链表,在对应链表操作时也是先比较hash值,如果相同再用equals方法比较。这就要求,相同的对象其hashCode返回值必须相同,如果键是自定的类,就特别需要注意。

HashMap扩容策略:

  1. 什么时候扩容? 当添加第一个元素时,默认分配的大小为16,不过,并不是size大于16时再进行扩展,下次什么时候扩展与threshold(阈值)有关。threshold表示阈值,当键值对个数size大于等于threshold的时候考虑进行扩容。threshold一般情况下,等于table.length乘以loadFactor(负载因子,默认0.75)。
  2. 怎么扩容? table的长度总是2的倍数,所以扩容就是先将table长度x2,然后再进行转换。转换的主要工作是重新计算键值对的数组下标。

注意: Java8对HashMap的实现进行了优化,在哈希冲突比较严重的情况下,即大量元素映射到同一个链表的情况下(具体是至少8个元素,且总的键值队个数至少是64),Java8会将该链表转换为一个平衡的排序二叉树。

小结

HashMap实现了Map接口,可以方便地按照键存取值,内部使用数组链表和哈希的方式进行实现
1. 根据键保存和获取值的效率都很高,为O(1),每个单向链表往往只有一个或少数几个节点,根据hash值可以直接快速定位。
2. HashMap支持key为null,key为null的时候,放在table[0]
3. HashMap中的键值对没有顺序,因为hash值是随机的
4. HashMap不是线程安全的,多线程环境下可以使用Hashtable或ConcurrentHashMap。

HashSet

HashSet实现了Set接口。 Set表示的是没有重复元素、且不保证顺序的容器接口,它扩展了Collection,但没有定义任何新的方法。

原理

HashSet内部是用HashMap实现的,它内部有一个HashMap实例变量

private transient HashMap<E,Object> map;

Map是有键和值的,HashSet相当于只有键,值都是相同的固定值,这个值是:

// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();

相应的add,get等方法都是间接的调用HashMap的方法了

小结

  1. 没有重复元素
  2. 可以高效地添加、删除元素、判断元素是否存在,效率都为O(1)
  3. 没有顺序
  4. 非线程安全

TreeMap

TreeMap中,键值对之间按键有序,TreeMap的实现基础是排序二叉树。

基本用法

TreeMap有两个常用构造方法

public TreeMap();
public TreeMap(Comparator<? super K> comparator)

第一个为默认构造方法,要求Map中的键实现Comparabe接口,TreeMap内部进行各种比较时会调用键的Comparabe接口中的compareTo方法。
第二个接口一个比较器对象comparator,如果comparator不为null,在TreeMap内部进行比较时会调用这个comparator的方法,而不再调用键的compareTo方法。

原理

TreeMap内部是用红黑树实现的,红黑树是一种大致平衡的排序二叉树。
内部主要有如下成员:

// 比较器
private final Comparator<? super K> comparator;  
// 树的根节点, Entry是节点类型
private transient Entry<K,V> root; 
// 当前键值对个数
private transient int size = 0;static final class Entry<K,V> implements Map.Entry<K,V> {// 键K key;// 值V value;// 左孩子Entry<K,V> left;// 右孩子Entry<K,V> right;// 父节点Entry<K,V> parent;// 节点颜色,非黑即红boolean color = BLACK;
}

保存键值对

public V put(K key, V value) {Entry<K,V> t = root;// 1. 第一次添加if (t == null) {// 判断key是否为nullcompare(key, key); // type (and possibly null) check// 直接将root指向该节点root = new Entry<>(key, value, null);size = 1;modCount++;return null;}// ...
}
// 当root为null的时候,主要是判断key是否为null
final int compare(Object k1, Object k2) {return comparator==null ? ((Comparable<? super K>)k1).compareTo((K)k2): comparator.compare((K)k1, (K)k2);
}
  1. 当添加第一个节点时,root为null,主要就是新建一个节点,设置root指向它。并判断key是否为null
public V put(K key, V value) {// ... // 如果不是第一次添加  int cmp;Entry<K,V> parent;// split comparator and comparable pathsComparator<? super K> cpr = comparator;// 如果设置了 comparator  if (cpr != null) {do {// 一开始指向跟节点parent = t;            cmp = cpr.compare(key, t.key);// 如果小于根节点,就将t设为左孩子,继续比较if (cmp < 0)t = t.left;// 如果大于,就将t设为右孩子,继续比较    else if (cmp > 0)t = t.right;else// 如果有值,表示已经有这个键了return t.setValue(value);// 如果t为null,则退出循环,parent就指向待插入节点的父节点    } while (t != null);}else {if (key == null)throw new NullPointerException();@SuppressWarnings("unchecked")Comparable<? super K> k = (Comparable<? super K>) key;do {parent = t;cmp = k.compareTo(t.key);if (cmp < 0)t = t.left;else if (cmp > 0)t = t.right;elsereturn t.setValue(value);} while (t != null);}Entry<K,V> e = new Entry<>(key, value, parent);if (cmp < 0)parent.left = e;elseparent.right = e;fixAfterInsertion(e);size++;modCount++;return null;
}
  1. 如果不是第一次添加, 先寻找父节点。寻找父节点根据是否设置了comparator分为两种情况。两种情况查找父节点的逻辑基本相同,只是如果没有设置comparator,则假设key一定实现了Comparable接口,同时key不能为null。如果自己设置了comparator,则可以为null。
  2. fixAfterInsertion(e):在调整数的结构,使之符合红黑树的约束,保持大致平衡。

小结

TreeMap同样实现了Map接口,但内部使用红黑树实现,红黑树是统计效率比较高的大致平衡的二叉树
1. 按键有序,TreeMap同样实现了SortedMap和NavigableMap接口,可以方便地根据键的顺序进行查找,如第一个、最后一个、某一范围的键等
2. 为了按键有序,TreeMap要求键实现Comparable接口或通过构造方法提供一个Comparator对象
3. 根据键保存、查找、删除的效率比较高,为O(h),h为树的高度,在树平衡的情况下,h为log2(N),N为节点数

TreeSet

TreeSet它实现了Set接口,在内部实现上,它基于TreeMap实现。
1. 没有重复元素
2. 添加、删除元素、判断元素是否存在,效率比较高,为O(log2N) ,N为元素个数
3. 有序 , 要求元素实现Comparable接口或通过构造方法提供Comparator对象

LinkedHashMap

LinkedHashMap是HashMap的子类,内部还有一个双向链表维护键值对的顺序。LinkedHashMap可以保持元素按插入或访问有序,这与TreeMap按键排序不同。

  • 按插入排序容易理解,先添加的在前面,后添加的在后面,修改操作不影响排序。
  • 访问排序:所谓访问就是指get/put操作,对一个键执行get/put操作后,其对应的键值对会移动链表末尾,所以,最末尾的是最近访问的,最开始的是最久没有被访问的。

基本使用

默认情况下,LinkedHashMap是按照插入排序的,想要按照访问排序得使用如下的构造方法:

public LinkedHashMap(int initialCapacity,float loadFactor,boolean accessOrder) {super(initialCapacity, loadFactor);this.accessOrder = accessOrder;
}

其中参数accessOrder就是用来指定是否按访问顺序,如果为true,就是访问顺序。

什么时候希望保持插入顺序呢?
当接收一些键值对输入,处理,然后输出,输出时希望保持原来的顺序。

什么时候希望按访问有序呢?
一种典型的应用是LRU缓存。 一般而言,缓存容量有限,不能无限存储所有数据,如果缓存满了,当需要存储数据时,就需要一定的策略将一些老的数据清理出去,这个策略一般称为替换算法。LRU是一种流行的替换算法,它的全称是Least Recently Used,即最近最少使用。它的思路是,最近刚被使用的很快再次被用的可能性最高,而最久没被访问的很快再被访问的可能性最低,所以被有限清理。
使用LinkedHashMap,可以非常容易地实现LRU缓存,默认情况下,LinkedHashMap没有对容量做限制,但它可以容易地做的,它有一个方法:

protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {return false;
}

在添加元素到LinkedHashMap后,会调用这个方法,传递的参数是最久没有被访问的键值对,如果这个方法返回true,则这个最久的键值对就会被删除。因为LinkedHashMap是没有容量限制的,所以默认总是返回false。

实现原理

LinkedHashMap是HashMap的子类,内部增加了如下的变量:

// 双向链表的头
private transient Entry<K,V> header;
// 表示按访问排序还是按插入排序
private final boolean accessOrder;

LinkedHashSet

LinkedHashSet是HashSet的子类,内部的Map实现类是LinkedHashMap

这篇关于【Java编程的逻辑】Map和Set的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1145410

相关文章

Java编译生成多个.class文件的原理和作用

《Java编译生成多个.class文件的原理和作用》作为一名经验丰富的开发者,在Java项目中执行编译后,可能会发现一个.java源文件有时会产生多个.class文件,从技术实现层面详细剖析这一现象... 目录一、内部类机制与.class文件生成成员内部类(常规内部类)局部内部类(方法内部类)匿名内部类二、

SpringBoot实现数据库读写分离的3种方法小结

《SpringBoot实现数据库读写分离的3种方法小结》为了提高系统的读写性能和可用性,读写分离是一种经典的数据库架构模式,在SpringBoot应用中,有多种方式可以实现数据库读写分离,本文将介绍三... 目录一、数据库读写分离概述二、方案一:基于AbstractRoutingDataSource实现动态

揭秘Python Socket网络编程的7种硬核用法

《揭秘PythonSocket网络编程的7种硬核用法》Socket不仅能做聊天室,还能干一大堆硬核操作,这篇文章就带大家看看Python网络编程的7种超实用玩法,感兴趣的小伙伴可以跟随小编一起... 目录1.端口扫描器:探测开放端口2.简易 HTTP 服务器:10 秒搭个网页3.局域网游戏:多人联机对战4.

Springboot @Autowired和@Resource的区别解析

《Springboot@Autowired和@Resource的区别解析》@Resource是JDK提供的注解,只是Spring在实现上提供了这个注解的功能支持,本文给大家介绍Springboot@... 目录【一】定义【1】@Autowired【2】@Resource【二】区别【1】包含的属性不同【2】@

springboot循环依赖问题案例代码及解决办法

《springboot循环依赖问题案例代码及解决办法》在SpringBoot中,如果两个或多个Bean之间存在循环依赖(即BeanA依赖BeanB,而BeanB又依赖BeanA),会导致Spring的... 目录1. 什么是循环依赖?2. 循环依赖的场景案例3. 解决循环依赖的常见方法方法 1:使用 @La

Java枚举类实现Key-Value映射的多种实现方式

《Java枚举类实现Key-Value映射的多种实现方式》在Java开发中,枚举(Enum)是一种特殊的类,本文将详细介绍Java枚举类实现key-value映射的多种方式,有需要的小伙伴可以根据需要... 目录前言一、基础实现方式1.1 为枚举添加属性和构造方法二、http://www.cppcns.co

Elasticsearch 在 Java 中的使用教程

《Elasticsearch在Java中的使用教程》Elasticsearch是一个分布式搜索和分析引擎,基于ApacheLucene构建,能够实现实时数据的存储、搜索、和分析,它广泛应用于全文... 目录1. Elasticsearch 简介2. 环境准备2.1 安装 Elasticsearch2.2 J

Java中的String.valueOf()和toString()方法区别小结

《Java中的String.valueOf()和toString()方法区别小结》字符串操作是开发者日常编程任务中不可或缺的一部分,转换为字符串是一种常见需求,其中最常见的就是String.value... 目录String.valueOf()方法方法定义方法实现使用示例使用场景toString()方法方法

Java中List的contains()方法的使用小结

《Java中List的contains()方法的使用小结》List的contains()方法用于检查列表中是否包含指定的元素,借助equals()方法进行判断,下面就来介绍Java中List的c... 目录详细展开1. 方法签名2. 工作原理3. 使用示例4. 注意事项总结结论:List 的 contain

Java实现文件图片的预览和下载功能

《Java实现文件图片的预览和下载功能》这篇文章主要为大家详细介绍了如何使用Java实现文件图片的预览和下载功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... Java实现文件(图片)的预览和下载 @ApiOperation("访问文件") @GetMapping("