当前位置: 首页 > news >正文

Java ~ Reference【总结】

一 概述


 简介

    在JDK1.2之前,Java中引用的定义是十分传统的:如果引用类型的变量中存储的数值代表的是另一块内存的起始地址,就称这块内存代表着一个引用。在这种定义之下,一个对象只有被引用和没有被引用两种状态。

    实际上,我们还希望存在这样的一类对象:当内存空间还足够的时候,这些对象能够保留在内存空间中;如果当内存空间在进行了垃圾收集之后还是非常紧张,则可以抛弃这些对象。基于这种特性,可以满足很多系统缓存功能的使用场景。

    从JDK1.2起,Java对引用概念进行了扩充,将引用分为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)及终引用(Final Reference)。其中强引用就是JDK1.2之前的引用,日常代码中绝大多数引用都是强引用。而其它几种引用则是JDK1.2中引入的,这些引用有着各自的特性及作用,例如软引用就代表了上述需求的“将对象在内存空闲时保留,不足时舍弃”的功能。这些引用概念存在着相应的类实现,Java通过Java类与底层机制相结合的方式实现了这些概念,而重点是这些类又全都继承自同一个父类,也是本文真正的核心 —— Referecne(引用)抽象类(下文简称引用)。

    引用抽象类是强引用之外引用(下文简称特殊引用)概念的实质化产物,其作用在于定义并实现了特殊引用的生命周期及运行流程,使得特殊引用不再是一个虚幻的设想,而是实际独立于强/无引用之外的第三存在,为软、弱等具体特殊引用实现提供了实质性基础,该知识点会在下文详述。

 特殊引用实现思想

    事实上,作为API层面上的实现,单纯依靠引用抽象类是无法实现特殊引用的,需要JVM层面(GC)的配合。因此,与其说引用抽象类实现了特殊引用,倒不如说其为GC分辨特殊引用提供了判断依据更加合适,具体如下图所示:

    由上图可知,通过在对象的强引用关系之间插入引用的方式,GC可以判断得出对象A/B之间存在特殊引用关系,由此就可以避开强引用的执行逻辑转而进行对特殊引用对象的特殊处理,从而实现不同于强引用的奇妙回收效果,例如“在剩余内存充裕时保留,紧张时回收”及“对象实际可达但依然被回收”等。

二 使用


 初始化

    引用抽象类由于是抽象类,因此构造方法自然是无法直接执行的,只能在子类的构造方法中调用。

  • Reference(T referent) —— 初始化指定所指对象但未注册ReferenceQueue(引用队列)类对象(下文简称引用队列)的引用。

  • Reference(T referent, ReferenceQueue<? super T> queue) —— 初始化指定所指对象且注册引用队列的引用。

 方法

  • public T get() —— 获取 —— 获取当前引用的所指对象,当所指对象不存在时返回null。所指对象初始是必然存在的,但可以在后期被清除。

  • public void clear() —— 清除 —— 清除当前引用的所指对象(即断开两者的引用关系),并不会将当前引用加入到注册引用队列中。该方法专为开发者提供,GC线程不会调用该方法断开引用与所指对象的关联。

  • public boolean isEnqueued() —— 是否入队 —— 判断当前引用是否已加入注册引用队列,是则返回true;否则返回false。引用加入注册引用队列时会将注册引用队列替换为“入队”引用队列,这是一个在引用队列类内部创建的全局静态引用队列,被作为引用加入注册引用队列的标志位来使用。因此判断当前引用是否加入注册引用队列无需遍历注册引用队列,直接判断注册引用队列是否是“入队”引用队列即可。

  • public boolean enqueue() —— 入队 —— 将当前引用加入注册引用队列中,成功返回true;否则返回false。该方法底层调用引用队列类的enqueue(Reference<? extends T> r) 方法实现。该方法专为开发者提供,“引用处理器”线程不会调用该方法将当前引用加入注册引用队列。

三 实现


 所指对象

    所谓的所指对象即是被引用所持有的对象,每个引用在创建时必须且只能持有一个对象,这个对象将因为引用的原因被GC认为存在特殊引用,从而受到特殊处理。根据引用类型的不同(软、弱及虚等),其收到的特殊处理也不尽相同。

 引用队列

    引用在创建时可以设置/注册引用队列,当引用的所指对象被GC回收时,引用会被安排加入注册引用队列中。因此可知,注册引用队列的实际作用是追踪所指对象的GC状态,通过判断引用是否处于注册引用队列中的方式开发者可以知道其所指对象是否已被GC回收,并以此为契机来执行某些自定义操作,例如回收资源等。

    当然,引用队列的注册并非是强制的,引用抽象类提供了构造方法Reference(T referent)来创建不注册引用队列的引用。此时,引用中用于持有注册引用队列的queue(队列)字段会被默认赋值为“空”引用队列。“空”引用队列与“入队”引用队列一样,是在引用队列类内部创建的全局静态引用队列,被作为“引用未注册引用队列”及“引用退出注册引用队列”的标志位来使用(两种状态的具体区分还需要搭配其它字段,该知识点会在下文详述)。

    如果引用未注册引用队列,则其自然就无法被加入到注册引用队列中,自然而然也就无法通过该方式来判断其所指对象是否被GC回收。但话虽如此,开发者依然可以通过调用get()方法的方式来进行判断。如果引用抽象类的子类没有重写该方法或在重写时没有添加额外的限制,则可以通过判断get()方法返回值是否为null的方式来判断所指对象是否被GC回收。事实上,由于将引用加入注册引用队列需要一定的程序与步骤,因此使用get()方法进行判断通常具有更高的实时性。

 待定列表

    待定列表是全局唯一的引用列表,以全局静态变量的方式保存在引用抽象类中,即用于持有待定列表引用的pending(待定)字段是一个静态变量。实际上,待定列表是逻辑列表并非真正的列表,即pending(待定)字段持有的实际是其头节点而非类似于ArrayList(数组列表)类对象的对象。由于引用抽象类在设计上兼容了节点(即组合了用于持有后继引用的discovered(发现)字段,当引用为尾节点时该字段值为自身,即自引用),因此可以通过遍历的方式来访问整个列表,从而只要持有了头节点就相当于持有了整个列表。如此设计的原因是因为待定列表本质上被作为堆栈来使用,节点/引用的插入/移除都会在列表的头部执行(头插法/头移法),因此并没有遍历整个列表的需求,故而无需使用功能如此完善的封装类。并且因为相同的原因,我们可以知道引用在待定列表中的移除是非公平的,即晚插入的引用反而会被先移除。

    待定列表被作为引用加入其注册引用队列之前的临时存放点。我们已知的是如果引用在创建时注册了引用队列,则当其所指对象被GC回收时其会被安排加入注册引用队列中,该任务会由“引用处理器”线程来执行。“引用处理器”类是引用抽象类自定义的静态内部类,同时也是Thread(线程)类的子类,被专门设计用来作为执行“将引用加入引入队列”的任务。“引用处理器”线程在引用抽象类的静态块中创建,整个系统中仅有一条,是优先级为10(最高)的守护线程,这令其能够获得更多的CPU资源来执行任务。但所指对象的回收显然是由GC线程负责的,两者的执行速率未必一致,如此当“引用处理器”线程无法及时将每个相关引用加入注册引用队列时将会造成GC线程等待(或者其它问题,该知识点会在下文详述)。为了避免这一点,自然就需要一块空间来暂存相关引用以充当两者间的缓冲区域,待定列表起的就是这个作用。

四 Reference(引用)机制


    所谓的Reference(引用)机制指的是从引用断开与其所指对象的连接到将引用从注册引用队列移除的完整执行流程,是在引用抽象类中的静态块中实现的。在这个流程中有着GC线程、API线程及用户线程的三方参与,引用在不同阶段会处于不同的状态,具体的流程及讲解如下所示:

    首先,引用初始会被统一保存在发现列表中。发现列表是一个由GC维护的引用列表,关于其的资料很少,源码中仅有几句描述,是属于JVM层面而非API层面的列表,目前唯一确定的是发现列表会保存所有引用,个人猜测这应该是GC为了快速检索及操作引用而采用的设计。引用位于发现列表中时处于活跃状态,由于活跃状态是引用的初始状态,并且初始状态下引用必然持有发现列表中的后继引用(发现列表与待定列表都使用discovered(发现)字段来持有后继引用,但区别是如果引用为尾节点则为null),因此可知引用再被创建时就被加入发现列表。

    发现列表中的引用会收到GC的特殊处理,或者说活跃状态的引用会收到GC的特殊处理。所谓的特殊处理,是指当引用的所指对象被GC判断为可回收时(判断条件会根据具体的引用抽象类子类的类型而有所差异),GC并不会像强引用一样直接将之回收,而是会先断开其与引用的连接(不通过引用的clear()方法实现)后再回收,并安排引用加入到注册引用队列中。GC线程首先会判断引用是否注册了引用队列,即判断queue(队列)字段持有的是否是“空”引用队列。如果是,说明未注册引用队列,直接将之从发现列表中移除,此时引用将直接变为最终的怠惰状态,意味着其已经完成了整个流程;否则意味着引用注册了引用队列,在将之从发现列表移除之后还需要以头插法的方式将之加入到待定列表中,并转变为待定状态,等待被“引用处理器”线程移除并加入注册引用队列中。因此可以知道的是,待定队列中保存的引用都是注册了引用队列的。之前我们说过,待定列表的存在是为了避免“引用处理器”线程无法及时将每个相关引用加入注册引用队列而造成的GC线程等待问题,事实上也可能是长遍历问题,即GC线程不等待“引用处理器”线程取走当前与所指对象断开连接的引用而直接向后遍历,等重遍历时在再由空闲的“引用处理器”线程将断连引用取走。但这么做必然会使得断连引用在发现列表中保留,从而在GC重遍历发现列表时需要遍历更长的长度,并且该方法也无法保证重遍历时“引用处理器”线程是空闲的。

    “引用处理器”线程会从待定列表的头部取出引用并将之加入注册引用队列中。关于“引用处理器”线程,上文已经详述过,此处就不再提及了。“引用处理器”线程所属的引用处理器类重写了run()方法,会以死循环的方式不断地从待定列表的头部取出引用并加入注册引用队列中。当待定列表中没有引用时,“引用处理器”线程会无限等待,直至待定列表存在引用后被GC线程唤醒。可以发现的是:待定列表虽然名为列表,但实际是作为堆栈来使用的,节点/引用的插入/移除都会在头部发生,这一点在上文也有所提及。如此一来造成的结果就是此处会存在线程安全问题,因为节点/引用的插入/移除分别由不同的线程执行,为此引用抽象类在此使用了类锁,即使用synchronized关键字并搭配静态变量的方式来保证线程安全。引用被从待定列表中移除后,理论上会直接加入注册引用队列中,但实际上此处还存在一个特殊判断,即判断引用是否是Cleaner(清洁工)类型(引用抽象类的孙类)。如果不是,则按正常流程将引用以头插法(队列…头插法…)加入注册引用队列中,并将引用转变为入队状态;而如果是,则该引用不会被加入注册引用队列,而是会在调用clean()方法(来自于Cleaner(清洁工)类)执行内部自定义操作后直接结束流程。该特殊操作是Reference(引用)机制的对外衍生,目的是取代不可靠的Finalization(终结)机制(但实际上两者的效果大同小异),具体内容会在Cleaner(清洁工)类/Finalizer(终结者)类中详述。

    引用加入注册引用队列中后,其注册引用队列将变为“入队”引用队列,以作为判断引用已入队的标志位。需要注意的是:引用使用next(下个)字段来持有注册引用队列的后继引用(如果引用为尾节点则自引用),这与发现/待定列表是不同的。为何如此设计的原因暂且未知,单单从API层面似乎看不出什么问题,我想可能与JVM层面的实现有关。我一开始猜测是不是因为引用可以同时存在于待定列表及发现队列中,后来发现并不能共存…后来我又猜测是因为next(下个)字段与queue(队列)字段组合共同表示引用状态的缘故,但转念一想如果继续使用discovered(发现)字段来持有注册引用队列的后继引用,则next(下个)字段完全可以替换为state(状态)字段来记录状态,这不仅不会增多字段的总数,并且状态的记录也比目前更加方便直观,因此我又否定了这个猜想。如果有知道具体原因的童鞋还望不吝赐教,鄙人不胜感激!

    加入注册引用队列的引用可作为其所指对象已被GC回收的判断依据,开发者可以此为契机执行某些自定义操作。这似乎是非常理所当然的事情,因为安排引用加入注册引用队列是所指对象被GC判定为可回收之后才会触发的行为。但如此就能判定所指对象一定已被GC回收吗?个人认为是否定的。因为Reference(引用)机制中的GC线程所做的仅仅是断开所指对象与引用的连接,真正的回收会由其它GC线程完成…那也就是说,“引用加入注册引用队列”与“所指对象被具体回收”这两者实质上是异步行为,没有任何实质证据证明引用加入注册引用队列时其所指对象一定已被GC回收。因此与其说“引用加入注册引用队列意味着其所指对象已被GC回收”,倒不如说“引用加入注册引用队列意味着其所指对象会被GC回收”更加合适…当然…这都是我个人的猜想而已。相比通过get()方法来判断所指对象是否已/会被GC回收,“引用入队”方案显然是存在延时的。上文的所有内容都在证明引用加入注册引用队列是程序性行为,这在实时性上与直接判断get()方法返回值是否为null的行为是无法比拟的,因为该判断在GC线程断开引用与所指对象的连接时就会成立…当然,这一切都建立在get()方法未被子类重写或重写时没有添加额外限制的基础上。

    开发者可以通过调用引用的isEnqueued()方法或从注册引用队列中取出引用的方式判断引用是否位于注册引用队列中,引用会从注册引用队列的头部取出,因此可知引用队列与待定列表一样,本质都被作为堆栈使用…可能大佬都是这个样子的…名字什么的差不多就可以了。当引用被用户线程从注册引用队列中取出时,意味着对于该引用而言,整个流程已彻底宣告结束。此时的引用将变为怠惰状态,表示其已经没有任何作用,只能等待着被GC回收。在这里某些童鞋可能已经发现了,如果开发者始终不将引用从注册引用队列中取出,是否意味着引用永远都不会变为怠惰状态呢?事实确实如此,虽说怠惰是引用的最终状态,但并不意味着引用一定会以怠惰的状态结束,并且上文的流程详述中也存在该类情况,在此我将对引用在Reference(引用)机制中的所有状态变化情况做一个总结,具体如下:

  • 活动 ——> 怠惰:引用未注册引用队列;
  • 活动 ——> 待定:引用注册了引用队列,但本身是Cleaner(清洁工)类型;
  • 活动 ——> 待定 ——> 入队:引用注册了引用队列,且本身不是Cleaner(清洁工)类型,但直至注册引用队列被GC回收都未曾出队;
  • 活动 ——> 待定 ——> 入队 ——> 怠惰:引用注册了引用队列,且本身不是Cleaner(清洁工)类型,并在注册引用队列被GC回收前出队。

 状态

    文中多次提及了状态,但始终没有整体性介绍这个概念,故而此处我将所有状态列举出来,具体如下所示。在引用抽象类中状态是一个很神奇的东西:一是因为其没有组合具体的字段来记录,而是通过queue(队列)字段与next(下个)字段组合表示的;二是在源码中也没有发现任何将之作为判断条件的地方…其似乎就是个单纯的概念,专门用于帮助开发者更加清晰的了解整个Reference(引用)机制…可能我还是有没有学透的地方。

    active(活动):活动状态是引用的初始状态,该状态下的引用会受到GC的“特殊照顾”。当所指对象被判定为可回收后,如果引用注册了引用队列,GC线程会将之加入到待定队列中,相当于将状态修改为了待定。而如果引用没有注册引用队列的话则会直接将其状态变为怠惰。当引用的状态为活动时,queue(队列)字段及next(下个)字段的值如下:
        queue(队列):注册引用队列或默认的"空"引用队列(如果未注册引用队列,会赋予一个默认的"空"引用队列表示未注册);
        next(下个):null(活动是唯一一个next(下个)字段为null的状态,因此可以直接通过判断next(下个)字段是否为null来判断其是否为活动状态)。

    pending(待定):当引用被GC加入待定列表时,即表示其状态变为了待定状态,该状态下的引用正等待着被“引用处理器”线程加入到注册引用队列中。由于加入待定列表是将引用加入到注册引用队列的前置步骤,因此可知只有注册了引用队列的引用才能够转变为这个状态。当引用的状态为待定时,queue(队列)字段及next(下个)字段的值如下:
        queue(队列):注册引用队列(待定状态下的引用必然注册了引用队列);
        next(下个):this(即自引用)。

    enqueue(入队):当引用被“引用处理器”线程加入注册引用队列时,即表示其状态变为了入队状态,这个状态下的引用正等待着被用户线程从注册引用队列中取出。当入队状态下的引用被用户线程从注册引用队列移除时,意味着其变为了怠惰状态,同时也意味着一个引用可能永远都不会变为怠惰…如果其永远都没有出队的话。当引用的状态为入队时,queue(队列)字段及next(下个)字段的值如下:
        queue(队列):“入队”引用队列(一个与“空”引用队列相同的预定值,表示当前引用已经加入的注册引用队列,可用作引用是否入队的判断条件);
        next(下个):注册引用队列中的后继引用,如果当前引用为尾节点则为this(即自引用)。

    inactive(怠惰):引用转变为怠惰状态有两种方式:一是因为没有注册引用队列而被GC线程设置;二是其从注册引用队列中出队时被用户线程设置。一个状态已经被转变为怠惰的引用意味着其已经没有什么事情可以做了,处于生命周期的尾声,存在的唯一意义就是等待被GC回收。当引用的状态为怠惰时,queue(队列)字段及next(下个)字段的值如下:
        queue(队列):"空"引用队列("空"引用队列即是引用未注册引用队列的默认值,也是其从注册引用队列中出队时会被赋予的终结值,因此"空"引用队列有着双重含义);
        next(下个):this(即自引用)。

五 相关系列


  • 《Java ~ Reference【目录】》
  • 《Java ~ Reference【源码】》

相关文章:

Java ~ Reference【总结】

一 概述 简介 在JDK1.2之前&#xff0c;Java中引用的定义是十分传统的&#xff1a;如果引用类型的变量中存储的数值代表的是另一块内存的起始地址&#xff0c;就称这块内存代表着一个引用。在这种定义之下&#xff0c;一个对象只有被引用和没有被引用两种状态。 实际上&#xf…...

最快方法求最长上升子序列(LIS)+最长公共子序列(LCS)模板(C/C++)

目录 1 LIS算法&#xff08;最长上升子序列&#xff09; 1.1 简介 1.2 代码 1.3 相关解释 2 LCS算法&#xff08;最长公共子序列&#xff09; 2.1 简介 2.2 代码&#xff08;动态规划&#xff0c;时间复杂度O&#xff08;nlogn&#xff09;&#xff09; 2.3 特殊…...

012+limou+C语言深入知识——(4)“结构体”与“枚举体”与“联合体”

一、结构体 1、结构体基础 &#xff08;1&#xff09;结构体完全声明 struct tag {member-list; }variable-list;//描述一个人 struct people {char name[10];//人名int age;//年龄int idnumber;//身份证 };&#xff08;2&#xff09;结构体不完全声明&#xff08;匿名结构体…...

Canvas百战成神-圆(1)

Canvas百战成神-圆 初始化容器 <canvas id"canvas"></canvas>canvas{border: 1px solid black; }让页面占满屏幕 *{margin: 0;padding: 0; } html,body{width: 100%;height: 100%;overflow: hidden; } ::-webkit-scrollbar{display: none; }初始化画笔…...

详解分库分表设计

详解分库分表设计 背景 ​ 在传统的单机数据库架构中&#xff0c;所有数据都存储在同一个数据库中&#xff0c;随着业务规模的不断扩大&#xff0c;数据量和并发量也会越来越大&#xff0c;这会给数据库的性能和可用性带来挑战。此外&#xff0c;当单机数据库的容量达到瓶颈时…...

动态规划-基础(斐波那契数、爬楼梯、使用最小花费爬楼梯、不同路径、不同路径II、整数拆分、不同的二叉搜索树)

动态规划&#xff0c;英文&#xff1a;Dynamic Programming&#xff0c;简称DP&#xff0c;如果某一问题有很多重叠子问题&#xff0c;使用动态规划是最有效的。所以动态规划中每一个状态一定是由上一个状态推导出来的。动态规划问题&#xff0c;五步走&#xff1a;状态定义&am…...

深入理解WebSocket协议

“ 一直以来对WebSocket仅停留在使用阶段&#xff0c;也没有深入理解其背后的原理。当看到 x x x was not upgraded to websocket&#xff0c;我是彻底蒙了&#xff0c;等我镇定下来&#xff0c;打开百度输入这行报错信息&#xff0c;随即看到的就是大家说的跨域&#xff0c;或…...

Vector的扩容机制

到需要扩容的时候&#xff0c;Vector会根据需要的大小&#xff0c;创建一个新数组&#xff0c;然后把旧数组的元素复制进新数组。 我们可以看到&#xff0c;扩容后&#xff0c;其实是一个新数组&#xff0c;内部元素的地址已经改变了。所以扩容之后&#xff0c;原先的迭代器会…...

22讲MySQL有哪些“饮鸩止渴”提高性能的方法

短连接风暴 是指数据库有很多链接之后只执行了几个语句就断开的客户端&#xff0c;然后我们知道数据库客户端和数据库每次连接不仅需要tcp的三次握手&#xff0c;而且还有mysql的鉴权操作都要占用很多服务器的资源。话虽如此但是如果连接的不多的话其实这点资源无所谓的。 但是…...

10.0自定义SystemUI下拉状态栏和通知栏视图(六)之监听系统通知

1.前言 在进行rom产品定制化开发中,在10.0中针对systemui下拉状态栏和通知栏的定制UI的工作开发中,原生系统的下拉状态栏和通知栏的视图UI在产品开发中会不太满足功能, 所以根据产品需要来自定义SystemUI的下拉状态栏和通知栏功能,首选实现的就是下拉通知栏左滑删除通知的部…...

怎样在外网登录访问CRM管理系统?

一、什么是CRM管理系统&#xff1f; Customer Relationship Management&#xff0c;简称CRM&#xff0c;指客户关系管理&#xff0c;是企业利用信息互联网技术&#xff0c;协调企业、顾客和服务上的交互&#xff0c;提升管理服务。为了企业信息安全以及使用方便&#xff0c;企…...

Activity工作流(三):Service服务

3. Service服务 所有的Service都通过流程引擎获得。 3.1 RepositoryService 仓库服务是存储相关的服务&#xff0c;一般用来部署流程文件&#xff0c;获取流程文件&#xff08;bpmn和图片&#xff09;&#xff0c;查询流程定义信息等操作&#xff0c;是引擎中的一个重要的服务。…...

算法--最长回文子串--java--python

这个算法题里面总是有 暴力解法 把所有字串都拿出来判断一下 这里有小小的优化&#xff1a; 就是当判断的字串小于等于我们自己求得的最长回文子串的长度&#xff0c;那么我们就不需要在进行对这个的判断这里的begin&#xff0c;还可以用来取得最小回文子串是什么 java // 暴…...

ElasticSearch-第二天

目录 文档批量操作 批量获取文档数据 批量操作文档数据 DSL语言高级查询 DSL概述 无查询条件 叶子条件查询 模糊匹配 match的复杂用法 精确匹配 组合条件查询(多条件查询) 连接查询(多文档合并查询) 查询DSL和过滤DSL 区别 query DSL filter DSL Query方式查…...

【AI大比拼】文心一言 VS ChatGPT-4

摘要&#xff1a;本文将对比分析两款知名的 AI 对话引擎&#xff1a;文心一言和 OpenAI 的 ChatGPT&#xff0c;通过实际案例让大家对这两款对话引擎有更深入的了解&#xff0c;以便大家选择合适的 AI 对话引擎。 亲爱的 CSDN 朋友们&#xff0c;大家好&#xff01;近年来&…...

美团笔试-3.18

1、捕获敌人 小美在玩一项游戏。该游戏的目标是尽可能抓获敌人。 敌人的位置将被一个二维坐标 (x, y) 所描述。 小美有一个全屏技能&#xff0c;该技能能一次性将若干敌人一次性捕获。 捕获的敌人之间的横坐标的最大差值不能大于A&#xff0c;纵坐标的最大差值不能大于B。 现在…...

【12】SCI易中期刊推荐——计算机信息系统(中科院4区)

🚀🚀🚀NEW!!!SCI易中期刊推荐栏目来啦 ~ 📚🍀 SCI即《科学引文索引》(Science Citation Index, SCI),是1961年由美国科学信息研究所(Institute for Scientific Information, ISI)创办的文献检索工具,创始人是美国著名情报专家尤金加菲尔德(Eugene Garfield…...

好不容易约来了一位程序员来面试,结果人家不做笔试题

感觉以后还是不要出面试题这环节好了。好不容易约来了一位程序员来面试。刚递给他一份笔试题&#xff0c;他一看到要做笔试题&#xff0c;说不做笔试题&#xff0c;有问题面谈就好了&#xff0c;搞得我有点尴尬&#xff0c;这位应聘者有3年多工作经验。关于程序员岗位&#xff…...

这几个过时Java技术不要再学了

Java 已经发展了近20年&#xff0c;极其丰富的周边框架打造了一个繁荣稳固的生态圈。 Java现在不仅仅是一门语言&#xff0c;而且还是一整个生态体系&#xff0c;实在是太庞大了&#xff0c;从诞生到现在&#xff0c;有无数的技术在不断的推出&#xff0c;也有很多技术在不断的…...

EEPROM芯片(24c02)使用详解(I2C通信时序分析、操作源码分析、原理图分析)

1、前言 (1)本文主要是通过24c02芯片来讲解I2C接口的EEPROM操作方法&#xff0c;包含底层时序和读写的代码&#xff1b; (2)大部分代码是EEPROM芯片通用的&#xff0c;但是其中关于某些时间的要求&#xff0c;是和具体芯片相关的&#xff0c;和主控芯片和外设芯片都有关系&…...

Django4.0新特性-主要变化

Django 4.0于2021年12月正式发布&#xff0c;标志着Django 4.X时代的来临。参考Django 4.0 release notes | Django documentation | Django Python 兼容性 Django 4.0 将支持 Python 3.8、3.9 与 3.10。强烈推荐并且仅官方支持每个系列的最新版本。 Django 3.2.x 系列是最后…...

MySQL高级面试题整理

1. 执行流程 mysql客户端先与服务器建立连接Sql语句通过解析器形成解析树再通过预处理器形成新解析树&#xff0c;检查解析树是否合法通过查询优化器将其转换成执行计划&#xff0c;优化器找到最适合的执行计划执行器执行sql 2. MYISAM和InNoDB的区别 MYISAM&#xff1a;不支…...

【Java】面向对象三大基本特征

【Java】面向对象三大基本特征 1.封装 On Java 8:研发程序员开发一个工具类&#xff0c;该工具类仅向应用程序员公开必要的内容&#xff0c;并隐藏内部实现的细节。这样可以有效地避免该工具类被错误的使用和更改&#xff0c;从而减少程序出错的可能。彼此职责划分清晰&#x…...

蓝桥杯C++组怒刷50道真题(填空题)

&#x1f33c;深夜伤感网抑云 - 南辰Music/御小兮 - 单曲 - 网易云音乐 &#x1f33c;多年后再见你 - 乔洋/周林枫 - 单曲 - 网易云音乐 18~22年真题&#xff0c;50题才停更&#xff0c;课业繁忙&#xff0c;有空就更&#xff0c;2023/3/18/23:01写下 目录 &#x1f44a;填…...

Shell自动化管理 for ORACLE DBA

1.自动收集每天早上9点到晚上8点之间的AWR报告。 auto_awr.sh #!/bin/bash# Set variables ORACLE_HOME/u01/app/oracle/product/12.1.0/dbhome_1 ORACLE_SIDorcl AWR_DIR/home/oracle/AWR# Set date format for file naming DATE$(date %Y%m%d%H%M%S)# Check current time - …...

Unity学习日记13(画布相关)

目录 创建画布 对画布的目标图片进行射线检测 拉锚点 UI文本框使用 按钮 按钮导航 按钮触发事件 输入框 实现单选框 下拉菜单 多选框选项加图片 创建画布 渲染模式 第一个&#xff0c;保持画布在最前方&#xff0c;画布内的内容显示优先级最高。 第二个&#xff0c;…...

初阶C语言:冒泡排序

冒泡排序是一种简单的排序算法&#xff0c;它重复地走访过要排序的数列&#xff0c;一次比较两个元素&#xff0c;如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换&#xff0c;也就是说该数列已经排序完成。1.冒泡排序关于冒泡排序我们在讲…...

带头双向循环链表

在前面我们学习了单链表&#xff0c;发现单链表还是有一些不够方便&#xff0c;比如我们要尾插&#xff0c;需要遍历一遍然后找到它的尾&#xff0c;这样时间复炸度就为O(N),现在我们引入双向带头链表就很方便了&#xff0c;我们先看看它的结构。通过观察&#xff0c;我们发现一…...

C#中的DataGridView中添加按钮并操作数据

背景&#xff1a;最近在项目中有需求需要在DataGridView中添加“删除”、“修改”按钮&#xff0c;用来对数据的操作以及显示。 在DataGridView中显示需要的按钮 首先在DataGridView中添加需要的列&#xff0c;此列是用来存放按钮的。 然后在代码中“画”按钮。 if (e.Column…...

WEB安全 PHP基础

WEB安全 PHP基础 PHP简述 PHP&#xff08;全称&#xff1a;PHP&#xff1a;Hypertext Preprocessor&#xff0c;即"PHP&#xff1a;超文本预处理器"&#xff09;是一种通用开源脚本语言。 在一个php文件中可以包括以下内容&#xff1a;  PHP 文件可包含文本、HTML、…...