更新时间:2025 09 18 01:21:00 作者 :庆美网 围观 : 68次
本篇文章给大家谈谈jdk1.5,jdk1.5升级1.7后中文乱码,以及对应的知识点,文章可能有点长,但是希望大家可以阅读完,增长自己的知识,最重要的是希望对各位有所帮助,可以解决了您的问题,不要忘了收藏本站喔。
JDK 1.8 的 Stream API 提供了一组固定的中间操作,开发者若需实现自定义逻辑,往往需要使用状态化的 lambda 表达式或外部库,这通常导致代码复杂且难以维护。
Stream Gatherers 引入了 gather 方法,接受一个 Gatherer 对象。Gatherer 类似于 Collector,但用于中间操作而非终端操作。它由四个函数组成:initializer(初始化状态,可选)、integrator(处理每个元素)、combiner(合并并行状态,可选)和 finisher(结束处理,可选)。这允许开发者定义状态依赖的中间操作,例如跟踪前一个元素或缓冲输入。
例如,处理连续重复元素的去重操作,在 JDK 1.8 中可能需要手动维护状态,而在 Stream Gatherers 中可以直接使用 gather 定义更优雅的逻辑。
JDK 1.8 的 Stream API 在处理无限流时存在限制,许多操作(如 collect)需要终端操作,容易导致内存溢出或性能问题。
Stream Gatherers 设计上支持无限流的懒惰处理,通过 Gatherer 的机制可以动态生成输出流,特别适合如传感器数据流或事件流的场景。
例如,windowSliding 可以对无限流进行滑动窗口分析,而不会提前加载所有数据。
JDK 24 提供了五个内置 Gatherers,位于 java.util.stream.Gatherers 类中,包括:
这些操作在 JDK 1.8 中要么需要手动实现(如通过 reduce 或自定义收集器),要么完全不可用。例如,windowFixed(4) 可以轻松生成如 [[0,1,2,3], [4,5,6,7]] 的窗口,而 JDK 1.8 需要复杂的缓冲逻辑。
以下表格总结了部分内置 Gatherers 与 JDK 1.8 的对比:
通过标准库优化的内置 Gatherers,开发者可以避免手动实现复杂逻辑,从而减少性能开销。例如,mapConcurrent 使用虚拟线程技术,特别适合 I/O 密集型任务,相比 JDK 1.8 的 parallelStream().map(),能更好地利用现代硬件资源。
自定义 Gatherers 允许开发者针对特定场景优化逻辑,避免 JDK 1.8 中可能出现的冗余计算或内存使用。
**Gatherer 支持通过 combiner 函数实现并行执行,这与 JDK 1.8 的并行流类似,但更灵活。例如,mapConcurrent 可以配置最大并发数(maxConcurrency),并利用虚拟线程技术,适合阻塞操作的并发处理,相比 JDK 1.8 的 fork-join 池更高效。
Stream Gatherers 减少了复杂变换所需的代码量。例如,JDK 1.8 中实现滑动窗口可能需要手动维护缓冲区和状态,而使用 windowSliding 只需要一行代码。
这不仅提升了代码的可读性,还降低了手动实现的错误风险,特别是在并行场景下。
假设需要生成大小为 2 的滑动窗口。
从上述对比可见,Stream Gatherers 显著简化了实现,特别适合状态依赖的操作。
一个可能出乎意料的细节是,mapConcurrent 的引入利用了虚拟线程技术(Project Loom),这在 JDK 1.8 中完全不可用。它允许并发映射操作在虚拟线程上执行,特别适合 I/O 密集型任务,如网络请求或数据库查询,相比 JDK 1.8 的 parallelStream().map(),性能提升显著。
Stream Gatherers 在 JDK 24 中为 Java Stream API 带来了重大改进,相较于 JDK 1.8,它提供了更强的自定义能力、更好的无限流支持和优化的并行处理。通过内置 Gatherers 和自定义操作,开发者可以更高效地处理复杂流变换,提升代码可读性和性能。
对于HashMap想必大家都不陌生,无论是平时code还是面试都经常和它打交道。今天我们通过源码的层面来分析一下它的实现原理,注意本文基于的是JDK1.8。
问题是从哪边开始聊起呢?我觉得不妨先从一段熟悉的代码开始。
然后我们会迫不及待点开HashMap这个类,发现里面有大量的属性和方法,一脸懵逼。那就直接点开put方法?点了之后发现下面这段代码。
依旧一脸懵逼,完全没有看下去的欲望,怎么办?为什么会这样?原因是我们不了解HashMap的数据结构,什么意思?也就是说,当把key,value存储到HashMap中之后,不知道它们是以何种数据排列的方式去存储的,这样根本就不明白源码写的思路是什么,所以我们先把目光转向到数据结构,更准确地说是HashMap的数据结构。
从网上的很多资料我们知道,HashMap1.7的数据结构是数组+链表,HashMap1.8的数据结构是数组+链表+红黑树,下面这张图我画出了HashMap1.8的数据结构。
有些哥们可能会说,我在网上看到的不是这样。这时候你可以发挥空间想象能力逆时针旋转个90度,也就是下面这样的展示形式。
不管如何,反正都能体现出是数组+链表+红黑树的数据结构的方式。虽然数据结构是知道了,但是关键是图解中的每个小格子表示的是什么呢?对于我们了解HashMap的原理和源码有什么作用吗?先别急,一个个来看,先看每个小格子表示什么。
我们可以猜想一下,每个小格子表示里面至少包含了key,value,为什么这么说呢?因为hashmap.put(key,value)之后,就会形成上述的数组+链表+红黑树的结构,那这个结构中的每个小格子至少把key和value涵盖进去了,如果不是,那么key,value怎么存储呢?ok,假如这个猜想是对的,那Java中想要同时存储key,value两个值,该怎么表示呢?我觉得可以用XXX类,比如下面的伪代码。
我觉得靠谱,如果真的是这样,那么要想形成上述的数据结构的图解,只需要创建一个个XXX类的对象,然后排列好它们的方式不就ok了吗?没错,关键这个排列要形式数组+链表+红黑树的数据结构。我们暂且给XXX一个名称叫”Node”,于是就是这样了。
这时候有些哥们想,上面都是你主观的一个猜想,源码中真的是这样做的吗?我们不妨在HashMap类中搜索一下”Node”,发现有这样一个内部类。
ok,至此每个小格子表示的含义猜想和验证已经完成,发现源码中真的也有这样一个Node类,并且里面维护了key和value属性,至于其他属性是什么含义,我们后面再聊。
上面既然已经验证了小格子对应的就是Node类,或者可以称为是Node节点。接下来我们的任务就是将这些节点来排列成数组+链表+红黑树的形式。
想要将Node节点形式数组,按照以往的经验只需要在类中维护一个Node[]的属性即可,那么源码中是否有这样做呢?
我们会发现源码中维护了这样一个成员变量,Node
链表无非就是Node节点和Node节点的关系的维护,这个关系可以分为单向链表或者双向链表,在前面的图解中我们发现这个链表是单向的,但是如果要想在源码中验证这个单向链表,只需要在原来的Node类中维护一个Node属性,如下所示。
那源码中是否是这样做的呢?通过下面代码中的Node
红黑树是一种特殊的二叉树,对于二叉树我们比较熟悉,会有父节点,左子树,右子树等。
这时候我们会想,源码中是否有这样来做呢?搜索”TreeNode”,发现会有这样一段代码。
跟我们的猜想是一样的,有表示父节点的parent属性,有表示左子树的left属性,还有表示右子树的right属性。
通过上面3点源码的分析,可以感受到数组,链表和红黑树的代码,也就是能够验证HashMap1.8的数据结构是数组+链表+红黑树的实现方式。
经过前面的分析,已经能够得到结论HashMap1.8果然是基于数组+链表+红黑树的方式实现的。
这时候再看HashMap1.8源码的实现就会很清晰。
当put(key,value)的时候,肯定先要创建数组,然后基于数组的下标索引创建出链表或者红黑树。这里有一点要注意,上述HashMap的数据结构图解是最终的效果图,但是在没有put任何数据之前,这个数据结构是什么都没有的,也就是一片空白,是需要经过一个个Node节点创建然后形成起来的。
所以先要创建出数组。
在putVal方法中开始有这段代码。判断table是否为null,也就是Node[]数组是否为null,如果是,则需要先初始化数组的大小。初始化数组的大小是通过resize()方法,我们定位到resize()方法。
注意下面的代码截取的是resize()方法的间断性部分。
接下来就是根据这个默认数组的大小16初始化数组。
到这里,在上述的数据结构图解中,数组的部分就已经形成。但是里面还没有存元素,这个元素的类型前面我们已经分析过,应该是Node节点,换句话说也就是有key,value等这些组成的Node的对象。
Node节点要想存储到上面初始化好的数组中,关键是到底存到哪个位置呢?上述数组初始化的大小为16,也就是Node节点到底是落在0-15索引的哪个位置。有哥们可能会想要不就从下标0开始存储?虽然是可行,但是问题是什么时候存储到1的位置呢?什么时候存储到2的位置呢?似乎这个界限不明确,还是放弃这样的想法。那怎么确定?要不这样,使用Random对象随机出一个0-15之间的数值?好像可以,如果是这样,万一不小心随机的数值一直是1和2,那最终可能只有1和2位置以及下面有Node节点,其他位置没有得到充分的利用,如下图所示。
这时候,1和2所在位置索引下面的节点就会很多。一方面看起来不美观其他位置没有得到充分的利用,另外一方面每次想要查询尾端的节点的值时,要经历过的过程必须知道前一个节点是什么,此时时间和空间复杂度比较高,所以Random这种产生Node节点位置的方式不合理。
经过上述尝试后,不妨这样,具体的位置由Node节点中的key本身来决定,也就是根据key来得到这个落点值。
我们可以将这个过程分为两步,第一:根据key得到一个整型数;第二:控制这个整形数在0-15之间。
(1)根据key得到整型数hash
key.hashCode():因为在Object类中有一个hashCode()方法,是一个native的方法,可以得到一个int类型的整型数,正好符合我们的想法。
我们暂且用一个int hash=key.hashCode()记录这个整型数的值,后面会调整。
(2)控制整型数hash的范围在0-15之间
int index=hash%16,此时index的结果就是0-15之间,后面这块也会调整。
至少目前为止这个落点我们能够计算出来了,虽然后面还会优化这块内容,但是思路是没错的。
原来我们是通过hash%16这种方式,但是效率不够高,不妨一起来看下源码中是怎么做的。
可以发现,源码中是通过hash%(n-1)这种方式,而不是hash%n,注意这里的n是数组的大小,比如默认大小16。
先不管这样做的优势如何,也就是也得到0-15的数值,我们就将这个&计算通过二进制来折腾一下。
hash: 010100101010101010101010101 32位
n-1: 01111 15的二进制表示
index
这个index最终结果最小值为00000,最大值为01111,换算成10进制,也就是0-15,即和hash%n的结果是一样的。那为什么作者使用的是&运算而不是%运算呢?很简单,&这样的效率更高,速度更快,这也是面试中很重要的一个点,大家一定要注意。
原来hash的计算方式直接是key.hashCode(),得出的结果直接和n-1进行&运算,得到index之后,就可以确定Node节点的位置了,但是这样真的好吗?其实index的结果真正取决于hash值,因为n-1是01111。
所以hash的值,或者说是hash二进制表示最后的几位决定了index的值,我们希望的是index的值尽可能不一样,这样数组每个索引位置能尽可能得到充分的利用,雨露均沾嘛,不然index值重复的可能性太高的话,就会形成像原来Random设想的那种方案,一方面不美观,一方面影响时间和空间复杂度。
那么hash值的最后几位能否尽可能不一样呢?或者说源码中对hash的计算方式和我们原来认为的key.hashCode()是否一样呢?不妨一起来看下put方法调用的时候,有一个hash(key)函数,点开该方法代码如下。
我们会发现,它采用的是使用key.hashCode()的高16位和低16位异或的方式
0101010101001100100101010010101010原本是这样
01010101010011001
00101010010101010 ^
hash 这个hash结果采用的是key.hashCode()高低16位进行异或运算后的结果
也就是说这里让key.hashCode()的高16位和低16位都参与了运算,得到的hash值最后几位重复的可能性会大大降低,也就是hash(key)算法的设计。所以平时面试中问HashMap中hash算法的设计是怎样的,就是上面的这个过程。
同时到这里也解决了源码中Node类里面为什么有一个int hash的属性,其实这个属性就是保存的hash算法计算的结果值,这个值确定了,Node节点落点的位置就确定了,也就是按照面向对象的思想,Node节点最终落到哪个数组的位置它自己得知道。
这时候我们先不着急看put下面的代码,不妨来看一下对于数组默认大小属性的定义。
不难发现,这个DEFAULT_INITIAL_CAPACITY采用的是位移运算,也就是1向左位移4位,也就是1后面加上4个0,也就是10000,换算成10进制,就是2的4次方,即16。
有哥们可能会想为什么采用位移运算?因为速度快。对于DEFAULT_INITIAL_CAPACITY上面有注释,意思是必须是2的N次幂,也就是数组的大小必须是2的N次幂。
这时候来想想为什么呢?不妨再回到计算Node落点的(n-1)&hash。
hash: 010100101010101010101010101 32位
n-1: 01111 15的二进制表示
index
我们上面说过,index的值尽可能的不要重复,不然最终Node节点都集中在一两个索引位置之下了。为了尽可能不重复,hash算法进行了高低16位的异或计算。n-1的值是01111所以index的结果实际上取决于hash的值,试想一下如果n-1不是01111,如果是01110会怎样?这时候hash的二进制结果最后一位无论是1还是0,index重复的可能性就会增加,所以必须保证n-1的结果是01111,换句话说必须保证n是10000这样的形式,也就是n[数组的大小]必须是2的N次幂。
总有人会不按照规则出牌,这时候就需要看HashMap的构造函数。
继而来看tableSizeFor(initialCapacity)到底做了什么,我们会发现,这个方法会根据传入的cap得到一个2的N次幂的值作为数组的大小。
上面已经对数组进行了初始化,也得到了每个Node节点应该在的位置。
这时候比如真的来一个key和value,得到该Node节点应该在的位置,接下来的流程该是如何呢?肯定要判断原来数组该索引位置中是否有Node节点。若没有,则直接将该节点放到该位置;若有,有的话就再看咯。
我们先来看没有的时候,源码是怎么做的。
如果有的话,则不能直接放到该位置,如下图所示
这个时候可以分为三种情况:
如果key值相同,只需要将原来下标位置的value值替换掉即可;
如果key值不同,则将新的节点放到原来索引节点的后面形成单向链表;
如果key值不同,原来索引下面已经是红黑树的数据结构了,则按照红黑树的数据结构将新的节点存储。
代码实现
如果最终e的值不为空,则使用新value替换老的value
通过上述代码可以发现在链表put的过程中,如果链表太长会将其转成红黑树。我们先想想为什么要转?之前说如果index的结果一样,key值不同,会慢慢往Node节点往下延长形成链表的数据结构,但是对于链表而言,长度太长的话,存取效率会低,因为链表要想找到某个节点必须要知道它的上一个节点。
但是即使采用了hash算法,保证了数组的大小是2的N次幂,还是避免不了链表长度慢慢变长,这时候查询或者插入效率降低,怎么办呢?不妨这样将链表的结构变形成树形结构,如下图所示。
那转换的条件是什么呢?也就是链表到底多长才需要转呢?在源码中是怎样定义的?
也就是说链表节点长度超过8就需要转红黑树,如果红黑树中节点数目小于6就再转成链表。
而且之前在链表节点不断增加时候代码也是这样判断的。
到这边想必大家都有点累了,此时把自己的脑袋给放空,只回想一个图,就是一开始HashMap的数据结构图,一张由数组+链表+红黑树的图。通过上述的分析,我们发现数组的索引位置会被Node节点占用,而且index相同的情况下还会形成链表或者红黑树的结构。试想有没有这样一种情况,就是数组的索引位置不够用了,或者说虽然可以不断往下形成链表或者红黑树,但是数组的大小难道就一直保持在16不变吗?比如Node节点已经像下面这样的分布了呢?
大家应该能明白我想表达的意思,这时候你会发现数据结构比较复杂,也不利于我们的存取节点了。所以得要制定一个标准,比如整个数据结构中节点的数量超过某个值之后就把数组的大小扩大一下,这样可以有效减轻节点的一个分布压力。就像是链表太长要转红黑树一样。那这个数组扩大的临界值怎么确定呢?
在成员变量中有一个这样的值。
这个临界值的确定可以用数组大小*扩容因子,其实在数组初始化方法resize中我们见过这个公式。
这个newThr=16*0.75=12就是扩容的标准,在resize()方法中最终将这个赋值给了一个成员变量,赋值的过程如下所示,也就是说这个threshold等于12。
每当put一个Node节点成功,最后会有这段代码的判断。
也就是说会通过一个成员变量size,默认值为0,记录每次put的次数,如果这个++size>threshold[12],之后就进行resize()操作,也就是进行扩容。
到这里,我们能够知道resize()除了有初始化数组的功能,还会有扩容的功能,而且这个扩容会2倍扩容,原因是要保证数组的大小必须是2的N次幂,原因前面已经说过咯。
判断数组的大小,此时数组大小是16。
此时oldCap的大小则大于0。
当新数组的大小变成32之后,就将新数组的大小创建成功。
此时会面临一个问题,新创建数组中没有任何Node节点,我们需要将原来数组中的Node节点”搬运“到新的数组中,那怎么搬运呢?
搬运要按照节点的类型来区分,我们可以采取这样的方式,先循环遍历原来数组的索引位置,要确保原来数组下表位置不为空才有必要进行搬运。
这时候可以分为3种情况。
(1)数组索引下标下面没有元素
这时候只需要使用hash值重新&上新数组n-1的值,计算节点在新数组中的位置即可。
(2)数组索引下标下面有元素,且元素类型为红黑树
如果索引下面的节点类型是红黑树,则按照红黑树的方式将Node节点切分,然后移动到新的数组中。
(3)数组索引下标下面有所愿,且元素类型为链表
这种情况表示原数组索引下面的节点类型为链表,此时要循环遍历链表,使用的是do-while循环。
下面这段代码最重要的其实是这句话
if ((e.hash & oldCap) == 0),也就是这块会计算链表中每个节点的hash&oldCap的值,最终结果和0比较,根据结果进行不同的处理,那么这种结果什么时候为0,什么时候不为0呢?
我们把if ((e.hash & oldCap) == 0)这个计算公式写出来
hash: 010010101001100101010101101010
oldCap: 10000 &
result
上述result到底何时才为0?细心的哥们会发现只有hash的倒数第5位位0的时候,结果才会0,否则结果不为0,而且为0的时候result和不为0的result相差是10000,也就是16,也就是oldCap的大小。
本来要移动老数组中链表的Node节点要重新计算hash&(n-1)的值,但是此时只要知道hash的倒数第5位是否为0,就能知道本来应该计算的结果。
如果hash二进制表示倒数第5位为0,即使采用hash&(n-1)的结果还是和原来index一样。
如果hash二进制表示倒数第5位为1,那么采用hash&(n-1)的结果就会比原来index大oldCap的大小。
其实上述两段代码形成的最终结果是,也就是节点在新的数组中的位置要么是在原来位置,要么是在原来位置+oldCap的位置。
通过本文分析,我们了解了HashMap1.8的数据结构以及源码原理源码实现,包括hash算法,put过程,加载因子,扩容等,希望对大家有所帮助。
OK,关于jdk1.5,jdk1.5升级1.7后中文乱码和的内容到此结束了,希望对大家有所帮助。
1,php开源商城系统更方便PHP程序快速开发,运行速度快,技术本身可以快速学习。嵌入HTML:因为PHP可以嵌入HTML语言,所以它与其他语言相关。编辑简单,
360CloudDisk具有自己独特的优势,例如快速下载速度,丰富的资源,大存储空间等。尽管服务器具有丰富的资源,并且许多文件可以在几秒钟内传输,但自2014年
JDK1.8的StreamAPI提供了一组固定的中间操作,开发者若需实现自定义逻辑,往往需要使用状态化的lambda表达式或外部库,这通常导致代码复杂且难以维护
Android系统要求将安装在系统中的每个应用程序均由数字证书签名,并且数字证书的私钥存储在程序开发人员的手中。Android系统使用数字证书在应用程序的作者和
在处理器方面,诺基亚820使用1.5GHz双核QualcommSnapdragonS4处理器,内置的1GB内存和8GB的身体存储空间,并支持MICROSD扩展。
首先,我们需要确保笔记本电脑内置了无线网卡。如果没有的话,我们需要购买一个无线网卡并安装好相应的驱动程序。然后,我们需要在笔记本电脑中打开无线网络开关。不同品牌
用户评论
我最近也遇到过类似问题!升级到JDK 1.7 后,本来好好的中文突然就乱码了,真是让人头疼。我试着翻阅了一些资料,好像说是编码格式不匹配引起的? 不知道你是怎么解决的呢?
有11位网友表示赞同!
这篇文章太及时了! 我现在 justement 在使用 JDK 1.5 开发,但最近项目需要用到一些新功能,迫切想要升级到 JDK 1.7。但是看到乱码问题,真的有点犹豫。
有18位网友表示赞同!
中文乱码问题可不是小事,简直能让人抓狂… 要不是偶尔遇到这个问题还好,可要是频繁出现,工作效率就无法保证了。 希望这种常见问题能得到及时解决吧!
有14位网友表示赞同!
我也碰到过这种情况啊!当时是使用了不同的编码格式导致的中文乱码。建议在更改 JDK 版本时注意检查项目中文件的编码规范,确保前后一致即可避免此类问题。
有14位网友表示赞同!
升级 JDK 1.5 到 1.7 需要仔细查看官方文档和解决方案,否则容易出现各种问题,例如中文乱码、兼容性问题等等。
有14位网友表示赞同!
我的方案是使用 IDEA 进行配置, 找到 project settings,设置一下 file encoding 为 UTF-8, 然后重启项目,再将系统字体设置为支持中文的字体,就能解决这个问题了!
有18位网友表示赞同!
这个乱码问题确实让人头疼! 我曾经折腾了好久,终于找到了解决方法 – 重新配置字符集和文件编码。
有6位网友表示赞同!
升级 JDK memang kadang-kadang sedikit merepotkan. Tapi, kalau kita pahami betul langkah-langkahnya dan teliti dalam prosesnya, seharusnya tidak ada masalah besar terjadi.
有17位网友表示赞同!
我以前就遇到过这种问题,后来了解到是系统字体的问题,需要选择支持中文字体的系统字体才能解决乱码问题。
有6位网友表示赞同!
升级 JDK 时要注意编码格式之间的转换,确保项目文件与数据库的字符集一致。否则可能会导致中文乱码的出现。
有11位网友表示赞同!
中文乱码问题确实很常见,升级 JDK 版本的时候需要注意! 有些老项目可能需要一些额外的兼容处理才能完美运行!
有20位网友表示赞同!
遇到这种情况建议先检查一下你的系统编码格式,可能是由于当前系统的编码与项目的编码不一致导致中文乱码。
有13位网友表示赞同!
这篇文章写的太简单了,对于我这种菜鸟来说根本看不懂! 希望能有更详细的解说和图示帮助!
有11位网友表示赞同!
升级 JDK 版本需要注意一些细节,例如字符集、数据库连接等方面,建议仔细阅读官方文档进行配置调整。
有10位网友表示赞同!
我也遇到过这个问题,后来发现是代码中某个地方使用错误的编码导致的乱码,最终找到了问题并修复了!
有18位网友表示赞同!
这个 JDK 版本升级确实很麻烦 ! 感觉每次都要经历各种各样的问题和 Bug… 希望以后开发的时候可以更加稳定!
有18位网友表示赞同!
建议在升级 JDK 之前,先做好备份工作,再进行测试,以防万一遇到意外情况无法恢复!
有14位网友表示赞同!
中文乱码这个问题确实令人头疼。有时候简单调整一下系统编码格式就能解决,但有时还需要根据项目具体情况进行调整。
有7位网友表示赞同!
希望 JDK 的未来版本能更加稳定可靠,减少一些升级过程中遇到的问题,让开发更便捷!
有20位网友表示赞同!