jvm深入研究文档--java中的堆--详解!--jvm底层探索(1)

news2024/11/29 2:52:19

阿丹:

JVM的内存分区包括以下几个部分:

  1. 堆区(Heap) - 这是JVM的主要部分,用于存储实例对象和大多数Java对象,如数组和用户定义的类。
  2. 方法区(Method Area) - 这是线程私有的,用于存放类对象(加载好的类)。
  3. 栈区(Stack) - 这是线程私有的,包括虚拟机栈和本地方法栈。虚拟机栈用于保存调用关系的内存空间,而本地方法栈用于存放本地方法之间的调用关系(本地方法指的是JVM内部使用C++写的代码)。
  4. 程序计数器(Program Counter Register)- 这是线程私有的,放的是下一个要执行的指令的地址。

以下是学习JVM底层的大纲路径:

  1. 首先了解Java程序的执行过程,以及JVM的工作原理。
  2. 深入了解JVM的内存划分和内存管理,包括堆、栈和方法区的概念和作用。
  3. 掌握Java对象的创建和销毁过程,以及对象的内存布局和访问方式。
  4. 了解JVM的垃圾回收机制和内存分配机制,包括引用计数法、可达性分析法等垃圾回收算法。
  5. 掌握JVM的线程管理、线程同步和锁机制。
  6. 学习JVM的类加载机制和字节码解析,以及JVM的内部结构和工作原理。
  7. 掌握一些常用的JVM参数和监控工具,以便更好地管理和调优JVM。

以上是学习JVM底层的基础大纲路径,需要掌握的基础知识比较多,但只有系统地学习这些知识,才能更好地理解和应用Java语言和JVM。

在学习前一定要管理好自己的学习大纲来有计划系统化的学习以及形成足够完善的知识网络。

统一知识预先知晓:

运行时数据区分为了两个部分!

线程私有区:也就是每一个单个线程独有的只有自己可见的

其中的本地方法栈外连接了执行引擎-》本地的方法接口以及本地的方法库

线程共享区:可以理解为所有的线程操作的一个对象

堆!是什么?

堆是Java内存管理的主要区域,也是垃圾回收的主要区域。它用于存储所有对象实例和大多数Java对象,如数组和用户定义的类。

具体来说,Java堆是JVM中最大的一块内存区域,被划分为多个部分,包括新生代和老年代。新生代用于存储新创建的对象,而老年代则用于存储长时间存活的对象。每个对象在堆内存中都有一个独立的内存空间,包括对象头、实例变量和对象引用的变量。

在Java堆中,所有的对象实例和大多数Java对象都被分配内存空间。这些对象的内存空间通常是连续的,可以通过对象的引用直接访问其内存空间。此外,Java堆也存放了一些特殊的数据结构,如Java堆异常和Java本地方法栈。

Java堆的垃圾回收机制是Java内存管理的重要部分,它用于自动释放不再使用的对象占用的内存。Java垃圾回收器会自动检测堆中的对象,当一个对象不再被引用或不再被使用时,垃圾回收器会自动将其回收,并释放其占用的内存空间。

总之,Java堆是JVM中最大的一块内存区域,用于存储所有对象实例和大多数Java对象,并且是垃圾回收的主要区域。

堆!干什么?解决了什么?

堆是用于存储对象实例和大多数Java对象的地方,它是Java内存管理的主要区域。

堆内存用于存储由new创建的对象和数组,它只负责存储对象信息。每个对象在堆内存中都有一个独立的内存空间,包括对象头、实例变量和对象引用的变量。

通过垃圾回收机制,堆解决了程序运行中如何处理不再使用的对象占用的内存问题,从而自动释放内存空间,避免了内存泄漏和溢出的问题。

此外,Java中的线程每个都会有一个相应的线程栈与之对应,这保证了程序的并发运行。而堆则是所有线程共享的,也可以理解为多个线程访问同一个对象,比如多线程去读写同一个对象的值。

总之,堆是Java内存管理和垃圾回收的重要区域,它解决了如何存储和管理对象实例和Java对象,以及如何处理不再使用的对象占用的内存问题。

堆!如何实现的?

它通过JVM的内存管理和垃圾回收机制来实现。

在JVM中,堆是所有线程共享的内存区域,用于存储所有的对象实例和大多数Java对象。堆内存被划分为多个部分,包括新生代和老年代。新生代用于存储新创建的对象,而老年代则用于存储长时间存活的对象。

每个对象在堆内存中都有一个独立的内存空间,包括对象头、实例变量和对象引用的变量。在Java堆中,所有的对象实例和大多数Java对象都被分配内存空间。这些对象的内存空间通常是连续的,可以通过对象的引用直接访问其内存空间。

Java堆的垃圾回收机制是Java内存管理的重要部分,它用于自动释放不再使用的对象占用的内存。Java垃圾回收器会自动检测堆中的对象,当一个对象不再被引用或不再被使用时,垃圾回收器会自动将其回收,并释放其占用的内存空间。

垃圾回收机制的实现通常涉及到两个阶段:标记和清除。在标记阶段,垃圾回收器会遍历所有对象,找出所有引用的对象,并将其标记为活动的;在清除阶段,垃圾回收器会清理未被标记的对象占用的内存空间。

总之,堆是通过JVM的内存管理和垃圾回收机制来实现的。每个对象在堆内存中都有一个独立的内存空间,并且通过垃圾回收机制自动释放不再使用的对象占用的内存空间。

堆!什么工作原理?底层是什么?

在JVM中,堆是Java内存管理的主要区域,也是垃圾回收的主要区域。它用于存储所有对象实例和大多数Java对象,如数组和用户定义的类。

堆内存的工作原理如下:

  1. 每个对象在堆内存中都有一个独立的内存空间,包括对象头、实例变量和对象引用的变量。对象头包含该对象的类型信息、哈希码、GC分代年龄,以及指向类元数据的指针,实例变量则是对象真正存储的数据。
  2. 堆内存是所有线程共享的内存区域,线程在创建对象时,会直接在堆内存中分配相应的内存空间来存储该对象。
  3. Java堆是可扩展的,当需要更多的内存空间时,Java运行时会在运行时动态地扩展堆的大小。
  4. Java堆通过垃圾回收机制自动管理内存,当一个对象不再被引用或不再被使用时,垃圾回收器会自动将其回收,并释放其占用的内存空间。垃圾回收机制的实现通常涉及到两个阶段:标记和清除。在标记阶段,垃圾回收器会遍历所有对象,找出所有引用的对象,并将其标记为活动的;在清除阶段,垃圾回收器会清理未被标记的对象占用的内存空间。

Java堆的底层实现是通过数组来实现的。每个对象在堆内存中都有一个独立的内存空间,并且可以通过数组来访问该对象。同时,Java堆也通过数组来存储和管理内存空间,例如在进行垃圾回收时,垃圾回收器会通过数组来记录和管理内存空间的状态。此外,Java堆还支持内存的动态扩展和收缩,例如在需要更多的内存空间时,Java堆会自动扩展数组的大小,以提供更多的可用内存空间;当Java堆中的空闲内存过多时,Java堆会自动收缩数组的大小,以释放空闲的内存空间。

综上所述,Java堆通过数组来实现内存的管理和动态扩展与收缩。它是一个可扩展的内存区域,用于存储所有的对象实例和大多数Java对象,并且通过垃圾回收机制自动管理内存。

堆!堆的组成?

JVM的堆内存是Java内存管理的主要区域,用于存储所有对象实例和大多数Java对象。堆内存的组成可以细分为以下部分:

  1. 新生代:新生代是类的诞生、成长、消亡的区域,用于存放新创建的对象。它包括三个部分:Eden区、From Survivor区和To Survivor区。
  2. 老年代:老年代主要存放生命周期长的存活对象。
  3. 永久代(JDK1.8以前的版本):永久代在逻辑上虽然在堆内存空间,但在物理上与堆是独立的。
  4. 元空间(JDK1.8以后的版本):元空间是方法区的一个实现,是堆的一个逻辑部分。参照下面的说明1

需要注意的是,从JDK1.8开始,永久代被元空间替代,元空间从JVM的堆内存中移动到系统的本地内存。此外,堆内存还可以细分为其他部分,如S0和S1区,这两个区域在JDK的自带工具输出中可以看到。总之,JVM的堆内存由多个部分组成,主要用于存储和管理Java对象实例。

从Java 1.8开始,JVM中的方法区被实现为元空间,而不再是之前的永久代。元空间位于堆外内存,不与堆内存混用。元空间使用的是本地内存,其最大内存大小由系统内存决定,而不是由堆的大小决定。方法区是JVM的规范,而元空间则是JVM规范的一种实现。虽然元空间不再是方法区,但是它实现了方法区的功能,加载class二进制流到左侧的jvm堆外区域,因此可以说元空间是方法区的等价实现。

说明1:

        元空间并不属于堆内存。在JVM的内存结构中,堆和元空间是两个独立的区域。

堆是Java内存管理的主要区域,用于存储所有对象实例和大多数Java对象,它是垃圾回收的主要区域。而元空间则是方法区的实现,它存储的是类的元数据,如类名、成员变量、方法等信息。元空间并不属于堆内存,而是使用本地内存,它的最大内存大小由系统内存决定。

因此,虽然元空间实现了方法区的功能,并且与堆内存有一定的关系,但是它并不属于堆内存的一部分。

我们来聊一聊关于堆的问题

堆内存被分为新生代和老年代,新生代可以被分为伊甸园去和两个幸存者区。

基于分代我们可以将垃圾回收的GC动作分为两个部分:

1、面向新生代的minor gc(young gc)

2、面向老年代的major gc(old GC)

3、一起垃圾回收就是full GC

新生代的GC的作用就是判断是Eden里面的数据是不是垃圾,如果不是垃圾的话就将这些数据放在幸存者区。如果是的话就清理掉。

那在minor gc判定你一次不是垃圾的时候,给你放到幸存者区。幸存者区有两个,在之后的每次GC的时候就判断一下是不是垃圾。同时年龄+1,当年龄到15岁的时候就可以成为老年代的一员了!

但是!

有几个直接进入老年代的情况:

1、大对象-》担保机制

担保转移动老年代的机制主要与 Survivor 区和 Full GC(全局垃圾收集)相关。下面是详细说明:

  • 在 JVM 启动时,Eden 区和两个 Survivor 区(S0 和 S1)会被初始化。当 Eden 区满了,或者存活的对象年龄超过一定阈值(默认为 15),就会触发一次Minor GC。
  • 在 Minor GC 过程中,活动(存活)的对象将被移动到 Survivor 区(S0 或 S1)。如果 Survivor 区也满了,那么存活的对象将被移动到另一个 Survivor 区或者老年代。
  • 如果 Survivor 区没有足够空间存放所有的活动对象,那么 JVM 会触发 Full GC。如果 Full GC 仍然无法回收足够的空间,那么 JVM 会抛出 OutOfMemoryError 错误。
  • 如果对象在 Survivor 区中经历了多次 GC 仍然存活,或者被显式地 pin(例如,被 final 修饰的对象的构造函数调用后),那么该对象将被移动到老年代。
  • 当老年代满了,或者老年代中的对象数量增加到一定阈值(默认为 8000),或者系统内存空间不足,无法分配足够内存给对象分配空间时,会触发 Full GC。
  • Full GC 会清理整个 JVM 的堆内存,包括新生代和老年代。所以 Full GC 的代价相对较大,应尽量避免频繁触发。

注意:以上的阈值和频率取决于具体的 JVM 设置和系统环境,可能会因 JVM 的不同版本和配置有所不同。

2、动态对象年龄判断

在幸存者区下面的某一个年龄以及一下的对象已经占据了整个幸存者区的一半以上,就将大于等于这个年龄的对象全部放到老年代。

老年代的GC就是判断老年代中的对象或者数据是不是垃圾,如果是的话就清理掉。 

堆中的GC!!

标记清除

标记清除-老年代使用的cms用的就是这个

标记清除就是先对垃圾进行标记在进行清除 ,但是如图所示可以看到在内存中留下了很多空洞。

 cms将数据清除整合了四个步骤

补充:GC判断垃圾的方式--可达性分析算法:

从GC-root开始用一条链路来将整个对象的引用图遍历出来,判断出有没有没有被引用的孤岛,那么就视为垃圾

如图所示的E就被判断成为了垃圾 

1、初始标记

在我们去寻找最开始的gc-root的时候就需要去stw,但是这个停顿的时间是非常快的,所以对于用户和程序的感知不大,gc-root的由来就是应用的开始。

三色标记法

一个节点被标记状态为:白-灰-黑

要注意因为我画图没办法并发画的问题在前一个节点变成黑色的同时下一个节点就已经变成黑色了。

白色:代表没有经过标记

灰色:正在标记

黑色:标记完成

出现问题:浮动垃圾、对象消失

2、并发标记

如果是单线程那自然不会出现很多问题,但是实际情况就是在GC标记的过程中用户线程对于引用的关系是随时改变的,这就出现了下面的两种问题!

浮动垃圾-出现场景

因为在标记是按照指针有序的进行的,在进行到一个节点的时候这个时候该节点突然释放掉了一个引用节点的标记引用,这个时候就会造成浮动垃圾

所以我们一会要研究一下jvm是如何解决的,其实这种情况问题不大,因为下一轮可达性算法的时候这个C就会被带走。

对象消失-出现场景:

 这个是因为对象的引用关系进行更改变动导致的,会造成在系统中所引用的对象突然消失。

过程描述:

        在可达性算法进行的时候在扫描到B对象的时候B对象突然释放掉了对于C对象的引用,同时A对象对C对象进行了引用,因为可达性算法是不可逆的,所以在接下来C就不会再被标记。就在GC中被删除掉了,对于整个程序来说C就突然消失了。这个情况是很恐怖的。

3、重新标记

主要解决上面的对象消失问题:

根据动图演示发现,首先是B要对C进行释放,然后A再和C进行连接,那么我只需要破坏掉中间的一个条件就可以避免这个事情的发生。

解决方式:
        1、增量更新

 使用类似于aop的概念完成了一次写隔离

我们对这个行为做一个记录,然后根据A作为根节点在进行一次扫描。注意在这个时候,因为如果其他的对象的引用再去改变,那不就扫描不完了?所以在这个是需要时间暂停也就是stw(世界停止)。

        2、原始快照

 在我开始遍历的时候就给整个对象引用图来一个快照,类似于mysql中的可重复读(快照读),

不管用户线程如何变化,在遍历的时候还是按照我保存的快照去走。

就算按照我们的场景B释放了对C的引用但是我还是给你标记下来。我给你B的删除动作无视掉掉了,就类似于mysql中的幻读。保证了我每次在遍历可达性的引用表的时候,我的这个表的稳定性。

同时因为保存了快照这个时候也是要时间停止的。STW(stop the world)!!

4、并发删除

并发的去删除掉没有被可达性算法达到的对象

标记复制

就是将垃圾放在一侧统一删掉,优点可见,但是肉眼可见的少了一半的内存空间。 

标记整理

就是将内存先去标记然后移动整理,最后在进行垃圾的删除,是前两个的优化,解决了前两个的问题,但是出现了更多了逻辑复杂度以及涉及到位置的移动所以复杂度更高。 

 

在jvm中的STW问题:世界停止

在 JVM 中,STW(Stop-The-World)是一个重要的概念,它指的是在垃圾回收(GC)期间,所有应用程序线程被暂停(Stopped)的阶段。STW 期间,应用程序线程无法继续执行,GC 操作可以顺利进行,但会带来一定的性能开销。

STW 问题主要出现在 CMS(Concurrent Mark Sweep)垃圾回收器中,它是一种并发的标记清除垃圾回收算法,主要适用于老年代(Old Generation)的垃圾回收。在 CMS 垃圾回收过程中,主要分为四个阶段:

  1. 初始标记(Initial Mark):这个阶段需要 STW,目的是标记出整个堆中所有活跃对象。
  2. 并发标记(Concurrent Mark):这个阶段是并发的,垃圾回收器线程和应用程序线程同时运行,垃圾回收器线程对堆中的对象进行标记,应用程序线程可以继续执行。
  3. 重新标记(Remark):这个阶段也需要 STW,目的是修正并发标记期间由于应用程序线程的动态变化而导致的标记错误。
  4. 清理(Sweep):这个阶段是并发的,垃圾回收器线程和应用程序线程同时运行,垃圾回收器线程回收不再使用的内存空间,应用程序线程可以继续执行。

由于初始标记和重新标记阶段需要 STW,因此在 CMS 垃圾回收过程中,应用程序线程会不可避免地出现停顿。为了减少 STW 带来的影响,可以采用以下几种方法:

  1. 使用 CMS 垃圾回收器的参数设置并发比例(-XX:+UseConcMarkSweepGC 和 -XX:CMSInitiatingOccupancyFraction 等参数),尽量减少并发标记和重新标记阶段的 STW 时间。
  2. 使用 G1(Garbage-First)垃圾回收器,它是一种面向对象分配策略的垃圾回收器,通过并行、并发和延迟清理的方式尽可能地减少 STW 时间。
  3. 采用 JIT 编译优化技术,将热点代码编译成本地代码,减少 STW 期间应用程序线程的停顿时间。
  4. 尽量避免分配大量短期对象,以减少垃圾回收的频率和 STW 的时间。
  5. 采用分布式架构或容器化技术,将应用程序部署在多个节点上,利用负载均衡等技术分担 STW 的压力。

总之,STW 是垃圾回收过程中不可避免的一个环节,但可以通过一些方法来降低它对应用程序性能的影响。

1、为什么堆里要分新生代和老年代?

分代假说:

1、绝大多数对象都是朝生夕灭的

2、熬过越多次垃圾收集过程的对象就越难以消亡

分区是为了优化GC!!! 

2、GC线程是如何和用户线程并行的?

cms使用了四层步骤

根据上面文章的讲述可以知道

1、初始标记  2、并发标记  3、重新标记  3、并发删除

通过这四步来完成的!!!

3、GC-root是如何枚举的?

普通对象指针(Ordinary Object Pointer)维护GC -root

         意思就是说,我们不在进行每次需要信息的是再去重新查询了,而是这里使用了预加载追加存放map的方式来管理,需要的时候直接返回输出整个map。进行追加的计算。预计算这个设计的思想在大数据中还是很常见的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1028277.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

内网穿透的应用-NAS私有云存储 - 搭建Nextcloud私有云盘并公网远程访问

文章目录 摘要1. 环境搭建2. 测试局域网访问3. 内网穿透3.1 ubuntu本地安装cpolar3.2 创建隧道3.3 测试公网访问 4 配置固定http公网地址4.1 保留一个二级子域名4.1 配置固定二级子域名4.3 测试访问公网固定二级子域名 摘要 Nextcloud,它是ownCloud的一个分支,是一个文件共享服…

利用fiddler正向代理前端请求到本地后端

前景:在实际开发测试环境中,(前后端均已上线到测试服务器或前端以上线而后端还在开发中)。在测试过程中(前端页面点击,功能测试)发现了bug或异常点。 正常排查问题可能是先利用浏览器检查工具查看接口的返回…

Unity Bolt UGUI事件注册方式总结

Bolt插件提供了丰富的事件注册方式,开发者几乎不用编写任何代码就可以完成事件的注册,进行交互。下面是我使用UI事件注册的相关总结。 1、通过UI控件自身拖拽实现事件的注册。 Button的事件注册: 新建一个UnityEvent事件, Butt…

PASCAL VOC2012数据集详细介绍

PASCAL VOC2012数据集详细介绍 0、数据集介绍2、Pascal VOC数据集目标类别3、 数据集下载与目录结构4、目标检测任务5、语义分割任务6、实例分割任务7、类别索引与名称对应关系 0、数据集介绍 2、Pascal VOC数据集目标类别 在Pascal VOC数据集中主要包含20个目标类别&#xff…

uniapp开发h5 调用微信sdk 全网最全指南!!!! 血泪史!!!

目录 场景: 技术栈: 遇到的问题先抛出来: 1.通过后端同学获取调用微信sdk所需的签名过程中,遇到的跨域问题 2.使用微信sdk前提必须是微信容器,换句话说就是微信浏览器打开,才能使用微信sdk 3.如何在开…

方案:浅析利用AI智能识别与视频监控技术打造智慧水产养殖监管系统

一、方案背景 针对目前水产养殖集约、高产、高效、生态、安全的发展需求,基于智能传感、智慧物联网、人工智能、视频监控等技术打造智慧水产系统,成为当前行业的发展趋势。传统的人工观察水产养殖方式较为单一,难以及时发现人员非法入侵、偷…

Windows系统如何部署Wing FTP Server与公网远程访问【内网穿透】

Wing FTP Server安装配置结合内网穿透实现公网访问本地站点 文章目录 Wing FTP Server安装配置结合内网穿透实现公网访问本地站点前言1.Wing FTP Server下载安装2.Wing FTP Server配置部署3.安装cpolar内网穿透3.1 注册账号3.2 下载cpolar客户端3.3 登录cpolar web ui管理界面3…

ChatGLM 实现一个BERT

前言 本文包含大量源码和讲解,通过段落和横线分割了各个模块,同时网站配备了侧边栏,帮助大家在各个小节中快速跳转,希望大家阅读完能对BERT有深刻的了解。同时建议通过pycharm、vscode等工具对bert源码进行单步调试,调试到对应的模块再对比看本章节的讲解。 涉及到的jupyt…

【多目标跟踪】 TrackFormer 耗时三天 单句翻译!!!

【多目标跟踪】 TrackFormer 耗时三天 单句翻译!!! TrackFormer: Multi-Object Tracking with Transformers Abstract The challenging task of multi-object tracking (MOT) re-quires simultaneous reasoning about track initiali…

纽禄美卡Neuromeka亮相美国FABTECH,展示用于焊接的3D视觉协作机器人

原创 | 文 BFT机器人 纽禄美卡Neuromeka公司在由美国精密成型协会、美国焊接协会、化工涂料协会等5大协会举办的美国金属加工及焊接展览会FABTECH上精彩亮相。这家总部位于韩国首尔的公司成立于2013年,是机器人解决方案领域的领先供应商,致力于提高各种…

通过 chatgpt 协助完成网站数据破解

Chatgpt 的出现极大地提升了程序员的工作效率,常见的使用场景包括代码自动生成、代码静态检查等,那么 chatgpt 能否用于某些网站的数据破解工作呢? 问题 某天线上服务开始报警,原来是某个视频网站无法获取到其 cdn 地址导致的下…

【AD】【规则设置】设置四层板

设置四层板 一般 4层板,都会把 地 和 VCC放在内层。1、使用快捷键D-K 进入层叠管理器,添加负片层添加完后,修改层名,方便辨识修改格式:属性层号 2、进入相应layer 设置网络设置GND层设置VCC层特点:在层内可…

【HackTheBox Topology】打靶记录

一、信息收集 1、nmap 扫描发现22 80 端口 2、访问80端口 找到两个域名 topology.htb latex.topology.htb 3、子域扫描发现如下两个域名 dev.topology.htb stats.topology.htb C:\root> gobuster vhost -u http://topology.htb --append-domain -w /usr/share/seclists…

【Android Framework系列】第15章 Fragment+ViewPager与Viewpager2相关原理

1 前言 上一章节【Android Framework系列】第14章 Fragment核心原理(AndroidX版本)我们学习了Fragment的核心原理,本章节学习常用的FragmentViewPager以及FragmentViewPager2的相关使用和一些基本的源码分析。 2 FragmentViewPager 我们常用的两个Page…

【基本数据结构 四】线性数据结构:队列

学习了栈后,再来看看第四种线性表结构,也就是队列,队列和栈一样也是一种受限的线性表结构,和栈后进先出的操作方式不同的是,队列是FIFO的结构,也就是先进先出的操作方式。 队列的定义 队列这个概念非常好理解。可以把它想象成排队买票,先来的先买,后来的人只能站末尾…

iTOP-RK3568开发板Linux 修改kernel logo

本文档配套资料在网盘资料“iTOP-3568 开发板\02_【iTOP-RK3568 开发板】开发资料\10_Linux 系统开发配套资料\05_Linux 修改内核 logo 配套资料”路径下。 5.3.1 准备 logo 系统默认内核 logo,如下图所示: 如 果 想 要 替 换 这 个 logo, 首 先 要 制…

SAP服务器文件管理

SAP服务器文件管理 文件说明:对于SAP服务器的文件管理,系统给出3个事物码,分别是显示目录的AL11,下载文件的 CG3Y和上传文件的CG3Z。 AL11显示目录:以查找系统参数文件为例,在前台执行事物码AL11进入,如图…

【面试经典150 | 数组】多数元素

文章目录 写在前面Tag题目来源题目解读解题思路方法一:哈希表方法二:排序方法三:摩尔投票法 写在最后 写在前面 本专栏专注于分析与讲解【面试经典150】算法,两到三天更新一篇文章,欢迎催更…… 专栏内容以分析题目为主…

HelpLook全新升级!定制AI问答机器人,企业内容中心焕新

一直以来,企业都在努力解决内外部“企业知识管理”问题:从纸质手册发放,转线上电子文档传阅(pdf/ppt/word等),再到整理客户常见问题(FAQ)和内部知识库(wiki),但始终没有找到一套完整方案将“企业知识”很好地集中管理及…

不得不爱的AI艺术写真头像二维码生成小程序开发

最近什么最火?AI最火! AI里什么最火?艺术写真生成和二维码美化最火。 一款小程序集合了高还原度的AI写真艺术照和二维码美化,你们说香还是不香? 并且加入了输入心愿就能生成独一无二的个性头像功能,直接…