首页 文章 精选 留言 我的

精选列表

搜索[Java],共10000篇文章
优秀的个人博客,低调大师

BeetlSQL 3.1.2 发布,Java 数据库访问工具

本次发布修改了较多Bug,建议升级 通过Json配置映射的Bug修复 代码生成MD文件到Maven工程的路径错误 代码生成Pojo信息未包含主键信息 代码生成math包导入错误 PageRequest 实现序列化接口 <dependency> <groupId>com.ibeetl</groupId> <artifactId>beetlsql</artifactId> <version>3.1.2-RELEASE</version> </dependency> BeetlSQL的目标是提供开发高效,维护高效,运行高效的数据库访问框架,在一个系统多个库的情况下,提供一致的编写代码方式。支持如下数据平台 传统数据库:MySQL,MariaDB,Oralce,Postgres,DB2,SQL Server,H2,SQLite,Derby,神通,达梦,华为高斯,人大金仓,PolarDB等 大数据:HBase,ClickHouse,Cassandar,Hive 物联网时序数据库:Machbase,TD-Engine,IotDB SQL查询引擎:Drill,Presto,Druid 内存数据库:ignite,CouchBase 阅读文档源码和例子 BeetlSQL3 性能测试 测试维度是ops/ms,每毫秒的调用次数 Benchmark Mode Cnt Score Error Units JMHMain.beetlsqlComplexMapping thrpt 5 212.378 ± 26.222 ops/ms JMHMain.beetlsqlExecuteJdbc thrpt 5 428.713 ± 66.192 ops/ms JMHMain.beetlsqlExecuteTemplate thrpt 5 374.943 ± 20.214 ops/ms JMHMain.beetlsqlFile thrpt 5 433.001 ± 65.448 ops/ms JMHMain.beetlsqlInsert thrpt 5 236.244 ± 112.102 ops/ms JMHMain.beetlsqlLambdaQuery thrpt 5 247.289 ± 19.310 ops/ms JMHMain.beetlsqlOne2Many thrpt 5 108.132 ± 10.934 ops/ms JMHMain.beetlsqlPageQuery thrpt 5 203.751 ± 9.395 ops/ms JMHMain.beetlsqlSelectById thrpt 5 393.437 ± 15.685 ops/ms JMHMain.jdbcExecuteJdbc thrpt 5 1083.310 ± 80.947 ops/ms JMHMain.jdbcInsert thrpt 5 308.341 ± 231.163 ops/ms JMHMain.jdbcSelectById thrpt 5 1019.370 ± 92.946 ops/ms JMHMain.jpaExecuteJdbc thrpt 5 94.600 ± 15.624 ops/ms JMHMain.jpaExecuteTemplate thrpt 5 133.017 ± 12.954 ops/ms JMHMain.jpaInsert thrpt 5 81.232 ± 26.971 ops/ms JMHMain.jpaOne2Many thrpt 5 101.506 ± 11.301 ops/ms JMHMain.jpaPageQuery thrpt 5 117.748 ± 4.512 ops/ms JMHMain.jpaSelectById thrpt 5 335.945 ± 27.186 ops/ms JMHMain.mybatisComplexMapping thrpt 5 102.402 ± 11.129 ops/ms JMHMain.mybatisExecuteTemplate thrpt 5 202.619 ± 16.978 ops/ms JMHMain.mybatisFile thrpt 5 151.151 ± 4.251 ops/ms JMHMain.mybatisInsert thrpt 5 141.469 ± 43.092 ops/ms JMHMain.mybatisLambdaQuery thrpt 5 15.558 ± 1.481 ops/ms JMHMain.mybatisPageQuery thrpt 5 63.705 ± 7.592 ops/ms JMHMain.mybatisSelectById thrpt 5 197.130 ± 19.461 ops/ms JMHMain.weedExecuteJdbc thrpt 5 416.941 ± 22.256 ops/ms JMHMain.weedExecuteTemplate thrpt 5 439.266 ± 57.130 ops/ms JMHMain.weedFile thrpt 5 477.561 ± 37.926 ops/ms JMHMain.weedInsert thrpt 5 231.444 ± 92.598 ops/ms JMHMain.weedLambdaQuery thrpt 5 422.707 ± 64.716 ops/ms JMHMain.weedPageQuery thrpt 5 246.018 ± 18.724 ops/ms JMHMain.weedSelectById thrpt 5 380.348 ± 20.968 ops/ms 性能测试代码

优秀的个人博客,低调大师

BeetlSQL 3.1.1 发布,Java 数据库访问工具

本次发布增加了少许功能 <dependency> <groupId>com.ibeetl</groupId> <artifactId>beetlsql</artifactId> <version>3.1.1-RELEASE</version> </dependency> Starter <dependency> <groupId>com.ibeetl</groupId> <artifactId>sql-springboot-starter</artifactId> <version>3.1.1-RELEASE</version> </dependency> Spring集成内置了uuid和雪花序列 Spring集成增加了支持事务超时的设置 修复了一个并发情况下的对数据库元数据操作的Bug Maven BeetlSQL的目标是提供开发高效,维护高效,运行高效的数据库访问框架,在一个系统多个库的情况下,提供一致的编写代码方式。支持如下数据平台 传统数据库:MySQL,MariaDB,Oralce,Postgres,DB2,SQL Server,H2,SQLite,Derby,神通,达梦,华为高斯,人大金仓,PolarDB等 大数据:HBase,ClickHouse,Cassandar,Hive 物联网时序数据库:Machbase,TD-Engine,IotDB SQL查询引擎:Drill,Presto,Druid 内存数据库:ignite,CouchBase 阅读文档源码和例子 BeetlSQL3 性能测试 测试维度是ops/ms,每毫秒的调用次数 Benchmark Mode Cnt Score Error Units JMHMain.beetlsqlComplexMapping thrpt 5 212.378 ± 26.222 ops/ms JMHMain.beetlsqlExecuteJdbc thrpt 5 428.713 ± 66.192 ops/ms JMHMain.beetlsqlExecuteTemplate thrpt 5 374.943 ± 20.214 ops/ms JMHMain.beetlsqlFile thrpt 5 433.001 ± 65.448 ops/ms JMHMain.beetlsqlInsert thrpt 5 236.244 ± 112.102 ops/ms JMHMain.beetlsqlLambdaQuery thrpt 5 247.289 ± 19.310 ops/ms JMHMain.beetlsqlOne2Many thrpt 5 108.132 ± 10.934 ops/ms JMHMain.beetlsqlPageQuery thrpt 5 203.751 ± 9.395 ops/ms JMHMain.beetlsqlSelectById thrpt 5 393.437 ± 15.685 ops/ms JMHMain.jdbcExecuteJdbc thrpt 5 1083.310 ± 80.947 ops/ms JMHMain.jdbcInsert thrpt 5 308.341 ± 231.163 ops/ms JMHMain.jdbcSelectById thrpt 5 1019.370 ± 92.946 ops/ms JMHMain.jpaExecuteJdbc thrpt 5 94.600 ± 15.624 ops/ms JMHMain.jpaExecuteTemplate thrpt 5 133.017 ± 12.954 ops/ms JMHMain.jpaInsert thrpt 5 81.232 ± 26.971 ops/ms JMHMain.jpaOne2Many thrpt 5 101.506 ± 11.301 ops/ms JMHMain.jpaPageQuery thrpt 5 117.748 ± 4.512 ops/ms JMHMain.jpaSelectById thrpt 5 335.945 ± 27.186 ops/ms JMHMain.mybatisComplexMapping thrpt 5 102.402 ± 11.129 ops/ms JMHMain.mybatisExecuteTemplate thrpt 5 202.619 ± 16.978 ops/ms JMHMain.mybatisFile thrpt 5 151.151 ± 4.251 ops/ms JMHMain.mybatisInsert thrpt 5 141.469 ± 43.092 ops/ms JMHMain.mybatisLambdaQuery thrpt 5 15.558 ± 1.481 ops/ms JMHMain.mybatisPageQuery thrpt 5 63.705 ± 7.592 ops/ms JMHMain.mybatisSelectById thrpt 5 197.130 ± 19.461 ops/ms JMHMain.weedExecuteJdbc thrpt 5 416.941 ± 22.256 ops/ms JMHMain.weedExecuteTemplate thrpt 5 439.266 ± 57.130 ops/ms JMHMain.weedFile thrpt 5 477.561 ± 37.926 ops/ms JMHMain.weedInsert thrpt 5 231.444 ± 92.598 ops/ms JMHMain.weedLambdaQuery thrpt 5 422.707 ± 64.716 ops/ms JMHMain.weedPageQuery thrpt 5 246.018 ± 18.724 ops/ms JMHMain.weedSelectById thrpt 5 380.348 ± 20.968 ops/ms 性能测试代码

优秀的个人博客,低调大师

JAVA必须掌握的数据结构和算法

常见的数据结构 链表 LinkedHashSet LinkedList 底层数据结构由链表和哈希表组成。 数据的添加和删除都较为方便,就是访问比较耗费时间。 数组 ArrayList 访问数据十分简单,而添加和删除数据比较耗工夫 堆 堆是一种图的树形结构,被用于实现“优先队列",优先队列是一种数据结构,可以自由添加数据,但取出数据时要从最小值开始按顺序取出 堆的特点: ①堆中的每个结点最多有两个子结点 ②子结点必定大于父结点 ③把新数据放在最下面一行靠左的位置。当最下面一行里没有多余空间时,就再往下另起一行,把数据加在这一行的最左端 ④如果子结点的数字小于父结点的,就将父结点与其左右两个子结点中较小的一个进行交换 堆中最顶端的数据始终最小,所以无论数据量有多少,取出最小值的时间复杂度都为O(1) 可知树的高度为log2n,那么重构树的时间复杂度便为O(logn) 栈 (LIFO) 略 队列 (FIFO) 略 哈希表 HashSet TreeSet底层数据结构是红黑树 哈希函数(Hash)计算key,哈希值除以数组的长度5,求得其余数。这个余数就是key的编号即位置 如果发生哈希冲突,就使用链表进行存储(链地址法)给数组设定合适的空间非常重要 除了链地址法以外,还有几种解决冲突的方法。其中,应用较为广泛的是“开放地址法”。这种方法是指当冲突发生时,立刻计算出一个候补地址(数组上的位置)并将数据存进去。如果仍然有冲突,便继续计算下一个候补地址,直到有空地址为止。可以通过多次使用哈希函数或“线性探测法”等方法计算候补地址。 二叉树 特点: ①第一个是每个结点的值均大于其左子树上任意一个结点的值 ②是每个结点的值均小于其右子树上任意一个结点的值 ③二叉查找树的最小结点要从顶端开始,往其左下的末端寻找。此处最小值为3。 ④二叉查找树的最大结点要从顶端开始,往其右下的末端寻找 添加数据的时候 与顶端数据比较 如果比他小就往左移,左边没有节点了就把插入的数据作为新节点添加到左下方,大于他则往右移(左小右大) 删除数据的时候 如果节点没有子节点 直接删 如果有一个 删了后子节点补上,如果有两个,删掉后从左子树中中找最大的补上 比较的次数取决于树的高度。所以如果结点数为n,而且树的形状又较为均衡的话,比较大小和移动的次数最多就是log2n。因此,时间复杂度为O(logn)。但是,如果树的形状朝单侧纵向延伸,树就会变得很高,此时时间复杂度也就变成了O(n)。 常见的算法整理 排序 冒泡排序 冒泡排序就是重复“从序列右边开始比较相邻两个数字的大小,再根据结果交换两个数字的位置”这一操作的算法。在这个过程中,数字会像泡泡一样,慢慢从右往左“浮”到序列的顶端,所以这个算法才被称为“冒泡排序” 冒泡排序的时间复杂度为O(n2) 选择排序 选择排序就是重复“从待排序的数据中寻找最小值,将其与序列最左边的数字进行交换”这一操作的算法。在序列中寻找最小值时使用的是线性查找 每轮中交换数字的次数最多为1次。如果输入数据就是按从小到大的顺序排列的,便不需要进行任何交换。选择排序的时间复杂度也和冒泡排序的一样,都为O(n2)。 插入排序 插入排序的思路就是从右侧的未排序区域内取出一个数据,然后将它插入到已排序区域内合适的位置上 时间复杂度和冒泡排序的一样,都为O(n2)。 堆排序 首先堆中存储所有的数据,并按降序来构建堆,然后从降序排列的堆中取出数据时会从最大的数据开始取,所以将取出的数据反序输出,排序就完成了。 堆排序的时间复杂度为O(nlogn)。 归并排序 归并排序算法会把序列分成长度相同的两个子序列,当无法继续往下分时(也就是每个子序列中只有一个数据时),就对子序列进行归并。归并指的是把两个排好序的子序列合并成一个有序序列。该操作会一直重复执行,直到所有子序列都归并为一个整体为止。 运行时间复杂度为O(nlogn) 快速排序 快速排序算法首先会在序列中随机选择一个基准值(pivot),然后将除了基准值以外的数分为“比基准值小的数”和“比基准值大的数”这两个类别。解决子问题的时候会再次使用快速排序,甚至在这个快速排序里仍然要使用快速排序。只有在子问题里只剩一个数字的时候,排序才算完成。 整体的时间复杂度为O(nlogn)。 数组查找 线性查找 线性查找需要从头开始不断地按顺序检查数据,因此在数据量大且目标数据靠后,或者目标数据不存在时,比较的次数就会更多,也更为耗时。若数据量为n,线性查找的时间复杂度便为O(n)。 二分查找(略) 图的搜索 广度优先搜索 广度优先搜索是一种对图进行搜索的算法。假设我们一开始位于某个顶点(即起点),此时并不知道图的整体结构,而我们的目的是从起点开始顺着边搜索,直到到达指定顶点(即终点)。在此过程中每走到一个顶点,就会判断一次它是否为终点。广度优先搜索会优先从离起点近的顶点开始搜索 深度优先搜索 深度优先搜索和广度优先搜索一样,都是对图进行搜索的算法,目的也都是从起点开始搜索直到到达指定顶点(终点)。深度优先搜索会沿着一条路径不断往下搜索直到不能再继续为止,然后再折返,开始搜索下一条候补路径。 贝尔曼-福特算法(略) 贝尔曼-福特(Bellman-Ford)算法是一种在图中求解最短路径问题的算法 狄克斯特拉算法(略) A*算法(略) 安全算法 共享密钥加密 公开密钥加密 混合加密 迪菲-赫尔曼交换 其他算法 k-means 算法 欧几里得算法 素性测试 网页排名 汉诺塔 【拓展】 图的表示:邻接矩阵和邻接表 遍历算法:深度搜索和广度搜索(必学) 最短路径算法:Floyd,Dijkstra(必学) 最小生成树算法:Prim,Kruskal(必学) 实际常用算法:关键路径、拓扑排序(原理与应用) 二分图匹配:配对、匈牙利算法(原理与应用) 拓展:中心性算法、社区发现算法(原理与应用) 2.图还是比较难的,不过我觉得图涉及到的挺多算法都是挺实用的,例如最短路径的计算等,图相关的,我这里还是建议看书的,可以看《算法第四版》。 3、搜索与回溯算法 贪心算法(必学) 启发式搜索算法:A*寻路算法(了解) 地图着色算法、N 皇后问题、最优加工顺序 旅行商问题 这方便的只是都是一些算法相关的,我觉得如果可以,都学一下。像贪心算法的思想,就必须学的了。建议通过刷题来学习,leetcode 直接专题刷。 4、动态规划 树形DP:01背包问题 线性DP:最长公共子序列、最长公共子串 区间DP:矩阵最大值(和以及积) 数位DP:数字游戏 状态压缩DP:旅行商 我觉得动态规划是最难的一个算法思想了,记得当初第一次接触动态规划的时候,是看01背包问题的,看了好久都不大懂,懵懵懂懂,后面懂了基本思想,可是做题下不了手,但是看的懂答案。一气之下,再leetcdoe专题连续刷了几十道,才掌握了动态规划的套路,也有了自己的一套模板。不过说实话,动态规划,是考的真他妈多,学习算法、刷题,一定要掌握。这里建议先了解动态规划是什么,之后 leetcode 专题刷,反正就一般上面这几种题型。 5、字符匹配算法 正则表达式 模式匹配:KMP、Boyer-Moore 6、流相关算法 最大流:最短增广路、Dinic 算法 最大流最小割:最大收益问题、方格取数问题 最小费用最大流:最小费用路、消遣 本文分享 CSDN - 爱吃早餐的程序员。如有侵权,请联系 support@oschina.cn 删除。本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

优秀的个人博客,低调大师

java安全编码指南之:锁的双重检测

简介 双重检测锁定模式是一种设计模式,我们通过首次检测锁定条件而不是实际获得锁从而减少获取锁的开销。 双重检查锁定模式用法通常用于实现执行延迟初始化的单例工厂模式。延迟初始化推迟了成员字段或成员字段引用的对象的构造,直到实际需要才真正的创建。 但是我们需要非常小心的使用双重检测模式,以避免发送错误。 单例模式的延迟加载 先看一个在单线程正常工作的单例模式: public class Book { private static Book book; public static Book getBook(){ if(book==null){ book = new Book(); } return book; }} 上面的类中定义了一个getBook方法来返回一个新的book对象,返回对象之前,我们先判断了book是否为空,如果不为空的话就new一个book对象。 初看起来,好像没什么问题,我们仔细考虑一下: book=new Book()其实一个复杂的命令,并不是原子性操作。它大概可以分解为1.分配内存,2.实例化对象,3.将对象和内存地址建立关联。 在多线程环境中,因为重排序的影响,我们可能的到意向不到的结果。 最简单的办法就是加上synchronized关键字: public class Book { private static Book book; public synchronized static Book getBook(){ if(book==null){ book = new Book(); } return book; }} double check模式 如果要使用double check模式该怎么做呢? public class BookDLC { private static BookDLC bookDLC; public static BookDLC getBookDLC(){ if(bookDLC == null ){ synchronized (BookDLC.class){ if(bookDLC ==null){ bookDLC=new BookDLC(); } } } return bookDLC; }} 我们先判断bookDLC是否为空,如果为空,说明需要实例化一个新的对象,这时候我们锁住BookDLC.class,然后再进行一次为空判断,如果这次不为空,则进行初始化。 那么上的代码有没有问题呢? 有,bookDLC虽然是一个static变量,但是因为CPU缓存的原因,我们并不能够保证当前线程被赋值之后的bookDLC,立马对其他线程可见。 所以我们需要将bookDLC定义为volatile,如下所示: public class BookDLC { private volatile static BookDLC bookDLC; public static BookDLC getBookDLC(){ if(bookDLC == null ){ synchronized (BookDLC.class){ if(bookDLC ==null){ bookDLC=new BookDLC(); } } } return bookDLC; }} 静态域的实现 public class BookStatic { private static BookStatic bookStatic= new BookStatic(); public static BookStatic getBookStatic(){ return bookStatic; }} JVM在类被加载之后和被线程使用之前,会进行静态初始化,而在这个初始化阶段将会获得一个锁,从而保证在静态初始化阶段内存写入操作将对所有的线程可见。 上面的例子定义了static变量,在静态初始化阶段将会被实例化。这种方式叫做提前初始化。 下面我们再看一个延迟初始化占位类的模式: public class BookStaticLazy { private static class BookStaticHolder{ private static BookStaticLazy bookStatic= new BookStaticLazy(); } public static BookStaticLazy getBookStatic(){ return BookStaticHolder.bookStatic; }} 上面的类中,只有在调用getBookStatic方法的时候才会去初始化类。 ThreadLocal版本 我们知道ThreadLocal就是Thread的本地变量,它实际上是对Thread中的成员变量ThreadLocal.ThreadLocalMap的封装。 所有的ThreadLocal中存放的数据实际上都存储在当前线程的成员变量ThreadLocal.ThreadLocalMap中。 如果使用ThreadLocal,我们可以先判断当前线程的ThreadLocal中有没有,没有的话再去创建。 如下所示: public class BookThreadLocal { private static final ThreadLocal<BookThreadLocal> perThreadInstance = new ThreadLocal<>(); private static BookThreadLocal bookThreadLocal; public static BookThreadLocal getBook(){ if (perThreadInstance.get() == null) { createBook(); } return bookThreadLocal; } private static synchronized void createBook(){ if (bookThreadLocal == null) { bookThreadLocal = new BookThreadLocal(); } perThreadInstance.set(bookThreadLocal); }} 本文分享自微信公众号 - 程序那些事(flydean-tech)。如有侵权,请联系 support@oschina.cn 删除。本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

优秀的个人博客,低调大师

java类中无法识别依赖包的问题

前段时间因为当前项目比较闲,被换到其他项目组做事情。换项目组带来的问题是,需要下载新的项目,并配置新的开发环境。这次换项目的过程中,有个环节让我花了不少时间折腾,以下就是遇到的问题。 问题: 下载新的项目,并配置新的开发环境后,启动项目时发现异常。异常情况为,依赖包都下载,也都更新了,但是类中始终无法识别到依赖包。 解决办法: 删除之前所有的依赖包,重新导入依赖包。

优秀的个人博客,低调大师

听说同学你搞不懂Java的LinkedHashMap,可笑

同学们好啊,还记得 HashMap 那篇吗?我自己感觉写得非常棒啊,既通俗易懂,又深入源码,真的是分析得透透彻彻、清清楚楚、明明白白的。(一不小心又上仨成语?)HashMap 哪哪都好,真的,只要你想用键值对,第一时间就应该想到它。 但俗话说了,“金无足赤人无完人”,HashMap 也不例外。有一种需求它就满足不了,假如我们需要一个按照插入顺序来排列的键值对集合,那 HashMap 就无能为力了。因为为了提高查找效率,HashMap 在插入的时候对键做了一次哈希算法,这就导致插入的元素是无序的。 对这一点还不太明白的同学,可以再回到 HashMap 那一篇,看看我对 put() 方法的讲解。 finalVputVal(inthash,Kkey,Vvalue,booleanonlyIfAbsent,booleanevict){HashMap.Node<K,V>[]tab;HashMap.Node<K,V>p;intn,i;//①、数组table为null时,调用resize方法创建默认大小的数组if((tab=table)==null||(n=tab.length)==0)n=(tab=resize()).length;//②、计算下标,如果该位置上没有值,则填充if((p=tab[i=(n-1)&hash])==null)tab[i]=newNode(hash,key,value,null);} 这个公式 i = (n - 1) & hash 计算后的值并不是按照 0、1、2、3、4、5 这样有序的下标将键值对插入到数组当中的,而是有一定的随机性。 那 LinkedHashMap 就是为这个需求应运而生的。LinkedHashMap 继承了 HashMap,所以 HashMap 有的关于键值对的功能,它也有了。 publicclassLinkedHashMap<K,V>extendsHashMap<K,V>implementsMap<K,V>{} 此外,LinkedHashMap 内部又追加了双向链表,来维护元素的插入顺序。注意下面代码中的 before 和 after,它俩就是用来维护当前元素的前一个元素和后一个元素的顺序的。 staticclassEntry<K,V>extendsHashMap.Node<K,V>{LinkedHashMap.Entry<K,V>before,after;Entry(inthash,Kkey,Vvalue,HashMap.Node<K,V>next){super(hash,key,value,next);}} 关于双向链表,同学们可以回头看一遍我写的 LinkedList 那篇文章,会对理解本篇的 LinkedHashMap 有很大的帮助。 在继续下面的内容之前,我先贴一张图片,给大家增添一点乐趣——看我这心操的。UUID 那篇文章的标题里用了“可笑”和“你”,结果就看到了下面这么乐呵的留言。 (到底是知道还是不知道,我搞不清楚了。。。)那 LinkedHashMap 这篇也用了“你”和“可笑”,不知道到时候会不会有人继续对号入座啊,想想就觉得特别欢乐。 01、插入顺序 在 HashMap 那篇文章里,我有讲解到一点,不知道同学们记不记得,就是 null 会插入到 HashMap 的第一位。 Map<String,String>hashMap=newHashMap<>();hashMap.put("沉","沉默王二");hashMap.put("默","沉默王二");hashMap.put("王","沉默王二");hashMap.put("二","沉默王二");hashMap.put(null,null);for(Stringkey:hashMap.keySet()){System.out.println(key+":"+hashMap.get(key));} 输出的结果是: null:null默:沉默王二沉:沉默王二王:沉默王二二:沉默王二 虽然 null 最后一位 put 进去的,但在遍历输出的时候,跑到了第一位。 那再来对比看一下 LinkedHashMap。 Map<String,String>linkedHashMap=newLinkedHashMap<>();linkedHashMap.put("沉","沉默王二");linkedHashMap.put("默","沉默王二");linkedHashMap.put("王","沉默王二");linkedHashMap.put("二","沉默王二");linkedHashMap.put(null,null);for(Stringkey:linkedHashMap.keySet()){System.out.println(key+":"+linkedHashMap.get(key));} 输出结果是: 沉:沉默王二默:沉默王二王:沉默王二二:沉默王二null:null null 在最后一位插入,在最后一位输出。 输出结果可以再次证明,HashMap 是无序的,LinkedHashMap 是可以维持插入顺序的。 那 LinkedHashMap 是如何做到这一点呢?我相信同学们和我一样,非常希望知道原因。 要想搞清楚,就需要深入研究一下 LinkedHashMap 的源码。LinkedHashMap 并未重写 HashMap 的 put() 方法,而是重写了 put() 方法需要调用的内部方法 newNode()。 HashMap.Node<K,V>newNode(inthash,Kkey,Vvalue,HashMap.Node<K,V>e){LinkedHashMap.Entry<K,V>p=newLinkedHashMap.Entry<>(hash,key,value,e);linkNodeLast(p);returnp;} 前面说了,LinkedHashMap.Entry 继承了 HashMap.Node,并且追加了两个字段 before 和 after。 那,紧接着来看看 linkNodeLast() 方法: privatevoidlinkNodeLast(LinkedHashMap.Entry<K,V>p){LinkedHashMap.Entry<K,V>last=tail;tail=p;if(last==null)head=p;else{p.before=last;last.after=p;}} 看到了吧,LinkedHashMap 在添加第一个元素的时候,会把 head 赋值为第一个元素,等到第二个元素添加进来的时候,会把第二个元素的 before 赋值为第一个元素,第一个元素的 afer 赋值为第二个元素。 这就保证了键值对是按照插入顺序排列的,明白了吧? 注:我用到的 JDK 版本为 14。 02、访问顺序 LinkedHashMap 不仅能够维持插入顺序,还能够维持访问顺序。访问包括调用 get() 方法、remove() 方法和 put() 方法。 要维护访问顺序,需要我们在声明 LinkedHashMap 的时候指定三个参数。 LinkedHashMap<String,String>map=newLinkedHashMap<>(16,.75f,true); 第一个参数和第二个参数,看过 HashMap 的同学们应该很熟悉了,指的是初始容量和负载因子。 第三个参数如果为 true 的话,就表示 LinkedHashMap 要维护访问顺序;否则,维护插入顺序。默认是 false。 Map<String,String>linkedHashMap=newLinkedHashMap<>(16,.75f,true);linkedHashMap.put("沉","沉默王二");linkedHashMap.put("默","沉默王二");linkedHashMap.put("王","沉默王二");linkedHashMap.put("二","沉默王二");System.out.println(linkedHashMap);linkedHashMap.get("默");System.out.println(linkedHashMap);linkedHashMap.get("王");System.out.println(linkedHashMap); 输出的结果如下所示: {沉=沉默王二,默=沉默王二,王=沉默王二,二=沉默王二}{沉=沉默王二,王=沉默王二,二=沉默王二,默=沉默王二}{沉=沉默王二,二=沉默王二,默=沉默王二,王=沉默王二} 当我们使用 get() 方法访问键位“默”的元素后,输出结果中,默=沉默王二 在最后;当我们访问键位“王”的元素后,输出结果中,王=沉默王二 在最后,默=沉默王二 在倒数第二位。 也就是说,最不经常访问的放在头部,这就有意思了。有意思在哪呢? 我们可以使用 LinkedHashMap 来实现 LRU 缓存,LRU 是 Least Recently Used 的缩写,即最近最少使用,是一种常用的页面置换算法,选择最近最久未使用的页面予以淘汰。 publicclassMyLinkedHashMap<K,V>extendsLinkedHashMap<K,V>{privatestaticfinalintMAX_ENTRIES=5;publicMyLinkedHashMap(intinitialCapacity,floatloadFactor,booleanaccessOrder){super(initialCapacity,loadFactor,accessOrder);}@OverrideprotectedbooleanremoveEldestEntry(Map.Entryeldest){returnsize()>MAX_ENTRIES;}} MyLinkedHashMap 是一个自定义类,它继承了 LinkedHashMap,并且重写了 removeEldestEntry() 方法——使 Map 最多可容纳 5 个元素,超出后就淘汰。 我们来测试一下。 MyLinkedHashMap<String,String>map=newMyLinkedHashMap<>(16,0.75f,true);map.put("沉","沉默王二");map.put("默","沉默王二");map.put("王","沉默王二");map.put("二","沉默王二");map.put("一枚有趣的程序员","一枚有趣的程序员");System.out.println(map);map.put("一枚有颜值的程序员","一枚有颜值的程序员");System.out.println(map);map.put("一枚有才华的程序员","一枚有才华的程序员");System.out.println(map); 输出结果如下所示: {沉=沉默王二,默=沉默王二,王=沉默王二,二=沉默王二,一枚有趣的程序员=一枚有趣的程序员}{默=沉默王二,王=沉默王二,二=沉默王二,一枚有趣的程序员=一枚有趣的程序员,一枚有颜值的程序员=一枚有颜值的程序员}{王=沉默王二,二=沉默王二,一枚有趣的程序员=一枚有趣的程序员,一枚有颜值的程序员=一枚有颜值的程序员,一枚有才华的程序员=一枚有才华的程序员} 沉=沉默王二 和 默=沉默王二 依次被淘汰出局。 假如在 put “一枚有才华的程序员”之前 get 了键位为“默”的元素: MyLinkedHashMap<String,String>map=newMyLinkedHashMap<>(16,0.75f,true);map.put("沉","沉默王二");map.put("默","沉默王二");map.put("王","沉默王二");map.put("二","沉默王二");map.put("一枚有趣的程序员","一枚有趣的程序员");System.out.println(map);map.put("一枚有颜值的程序员","一枚有颜值的程序员");System.out.println(map);map.get("默");map.put("一枚有才华的程序员","一枚有才华的程序员");System.out.println(map); 那输出结果就变了,对吧? {沉=沉默王二,默=沉默王二,王=沉默王二,二=沉默王二,一枚有趣的程序员=一枚有趣的程序员}{默=沉默王二,王=沉默王二,二=沉默王二,一枚有趣的程序员=一枚有趣的程序员,一枚有颜值的程序员=一枚有颜值的程序员}{二=沉默王二,一枚有趣的程序员=一枚有趣的程序员,一枚有颜值的程序员=一枚有颜值的程序员,默=沉默王二,一枚有才华的程序员=一枚有才华的程序员} 沉=沉默王二 和 王=沉默王二 被淘汰出局了。 那 LinkedHashMap 是如何来维持访问顺序呢?同学们感兴趣的话,可以研究一下下面这三个方法。 voidafterNodeAccess(Node<K,V>p){}voidafterNodeInsertion(booleanevict){}voidafterNodeRemoval(Node<K,V>p){} afterNodeAccess() 会在调用 get() 方法的时候被调用,afterNodeInsertion() 会在调用 put() 方法的时候被调用,afterNodeRemoval() 会在调用 remove() 方法的时候被调用。 我来以 afterNodeAccess() 为例来讲解一下。 voidafterNodeAccess(HashMap.Node<K,V>e){//movenodetolastLinkedHashMap.Entry<K,V>last;if(accessOrder&&(last=tail)!=e){LinkedHashMap.Entry<K,V>p=(LinkedHashMap.Entry<K,V>)e,b=p.before,a=p.after;p.after=null;if(b==null)head=a;elseb.after=a;if(a!=null)a.before=b;elselast=b;if(last==null)head=p;else{p.before=last;last.after=p;}tail=p;++modCount;}} 哪个元素被 get 就把哪个元素放在最后。了解了吧? 那同学们可能还想知道,为什么 LinkedHashMap 能实现 LRU 缓存,把最不经常访问的那个元素淘汰? 在插入元素的时候,需要调用 put() 方法,该方法最后会调用 afterNodeInsertion() 方法,这个方法被 LinkedHashMap 重写了。 voidafterNodeInsertion(booleanevict){//possiblyremoveeldestLinkedHashMap.Entry<K,V>first;if(evict&&(first=head)!=null&&removeEldestEntry(first)){Kkey=first.key;removeNode(hash(key),key,null,false,true);}} removeEldestEntry() 方法会判断第一个元素是否超出了可容纳的最大范围,如果超出,那就会调用 removeNode() 方法对最不经常访问的那个元素进行删除。 03、最后 由于 LinkedHashMap 要维护双向链表,所以 LinkedHashMap 在插入、删除操作的时候,花费的时间要比 HashMap 多一些。 这也是没办法的事,对吧,欲戴皇冠必承其重嘛。既然想要维护元素的顺序,总要付出点代价才行。 那这篇文章就到此戛然而止了,同学们要觉得意犹未尽,请肆无忌惮地留言告诉我哦。(一不小心又在文末甩仨成语,有点文化底蕴,对吧?) ------------------ 公众号:沉默王二(ID:cmower)CSDN:沉默王二这是一个有颜值却靠才华吃饭的程序员,你知道,他的文章风趣幽默,读起来就好像花钱一样爽快。 长按下图二维码关注,你将感受到一个有趣的灵魂,且每篇文章都有干货。 ------------------ 原创不易,莫要白票,如果觉得有点用的话,请毫不留情地素质三连吧,分享、点赞、在看,我不挑,因为这将是我写作更多优质文章的最强动力。 PS:公布一下周日的中奖名单,有小伙伴表示,人生第一次中奖啊,激动啊,二哥,你咋这么棒!哎呀,那股激动的劲啊,放心,还会有的,只要你们想白嫖,二哥就给机会。 本文分享自微信公众号 - 沉默王二(cmower)。如有侵权,请联系 support@oschina.cn 删除。本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册