【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

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

Java进阶13讲__第12讲_1/2

多线程、线程池 1.  线程概念 1.1  什么是线程 1.2  线程的好处 2.   创建线程的三种方式 注意事项 2.1  继承Thread类 2.1.1 认识  2.1.2  编码实现  package cn.hdc.oop10.Thread;import org.slf4j.Logger;import org.slf4j.LoggerFactory

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

Linux 网络编程 --- 应用层

一、自定义协议和序列化反序列化 代码: 序列化反序列化实现网络版本计算器 二、HTTP协议 1、谈两个简单的预备知识 https://www.baidu.com/ --- 域名 --- 域名解析 --- IP地址 http的端口号为80端口,https的端口号为443 url为统一资源定位符。CSDNhttps://mp.csdn.net/mp_blog/creation/editor