队列底层同样可以采用顺序或列式实现。我们先来看顺序实现。在顺序实现中,底层通过数组来存储元素, max size 表示底层数组的长度, data 就是 我们的底层数组。 front 是 对头所引, rear 是 对尾所引。 抽象化通过 new 关键字在堆中创建底层数组的存储空间。 front 和 rear 默认值都为零。判断队列是否为空。为什么要用 front 等等于 rear 这个条件来判断呢?最初它们都指向零号所引位置, 随着我们的操作,它们可能指向其他位置。但只要 front 等等于 rear, 就 表示此时队列为空,这是对应的代码。如果队列为空,返回处,否则返回 false。 获取对头数据要用 q m t 方法判断队列是否为空,如果队列为空,返回 no, 否则返回对头元素的数据 入队。有时我们的队列会这样,在插入数据前需要移动元素才能继续插入数据。具体要向前移动几个格子呢? 我们发现正好是 front 对 应的锁眼下标,我们用木 l, e, n 来表示,于是我们将所有元素向前移动木 l, e, n 的 格子,这样就可以继续插入了。我们来看对应的代码, 如果对尾锁眼 re 大 于等于 max size, 此时对列可能有两种情况,我们还要看对头锁眼 front 是 否为零, 如果它为零,表示队列已满,无法插入,否则我们需要移动元素。为插入腾出空间。我们通过这个放循环将数组中的元素迁移。现在可以看入队操作了。首先调用 insert capacity 方法, 如果返回处表示有空间,可以进行插入,将 e 放入底层数组,缩写为 r 的 位置,并将 r 的 值加一 出对。调用 q m t 方法,如果对列为空,直接返回 non, 表示出对失败,否则返回对头元素的值,并将 front 的 值加一。 便利通过 q m t 方法判断对列是否为空。如果对列不为空,通过否循环输出底层数组所演成 front 到 rear 之间的所有元素。如果是循环对列,我们就不需要移动底层数组中的元素了,但需要注意如何判断对列已满。 此时队列满了, front 指向零, rear 应该指向哪里呢? rear 应该指向带插入的位置,此时哪还有带插入的位置了。 所以对于循环队列来说,不能真正的插满,这样就表示队列已满。也就是说 rear 加一抹上 max size 等于 front, 就 表示队列已满。这是对应的代码。前面的逻辑我们都分析过了,这行代码的逻辑是防止 rear 的 值超过 max size 处,对方法多了这行代码,防止 front 的 值超过 max size。 调整完这两个方法,我们的循环队列就完成了。我们再来看一下列式实现,我们定义了 q node 表示底层列表中的节点, 节点中还是数据域和指向域。除此之外,我们还定义了对头引用和对尾引用。除此化,通过 new 关键字在堆中创建头节点的存储空间。头节点的 dat 默认为 non, next 指向 non。 对 头和对尾引用都指向头节点,表示队列为空, 判断对列是否为空。如果 front 等等于 rear, 表示对列为空,返回处,否则返回 false, 获取对头数据,调用 q m t 方法判断对列是否为空,如果对列为空,返回 none, 否则返回对头元素的数据。 front 的 next 就是 对头元素 入队。我们采用尾插法创建待插入节点 node, rear 的 next 指向 node, 再将 rear 指向 node, 这是对应的原码。这两行代码是尾插法的核心逻辑。我们刚才分析过了,出队 front 的 next 就是 对头,也就是准备出队的节点。我们用 had 表示,将 front 的 next 设置为 had 的 next, 后期会自动释放 had 的 存储空间,这样就完成了出队。 但有一种特殊情况我们需要考虑,当列表中只有一个节点时,也就是 rear 等等于 had。 我 们将 rear 指向 front, 然后再将 front 的 next 设置为 had 的 next, 也就是 not 记 c, 会自动释放 had 的 存储空间, 这是对应的元码。调用 q m t 方法,如果队列为空,直接返回 not, 表示出对失败,否则将对头从底层列表中移除。对应的逻辑我们刚才分析过了, 便利通过 qmp 方法判断对列是否为空。如果对列不为空,通过外循环输出对列的所有元素。好,今天就聊到这,如果你有任何问题,欢迎大家在评论区或粉丝群里讨论。
粉丝1.9万获赞8.5万

hello, 大家好,未来的加瓦工程师们,欢迎大家来到加瓦零基础入门到精通的一个课程系列, 想要掌握加瓦这个武林秘籍吗?别急,我们一关一关的闯,不管你是大学生转行者还是纯好奇,跟着我点赞、关注、收藏起来,带你打开加瓦新世界的大门。 嗯,上一节课的话给大家讲了那个什么呃面向对象的编程啊,面向对象编程他的一个实现 方式和一个逻辑啊。我们来回顾一下,首先你面向对象的时候,你首先你要把什么生活中的呃案例或者说你要解决的问题,把它抽象成一个类啊?把它抽象成一个模板,然后呢?抽象成模板之后你要考虑一下这个什么 这个类他应该拥有哪些属性,对吧?那这个类他应该有哪些方法,是吧?然后呢?然后再进行代码里去实现,对吧?然后呢,从而达到解决这个问题的一个目的啊? 然后呢,我们这节课要讲的是面向对象的三大特征啊,一个是面向对象的一个三大特征,大家还记不记得啊?一个叫继承,一个叫多肽 啊?然后我们来看一下这节课,我们来看一下封装是什么啊?我们先看下学习目标,第一步我们要理解封装的一个核心逻辑和作用啊,然后第二个的话我们要掌握什么?掌握封装实现那个语法, 然后用用什么封装保护数据安全性啊?然后如何判断何时需要封装啊?首先我们来看一下封装的一个概念啊,封装就是什么隐藏对象内部的那个复查性, 对外暴露必要的操作接口,对吧?就说,哎,你不管我内部实现的逻辑,就算写几万行,几千万行代码,跟你外部没有半毛钱关系啊,你来调用我的时候,你只需要调用我的方法把参数传进来, 至于我业务系统业务方法内怎么走的,你不用管啊,你只管,哎,把参数丢进来,然后看看能不能给对你返回正确的一个操作结果啊。这就是封装 隐藏,隐藏外部啊,隐藏信息,信息隐藏,就是不让外部直接访问内部的数据啊,就是保证数据安全性,哎,我不让你外部直接可以获取啊。然后呢?就是访问 控制啊,就说通过权限访问修饰符啊,控制成员那个可见性啊。来,我们再来回顾一下这个权限访问修饰符啊, private 啊,本类,然后不同胞啊,同胞不能, 子类不能,全局不能啊。所以说封装我们用的最多的就是 private 进行封装啊,封装私有化成员变量,那我们封装完了之后,哎, private 我 修饰化了,那本类不能用,那我怎么办?那我肯定要对外提供两个方法,一个是什么?一个是 get 方法,就是说,哎,我提供一个 get 方法,就是说,哎,你可以获取这个变量,但你必须同通过这个入口来获取,就是从通过 get 方法这个入口来获取, 对吧?然后你 get 的 时候我会做一些,我可以做一些逻辑判断,之后我再把这个值返回给你,是吧? 然后设置的话就是,哎,你要,你要给这个对象的属性赋值啊,你不能直接通过对象点的方式来复制了,因为他只能本类访问嘛,你创建对象,那你肯定是在其他类去创建的嘛?不可能在本类就去创建这对象嘛,是吧?然后的话, 所以说要通过 set 方法,那你 set 进来的方法,我要先叫验你的值是否正确啊?是否符合我的一个业务逻辑规范啊?如果不符合,那我就,哎,不让你 set, 是 吧?然后通过认识来区分成员变量和参数啊?然后我们看一个案例,就是设计一个什么 银行账户内要求余额不能直接访问,对吧?哎,你不能直接访问我的余额,对吧?啊?然后的话就是存款必须达到正位数啊?就说,哎,对数据什么安全性那个叫验,合法性那个叫验,对吧?就是说封装吗?哎, 就是说要做这些事情,然后取取款的话不能透支,哎,就比如说你的余额都为零了,是吧?那还取什么款啊?取不了,然后的话,哎,还有的话就是提供什么余额查询的一个方法。 好,我们来看一下代码块是怎么实现的啊?第一个,哎,我们之前说了,你要想一个东西,你要实现这个东西,那我们是不是需要一个类一个模板啊?这个模板里面应该有哪些属性?第二步就是我们要考虑的啊,这个是账号对吧?一个户主,一个余额,一个密码,是吧?这些都核心数据啊。 然后的话我们要创建什么创建对象,那是不是要把这个出使出使信息给通过构造方法复制进来,是吧?那我们 对外提供 get 方法啊,啊?提供 get 方法,然后把这个对应的数据给到它,对吧?然后我们再设置方法的时候,我们要将验什么?将验姓名不能为空,对吧?如果是为空,那就不能不能给他 set 啊? 比如说我们要 set password, 如果修改密码的时候我们要干嘛?验证一下什么?验证一下原来的密码啊?是不是等于原来的密码?如果不等于的话也会报错啊? 嗯, 然后的话业务封装方法就是说你要去存款的时候必须什么要存款?必须是证书才能才能存款啊?然后这一块的话 啊,就是取款的话,哎,就是你必须金额干嘛必须大于零,不然还是不能进行取款的。还有个就是验证密码啊,验证密码内部使用啊,我们来看一下然后这个封装啊,封装它的一个作用啊, 其实封装就是通过 get set 的 方法嘛,对吧? get set 的 方法啊,这个四点九,对,就是这儿。然后的话我们来看一下这个类,首先 我们来看一下这个 test 的 类,你看我们创建了一个对象,是吧?这账号叫幺零零八六张三,密码是一二三四五六,对吧? 然后呢,你看我们通过封装了之后,我们是不能干嘛?不能直过,直接通过对象点的方式来访问这个属性,你看 为啥?因为这里用的是 private 关键字修饰啊,本类本类他只允许在本类访问,你外部是访问不了,但如果说我在这里面来访问,本类里面来访问呢? 就算六个卖方法吗?在里面写一个方法,他们在同一个类里面啊,这里是可以访问的啊,这不会报错,在外部他就不行,那外部不行,那怎么办? 那我们就要通过什么通过 get set 的 方法来访问啊?你看比如说我要获得他这个余额,对吧? get 半小时对吧?哎,是不是拿到了,对吧?通过 get 的 方法是吧? 通过 get 方法,然后的话,哎,如果说我们要 set 的 话,那是不是要修改的话,是不是调它 set 方法,对吧? set, 修改它帐号名,对吧? set, 然后你把参数传进去,然后它会判断,哎,你的参数是否为空啊,进行一些判断。 所以说总结来说封装就是干嘛?就是把数据哎,封装起来,藏起来啊,让你外部不知道任何细节,就是提供啊,你的一个 操作的一个简变性,具体这个业务逻辑你不管是怎么实现的啊?你不要管你,你外部就只管调用就行了啊,并且封装他的好处就是,呃,不管你在获取对象的值还是干嘛, 在在给这个对象赋值的时候,哎,你都必须从我提供了一个啊,这两个入口啊,一个是 get, 一个是 get, 一个是 set 啊,这两个入口,这两个入口,我可以在这两个入口进行逻辑判断啊,判断你的操作啊,是否符合业务逻辑的规范啊? 来我们来看一下啊,看一下它这个运行结果。 首先你看他说存款成功,我们就存款,对吧?存款必须大于等于零,对吧?大于等于零, 然后呢,他的余额就加上这个他的存款那个金额嘛,加上,哎,存款成功当前他然后打印一下当前余额,是不是走这里没错,是吧?然后如果说我们再去存款的话,哎,他存款失败的那负数,那他就存不进去了,对吧?所以说我们这个什么业务方法的 操作进一个封装啊,这是业务操作,又放一个操作的一个封装,封装的话,哎,你看他给你存款时候,他发现,哎。这个什么大于零呢?并不大于零,走 s 啊。就是说存款必须大于零啊,比如说我要取款,对吧?我要取两千,是吧? 取两千,哎。取两千?我刚才存了多少?刚才存了五千,对吧?我取两千啊。取两千。那那应该是取款成功,那他还剩什么?三千,对吧?还剩三千,那我现在还要取四千,那怎么办?取不了啊,取不了。为啥呢? 因为你余额只有三千的吗?余额不足了吗?是吧?你不说,哎,你要取款的话啊,他判断你余额足不足足的话就取款成功,并且要减掉什么?用大单钱的余额,减掉你这个余额啊,减掉不足的话就不行啊。然后的话,呃 我们要修改密码啊。原密码是什么?一二三四五六,对吧?一二三四五六。我们看一下是不是一二三五六,是吧?然后新密码啊,这个是这样的啊,他密码改掉了之后呢?这个地方他新密码就应该是这个了啊,但是他这里写的随便写一个啊,新密码, 哎,然后这里应该就把密码错误啊。第一个就是密码修改成功,这个是密码修改错误,对吧?啊?然后这里的话这就是封装 啊,这就是封装啊,银行取款案例的一个封装案例。然后我们来看一下封装的一个核心价值啊。 封装没有封装的话,干嘛外部可以直接修改啊?他会破坏你数据一个完整性。什么叫破坏你数据完整性?就比如说刚才那里你要要取款的时候,哎,你直接你不封装的话,你直接去操作,那你只有五千,对吧? 你只有三千,你要取五千,那也能取成功啊,那最后取了,那是不是有些东西就变成负数了,对吧?那就不合这个业务逻辑了啊?他是不合理的, 而有封装的话,他可以通过怎么通过方法进行校验,对吧?保证树立的数据一个合理性,对吧?然后就是隐藏细节,哎,外部只需要这样这个方法就可以存钱,对吧?啊?调用另外一个方法就可以啊? 存钱,一个方法存钱,一个取钱,对吧?修改密码,对吧?外部他不需他干嘛降低了什么使用的一个复杂度,并且他灵活, 呃,可以灵活的进行维护,包括你多个地方都可以调用你这段逻辑不用干嘛不用到处的重复写啊?重复写你只需要调用这个逻辑就可以了啊。 然后的话,如果说维护的一个灵活性,就比如说,哎,如果未来你要增加一个需求,对吧?就比如说你单次存款必须要大于五万你才能存款,对吧?那你在这加一个逻辑,对吧?前面加一个逻辑判断,如果说没有,那你就返回,对吧? 这就是他的一个方便灵活性的一个维护啊,这是封装。我们来看一下封装的一个设计原则啊,就是最小暴露啊,就是说这个东西你能私有就私有啊,属性全部 private 啊,就是统一的,统一的接口,就是通过方法啊, 访问什么数据,然后通过 get patterns 啊这些方法啊,进行访问数据,然后数据校验,在 set 的 方法中进行一个数据校验,存款不能会负数啊。业务性的完整性,也就是说相关封座,呃,相关操作封装在一起,然后就是什么取款等于什么 叫验加扣款加记录啊?比如说叫验,哎,你在内部写个封,封装一个方法啊,扣款你也封装成一个方法,哎,记录你也封装成一个方法,对吧? 然后呢?啊,最终你对外提供什么?提供一个什么取款的接口啊,就可以了,就是就是这个原则啊,最小暴露原则啊。 然后的话实际应用场景我们来看一下,就比如说用户管理系统,哎,就比如说我们要加密存储啊,存储 啊,还有购物车系统啊,就说,哎,数量不能为负数啊,就比如说 set 这些,比如说我们配置管理啊,配置密钥啊,可以,我们就比如说我们要配置一些密码,对吧? 就是比如说我们在在干嘛?数据库里面存的你的相关的账号密码的时候,我们存的那个密码啊,都不是什么都不是铭文的一二三四五六。一般我跟你说啊,开发系统中,比如说数据库里面有一张 user 表,对吧? user 表 表里面是不是有一个叫 p a s w r d password 的 字段,对吧?但是这个字段呢,我们一般一般不会存储铭文。什么是铭文啊?我们不会存,我们假设你密码是一二三五六,我们直接,我们不可能存,直接三四一二三五六,我们会干嘛? 我们会通,我们会通过啊,加密,比如说哈希,对吧?然后把你的密码变成什么?变成一串啊?不可逆的一个制服串,哎, 这样啊,这样去存到数据库,对吧?然后下次你输入密码的时候,那怎么办?那我们又通过之前那个哈希啊,哈希一个哈希方法, 哈希方法,对吧?你?哈希,然后你输入一个字母串,对吧?你每次得到的一个结果都是什么?都是一样的,一个都是一样的,一个什么很乱的字母串啊?这个就哈希的一个作用,那这样的话数据库不会存你的那个铭文,对吧? 那即使你什么,即使我们这个系统的最高人员,或者说我们开发者啊,都不会知道你的原始密码是什么啊?所以说这个密码只有你自己知道,这样的话,即使我们干嘛,即使我们的数据库被盗,系统被黑,对吧? 那他这个什么,他这个密码都是不是铭文存储的啊?他也不知道你的密码,这密码只有你知道,所以说这就会提高了什么?提高了一个系统安全性。所以说那我们在 user 里面啊,如果说我们 要要搞这个操作,那我们是可不可以在 user 对 象里面设置方法里面,对吧?然后在设置之前我们做一下哈西这个什么 password 之后干嘛再把你这个密码存到数据库,是吧?啊?所以说 set 封装它, set 方法,这就有这些作用, 然后的话维度啊,它主要是安全,对吧?保护好数据啊,就是防止外部直接非法修改啊。还有个就是简单啊,降低你这个方法啊,他只需要调用你的方法,把参数给你啊, 我不关心你内部的实现,你直接把结果给到我就行啊,如果结果不正确,那你自己去改啊,然后谁提供了这个就谁去改。就是我不关心你这个方法,具体你干嘛,反正你就要提供一个,我把参数给你, 你给我提供一个正确的结果啊,还有个就是灵活方面修改啊,就是内部实现啊,变化不影响外部啊,还有个就是什么可控统一管理啊,所有的操作都经过这两个方法啊, 然后我们看一下快速记忆口诀啊,就是属性私有化,然后就是方法公开化,它数据要保护啊,就是访问靠方法 get 取数据,那 set 的 话设置数据啊,业务方法里啊,教验加逻辑, 然后的话我们来看一下。呃,这有几个练习啊?练习一的话就是说啊,你创建一个时丢顿头类,对吧? 然后呢添加年龄和属性,然后你在在干嘛?在给这个啊,学生对象负责的时候,要求这个年龄只能在什么?在零到一百五之间啊。然后呢设置一个 order 类,对吧?订单类,你看一下这订单,就比如说你在淘宝上购买一个订单啊, 购买一个东西,那这个东西你是是不是有一个叫订单记录啊?这订单记录那是不有包含的一些核心的关键信息,就比如说你的下单时间对吧?你订单金额,你的订单编号是吧? 就这些,还有你的一个订单状态对吧?还有等等的属性,然后我们设计个 o 点类啊, o 点类,那你就设计个订单金额和订单编号嘛,就只要两个,对吧?然后呢就比如说你的状态啊,就比如说你的这个状态,就比如说啊,已经你已经收货了 啊,如果说你你定一个退款方法,哎,如果说你已经收货了,那你这个如果说再发你退款,那是不是不能退了? 或者说你这个订单的时间对吧?时间你超过了多少天,对吧?你就不能退款,比如说你这个订单的金额,比如添加订单时候啊,这订单金额不能会负数啊,这都是一些啊教训上的一个东西,然后然后再设计一个这个 这个类,然后的话射速和射速的一个自动转换啊,然后这这里的案例的话,大家可以按照这些思路,然后参照这个啊,慢慢的去实现一下, 从而更加的理解这个啊,封装的一个用处啊。然后这句话的话就给大家先说到这里, 嗯,上一节课的话给大家讲了那个什么呃面向对象的编程啊,面向对象编程他的一个实现 方式和一个逻辑啊。我们来回顾一下,首先你面向对象的时候,你首先你要把什么生活中的呃案例,或者说你要解决的问题,把它抽象成一个模板,然后呢抽象成模板之后,你要考虑一下这个什么 这个类他应该拥有哪些属性,对吧?那这个类他应该有哪些方法,是吧?然后呢?然后再进行代码里去实现,对吧?然后呢从而达到解决这个问题的一个目的啊? 然后呢我们这节课要讲的是面向对象的三大特征啊,一个是面向对象的一个三大特征,大家还记不记得啊?一个叫继承,一个叫多肽 啊?然后我们来看一下这节课,我们来看一下封装是什么啊?我们先看下学习目标,第一步我们要理解封装的一个核心逻辑和作用啊,然后第二个的话,我们要掌握什么?掌握封装实现那个语法, 然后用用什么封装保护数据安全性啊?然后如何判断何时需要封装啊?首先我们来看一下封装的一个概念啊,封装就是什么隐藏对象内部的那个复查性, 对外暴露必要的操作接口,对吧?就说,哎,你不管我内部实现的逻辑,就算写几万行,几千万行代码,跟你外部没有半毛钱关系啊,你来调用我的时候,你只需要调用我的方法把参数传进来, 至于我业务系统业务方法内怎么走的,你不用管啊,你只管,哎,把参数丢进来,然后看看能不能给对你返回正确的一个操作结果啊,这就是封装 隐藏,隐藏外部啊,隐藏信息,信息隐藏,就是不让外部直接访问内部的数据啊,就是保证数据安全性,哎,我不让你外部直接可以获取啊,然后呢?就是访问 控制啊,就说通过权限访问修饰符啊,控制成员那个可见性啊。来,我们再来回顾一下这个权限访问修饰符啊, private 啊,本类,然后不同胞啊,同胞不能, 子类不能,全局不能啊,所以说封装我们用的最多的就是 private 进行封装啊,封装私有化成员变量,那我们封装完了之后,哎, private 我 修饰化了,那本内不能用,那我怎么办?那我肯定要对外提供两个方法,一个是什么?一个是 get 方法,就说,哎,我提供一个 get 方法,就说,哎,你可以获取这个变量,但你必须同通过这个入口来获取,就是从通过 get 方法这个入口来获取, 对吧?然后你 get 的 时候我会做一些,我可以做一些逻辑判断,之后我再把这个值返回给你,是吧? 然后设置的话就是,哎,你要你要给这个对象的属性赋值啊,你不能直接通过对象点的方式来复制了,因为他只能本类访问嘛,你创建对象,那你肯定是在其他类去创建的嘛,不可能在本类就去创建这对象嘛,是吧?然后的话, 所以说要通过 set 方法,那你 set 进来的方法,我要先叫验你的值是否正确啊?是否符合我的一个业务逻辑规范啊?如果不符合,那我就,哎不让你 set, 是 吧?然后通过认识来区分成员变量和参数啊,然后我们看一个案例,就是设计一个什么 银行账户内要求余额不能直接访问,对吧?哎,你不能直接访问我的余额,对吧?啊?然后的话就是存款必须达到正位处啊,就说,哎,对数据什么安全性的个效应,合法性的个效应,对吧?就是说封装吗?哎, 就是说要做这些事情,然后取一款的话不能透支,哎,就比如说你的余额都为零了,是吧?那还取什么款啊?取不了, 然后的话,哎,还有的话就是提供什么余额查询的一个方法,然后我们来看一下代码块是怎么实现的啊?第一个,哎,我们之前说了你要 想一个东西,你要实现这个东西,那我们是不是需要一个类一个模板啊?这个模板里面应该有哪些属性?第二步就是我们要考虑的啊,第一个是账号对吧?一个户主,一个余额,一个密码是吧?这些都核心数据啊, 然后的话我们要创建什么创建对象,那是不是要把这个出使出使信息给通过构造方法复制进来,是吧?那我们 对外提供 get 方法啊,啊?提供 get 方法,然后把这个对应的数据给到他,对吧?然后我们再设置方法的时候,我们交易什么?交易姓名不能为空,对吧?不是为空,那就不能不能给他 set 啊, 比如说我们要设置 password, 如果修改密码的时候我们要干嘛?验证一下什么?验证一下原来的密码啊?是不是等于原来的密码?如果不等于的话也会报错啊? 嗯, 好的话,业务封装方法就是说,哎,你要去存款的时候必须什么要存款?必须是证书才能才能存款啊,然后这一块的话 啊,就是取款的话,哎,就是你必须金额干嘛必须大于零,不然是不能进行取款的。还有个就是验证密码啊,验证密码内部使用啊,我们来看一下然后这个封装啊,封装它的一个作用啊, 其实封装就是通过 get set 的 方法嘛,对吧? get set 的 方法啊,这个四点九,对,就是这儿。然后的话我们来看一下这个类,首先 我们来看一下这个 test 的 类,你看我们创建了一个对象是吧?他账号叫幺零零八六张三,密码是一二三四五六,对吧? 然后呢,你看我们通过封装了之后,我们是不能干嘛?不能直过直接通过对象点的方式来访问这个属性,你看 为啥?因为这里用的是 private 关键字修饰啊,本类本类他只允许在本类访问,你外部是访问不了,但如果说我在这里面来访问,本类里面来访问呢? 就算六个卖方法吗?在里面写一个方法,他们在同一个类里面啊,这里是可以访问的啊,这不会报错,在外部他就不行,那外部不行,那怎么办? 那我们就要通过什么通过 get set 的 方法来访问啊?你看比如说我要获得他这个余额,对吧? get 半小时,对吧?哎,是不是拿到了,对吧?通过 get 的 方法是吧? 通过 get 方法,然后的话,哎,如果说我们要 set 的 话,那是不是要修改的话,是不是调它 set 方法,对吧? set, 修改它帐号名,对吧? set, 然后你把参数传进去,然后它会判断,哎,你的参数是否为空啊,进行一些判断。 所以说总结来说封装就是干嘛?就是把数据哎,封装起来,藏起来啊,让你外部不知道任何细节,就是提供啊,你的一个 操作的一个简编性,具体这个业务魔镜你不管是怎么实现的啊,你不要管你,你外部就只管调用就行了啊,并且封装他的好处就是,嗯,不管你在获取对象的值还是干嘛, 在在给这个对象赋值的时候,哎,你都必须从我提供了一个啊,这两个入口啊,一个是 get, 一个是 get, 一个是 set 啊,这两个入口,这两个入口,我可以在这两个入口进行逻辑判断啊,判断你的操作啊,是否符合业务逻辑的规范啊, 来我们来看一下啊,看一下它这个运行结果。 首先你看他说存款成功,我们就存款,对吧?存款必须大于等于零,对吧?大于等于零, 然后呢,他的余额就加上这个他的存款那个金额嘛,加上,哎,存款成功当前他,然后打印一下当前余额,是不是走这里没错,是吧?然后如果说我们再去存款的话,哎,他存款失败的那负数,那他就存不进去了,对吧?所以说我们这个什么业务方法的 操作进一个封装啊,这是业务操作,又放一个操作的一个封装。封装的话,哎,你看他给你存款时候,他发现,哎,这个什么大于零呢?并不大于零走 s 啊,就是说存款必须大于零啊,比如说我要取款,对吧?我要取两千,是吧? 取两千,哎,取两千?我刚才存了多少?刚才存了五千,对吧?我取两千啊,取两千,那那应该是取款成功,那他还剩什么?三千,对吧?还剩三千,那我现在还要取四千,那怎么办?取不了啊,取不了。为啥呢? 因为你余额只有三千的吗?余额不足了吗?是吧?你不说,哎,你要取款的话啊,他判断你余额足不足足的话就取款成功,并且要减掉什么?用大当前的余额减掉你这个余额啊,减掉不足的话就不行啊。然后的话,呃, 我们要修改密码啊,原密码是什么?一二三四五六,对吧?一二三四五六,我们看一下是不是一二三五六,是吧?然后新密码啊?这个是这样的啊,他密码改掉了之后呢?这个地方他新密码就应该是这个了啊,但是他这里写的随便写一个啊,新密码,哎, 然后这里应该就把密码错误啊。第一个就是密码修改成功,这个是密码修改错误,对吧?啊?然后这里的话这就是封装 啊,这就是封装啊,银行取款案例的一个封装案例。然后我们来看一下封装的一个核心价值啊。 封装没有封装的话,干嘛外部可以直接修改啊?他会破坏你数据一个完整性。什么叫破坏你数据完整性?就比如说刚才那里你要要取款的时候,哎,你直接,你不封装的话,你直接去操作,那你只有五千,对吧? 你只有三千,你要取五千,那也能取成功啊,那最后取了,那是不是有些东西就变成负数了,对吧?那就不合这个业务逻辑了啊?他是不合理的, 而有封装的话,他可以通过什么?通过方法进行校验,对吧?保证树立的数据一个合理性,对吧?然后就是隐藏细节,哎,外部只需要这样这个方法就可以存钱,对吧?啊?调用另外一个方法就可以啊? 存钱,一个方法存钱,一个取钱,对吧?修改密码,对吧?外部他不需他干嘛降低了什么使用的一个复杂度,并且他灵活, 呃,可以灵活的进行维护,包括你多个地方都可以调用你这段逻辑,不用干嘛不用到处的重复写啊?重复写你只需要调用这个逻辑就可以了啊。 然后的话,如果说维护的一个灵活性,就比如说,哎,如果未来你要增加一个需求,对吧?就比如说你单次存款必须要大于五万你才能存款,对吧?那你在这加一个逻辑,对吧?前面加一个逻辑判断,如果说没有,那你就返回,对吧? 这就是他的一个方便灵活性的一个维护啊,这就是封装。我们来看一下封装的一个设计原则啊,就是最小暴露啊,就是说这个东西你能私有就私有啊,属性全部 private 啊,就是统一的,统一的接口,就是通过方法啊, 访问什么数据,然后通过 get patterns 啊这些方法啊,进行访问数据,然后数据校验,在 set 的 方法中进行一个数据校验,存款不能会负数啊,业务性的完整性,也就是说相关封座,呃,相关操作封装在一起,然后就是什么取款,等于什么 叫验加扣款加记录啊,比如说叫验,哎,你在内部写个封,封装一个方法啊,扣款你也封装成一个方法,哎,记录你也封装成一个方法,对吧? 然后呢?啊,最终你对外提供什么?提供一个什么取款的接口啊,就可以了,就是就是这个原则啊,最小暴露原则啊。 然后的话实际应用场景我们来看一下,就比如说用户管理系统,哎,就比如说我们要加密存储啊,存储 啊,还有购物车系统啊,就说,哎,数量不能为负数啊,就比如说 set 这些,比如说我们配置管理啊,配置密钥啊,可以,我们就比如说我们要配置一些密码,对吧? 就是比如说我们在在干嘛?数据库里面存的你的相关的账号密码的时候,我们存的那个密码啊,都不是什么都不是铭文的一二三四五六。一般我跟你说啊,开发系统中,比如说数据库里面有一张 user 表,对吧? user 表 表里面是不是有一个叫 p a s w r d password 的 字段,对吧?但是这个字段呢,我们一般一般不会存储铭文。什么是铭文啊?我们不会存,我们假设你密码是一二三五六,我们直接,我们不可能存,直接三四一二三五六,我们会干嘛? 我们会通,我们会通过啊,加密,比如说哈希,对吧?然后把你的密码变成什么?变成一串啊,不可逆的一个字母串,哎, 这样啊,这样去存到数据库,对吧?然后下次你输入密码的时候,那怎么办?那我们又通过之前那个哈希啊,哈希一个哈希方法, 哈希方法,对吧?你哈希,然后你输入一个字母串,对吧?你每次得到的一个结果都是什么?都是一样的,一个都是一样的,一个什么很乱的字母串啊?这个就哈希的一个作用,那这样的话数据库不会存你的那个铭文,对吧? 那即使你什么,即使我们这个系统的最高人员,或者说我们开发者啊,都不会知道你的原始密码是什么啊?所以说这个密码只有你自己知道,这样的话,即使我们干嘛,即使我们的数据库被盗,系统被黑,对吧? 那他这个什么,他这个密码都是不是铭文存储的啊?他也不知道你的密码,这密码只有你知道,所以说这就会提高了什么?提高了一个系统安全性。所以说那我们在 user 里面啊,如果说我们要 要搞这个操作,那我们是可不可以在 user 对 象里面设置方法里面,对吧?然后在设置之前我们做一下哈西这个什么 password 之后干嘛再把你这个密码存到数据库,是吧?啊?所以说 set 封装它 set 方法,这就有这些作用。 然后的话维度啊,它主要是安全,对吧?保护好数据啊,就是防止外部直接非法修改啊。还有个就是简单啊,降低你这个方法啊,他只需要调用你的方法把参数给你啊, 我不关心你内部的实现,你直接把结果给到我就行啊,如果结果不正确,那你自己去改啊,然后谁提供了这个就谁去改。就是我不关心你这个方法,具体你干嘛,反正你就要提供一个,我把参数给你, 你给我提供一个正确的结果啊,还有个是灵活方面修改啊,就是内部实现啊,变化不影响外部啊,还有个就是什么可控统一管理啊,所有的操作都经过这两个方法啊, 然后我们看一下快速记忆口诀啊,就是属性私有化,还有就是方法公开化,那数据要保护啊,就是访问靠方法 get 取数据,那 set 的 话设置数据啊,业务方法里啊,教验加逻辑, 然后的话我们来看一下,呃,这有几个练习啊?练习一的话就是说啊,你创建一个时丢顿特类,对吧? 然后呢添加年龄和属性,然后你在在干嘛?在给这个啊,学生对象负责的时候,要求这个年龄只能在什么?在零到一百五之间啊。然后呢设置一个 order 类,对吧?订单类,你看一下这订单,就比如说你在淘宝上购买一个订单啊, 购买一个东西,那这个东西你是是不是有一个叫订单记录啊?这订单记录那是不有包含的 一些核心的关键信息,就比如说你的下单时间,对吧?你订单金额,你的订单编号是吧?就这些,还有你的一个订单状态,对吧?还有等等的属性,然后我们设计个 order 类啊, order 类,那你就设计个订单金额和订单编号嘛,就只要两个,对吧? 然后呢?就比如说你的状态啊,就比如说你的这个状态,就比如说啊,已经你已经收货了,如果说你你已经收货了, 那你这个如果说再发你退款,那是不是不能退了?或者说你这个订单的时间,对吧?时间你超过了多少天,对吧?你就不能退款。比如说你这个订单的金额,比如添加订单时候啊,这订单金额不能会负数啊,这都是一些 啊教验上的一个东西,然后,然后再设计一个这个这个类,然后的话射速度和射速度的一个自动转换啊,然后这这里的案例的话大家可以。

你有没有遇到过各种诡异的问题?代码写的好好的,可就是不按照预期运行。比如这段代码逻辑很清晰,子线程在循环判断 flag 标识一旦为 false, 子线程会立即退出。 可明明已经在主线程中把 flag 改成 false 了呀,结果子线程就像瞎了一样,还在那傻傻的死循环,根本没有要退出的迹象。百般确认,逻辑确实没有问题, 可跑起来就是不对劲,是不是很邪门?先别慌,作为一个优秀的程序员,咱不信鬼也不信神,遇到事先点根香,啊!不对,是先点根烟压压惊!其实这根本不是啥神秘事件,而是并发编程里的一个非常典型的坑可见性问题。 今天我们就来拆解这个问题,顺便认识一位编程界的大神沃洛特尔,他不仅能解决可见性问题,还能避免指定重排带来的一系列问题。 如果你不清楚这两个概念,请不要划走,我们一一说来。这里是源码世界,用动画带你轻松搞定编程知识,专治各种看不懂。 先说说刚才的子线城为啥会眼瞎,以及啥是可见性问题。来打个比方,想象一下,你和同事用 get 维护同一个项目,你这边咔咔改完代码,还没来得及部署到远程仓库,结果你的同事一看,哎,代码怎么还是旧的呢?问题出在哪呢? 很简单,用过 get 的 都知道,你得先将最新提交推送到远程,然后同时再拉取一下,才能看见最新的改动。 像这种协助场景下,一个人的改动,别人无法立即看到的现象,就是可见性问题的雏形。在 java 的 世界里,为了屏蔽不同硬件的底层差异, jvm 搞了一套规范,叫 java 内存模型, 也就是 j m m。 在 这套规范里,所有的共享变量都存在主内存里,并且每一个县城都有自己的工作内存, 里面放着主内存变量的副本。县城要改一个变量,得先修改自己工作内存里的副本,这时候,主内存和其他县城的工作内存里的值都还是旧值。 如果此时另一个县城恰好来读这个变量,它读到的自然是自己工作内存里的旧版本。不过这只是 jvm 层面的解释。其实问题的根源在硬件那儿。 我们知道 cpu 跑的太快了,内存根本跟不上。为了不让 cpu 干等着,工程师们在 cpu 和内存之间加了多层缓存,每个 cpu 核心都有自己的缓存。那问题来了,对于同一个共享变量,不同核心缓存里的数据不一样,怎么办呢?工程师们自然也意识到了这个问题, 于是他们搞了一个叫缓存一致型协议的东西,专门用来让各个核心的缓存数据保持一致。照理说,有了它,数据就应该一致了呀,不应该有可见性问题了吧? 问题没那么简单,工程师们觉得多级缓存虽然读起来快,但同步起来还是太慢了。举个例子, 一个核心要改某个共享变量,他不光要把结果写到自己的缓存里,还要扯着嗓子喊,嘿,兄弟们,我改了啊,你们手里的那个版本已经作废了,不能再用了,收到请回复。然后他就傻等着, 直到所有的核心都回复收到,已作废,这时候才可以继续干下一件事。对于 cpu 来说,这种等待简直就是浪费时间,于是工程师又在每个核心和缓存之间加了一个斜缓冲区。这下爽了,要写数据,直接扔到斜缓冲区里就行,不用再等了, cpu 可以 继续疯狂的执行后面的指令,效率确实提高了。但问题也来了,数据没有从协缓冲区写入缓存时,不同核心看到的数据仍然是不一样的,这就是可见性问题的硬件根源。聊完可见性,再来说一说另一个坑,指令重排问题。 也就是说,代码实际执行的顺序可能和你写的代码顺序不一样。这事发生在两个阶段,编一期和 cpu 执行期。由于代码的执行顺序一定程度上会影响 cpu 的 执行效率, 所以在编一代码时,为了尽可能的充分利用 cpu 的 性能,可能会根据一些规则适当的调整代码的前后顺序。 而 cpu 执行时,还记得前面说的斜缓冲区吗?可能会出现这种情况, a 等于一的数据还在斜缓冲区里等待时, b 等于二可能已经执行完了,并且数据先一步来到了缓存里,此时从别的核心的视角来看,相当于先执行了 b 等于二,就好像发生了指令重排。不过,无论是哪种重排,在单线程下都是能够保证程序的最终结果是正确的。 但在多县城下可能会出大问题。比如这个例子,主县城负责出售化配置数据,然后设置 over 为处,表示配置已就绪,子县城循环等待,一旦配置就绪,就读取配置数据并执行后续逻辑。 但是如果主县城发生了指令重排,先执行了 over, 等于处子。县城一看配置已经就绪了,立即去读取配置数据,但是配置数据可能还没有初步化完,这时候很可能会发生空置针异常。 好了,问题搞清楚了,一个是看不见,一个是乱排序,怎么解决呢?没错,就是开头我们提到的关键字 volatile。 从语义上说,它有两个作用, 第一,保证可见性,被他修饰的变量一有改动,立马强制同步到主内存,同时其他工作内存中的变量副本会立即失效,需要时必须从主内存重新读取, 从而做到县城间的立即可见。第二,禁止指令重排,它在变量读写前后各加了一道内存屏障,这就像在代码里砌了一道墙,告诉编辑器和 cpu, 墙前面的代码不允许跑到墙后面去,墙后面的代码也不允许跑到墙前面去。 在硬件层面,这个所谓的屏障其实就是一条特殊的指令,比如叉八六架构下的 lock 前缀指令,它会以原子操作,强制把斜缓冲区里的数据刷到缓存里,这就保证了可见性。 由于前一条指令的可见性一定在后一条之前,所以从其他核心的视角来看,也就没有指令重排的效果了。对于硬件层面的解释比较粗糙, 并且不同硬件的实现细节也不一样。但作为 java 程序员,我认为能理解到 jvm 规范这一层,就足够能写出健壮的代码了。底层硬件的事就留给爱卷的大神们在评论区讨论吧,如果你喜欢我的视频,别忘了关注我,下期更精彩!

然后呢我们接着昨天继续讲哈,昨天是把这个一个员工管理通过复制这个管理员的一个技术模块侦查检查给复制出来了,然后呢对应的他的侦查检查 复制出来之后呢,他的只有几个时段啊,看一下结构, 有 id, 然后有名,密码,姓名。那么这个员工管理的字段呢?它肯定不是这几个字段了,员工,员工员工表,不止这个不止啊, 这几个字段,然后呢,我们肯定要增加增加字,相应的业务字段啊, 相应的业务字段,然后呢?如,嗯,年龄,嗯,然后或者是呃部门等等啊,好多。 然后呢进行业务字段,然后呢对应的一个存储展示, 用微叉,现在是用微叉,可以用那个 int 型,使用这个微叉不进行年龄统计的话,它基本上微叉和 微叉和数字都是 ok 的。 然后呢是步码, 这样的话我们要进行那个业务的扩展啊,本节小节呢?就是本节呢,就是说本节视频啊, 本节视频主要是业务,业务表,业务表增 加次段,然后呢 d o 字段的增加,扩展,减少,都是差不多一样啊,主要是主要的是涉及几个地方设计, 几个地方修改啊, model, d o 方法都不用 model, 还有是呢, betis 数据库引设的一个查询数据持久程的一个配置文件。 然后呢,如果有业务逻辑的话,逻辑,因为咱们有 service 嘛, service, service 城直接写在了,因为它属于简单的代码, service 城比较少, 业务功能比较少,直接和在了,和在了,是 control 控制上, 这个不不是排排斥那个四位数啊,只是说 有这个是可以的,没有的也是可以实现的,所以说是不是排斥的啊,就是没有么太大必要,如果业务逻辑多的话,必须得有个四位数去控制业务逻辑。然后呢 我们把现在就是演示一下,最后呢再改完之后呢,这是后端啊, 后端重要的是看地方,前端的对应的字段的展示,前端的对应字段的展示这些啊, 这些如果是提示,再用那个 cursor 之类,如果提示词不是那么利索的话,或者不准确的话,可能是要改错的。 现在我们要做一个对应的一个实实操了啊。然后呢,我们要做一个这个首先要改一个是 这样 ok 了,这个是对应的它这个两个字段加上了 time model 层有了,你再检查一下这个 d o 层啊,没有什么需要改的,可以不用改。然后我们要改一个是什么呢?改一个是 mybet, 从上面看啊,这面可以,上面可以不改,因为它输入了一个拼音的一个查询条件。然后呢我们把这个主要是改的 insert 或者是 insert 和 update, 这个 你 set。 然后呢我们改一个年龄,加上其实也不是改起年龄部门,这就提示出来了。然后呢是 if 这边改 一录那个 table 键就 ok 了啊,这样的话我们就完成了它的一个修改啊,主要是 delete max 可以 要,如果用,用的话可以进行拼接,如果不用的话基本上也没有太大意义。 然后呢我们要做的是 前端已经,这个后端已经完成了啊,这个改完之后呢,检查一下对应吧,因为没有对应的,没有特别的,除了查询条件,查询条件用的时候再改, 这样的话我们就改完,改完之后呢,我们要做改改前端啊,改完之后如果是没有这这一报的话,我这一报好像过期了,我要重启一下, 重写一下这个服务,加完了也才能购买 base 文件才能生效。这个时候呢我们要改对应的一个是 改对应的一个前端页面代码复制一下啊, 程序员呢基本上就是 control c, control a 用的比较多,然后呢这个是年龄, 年龄,然后呢 然后是部门 把显示页面,我们要改一下 年龄, 把这个胶液加上一下啊。 嗯,其实就是让 bug 版的去改这些对应扩展代码也没有什么特别的, 这梳子因为已经固化了一个,就是一个中间两下模板,按照模板对应的地方去贴 好了,这个就可以正常显示了。啊,我们刷新一下页面啊,这个就是把这个出来了,然后呢我们编辑一下啊, 这样 ok 了,这个就是实现了他的一个业务自动扩展,这个好像再改一下,这个是员工, ok, 这样呢我们完成了一个业务自动的扩展,然后在下个视频继续学习新的业务技术知识啊。

今天咱们来聊一聊在并行编程当中如何使用 release 和 acquire 这两种内存序来确保不同县城之间操作的顺序和可见性。 这个也是一个比较重要的话题,那我们就直接开始吧。咱们先来说说就是这个烽火台模型在并行开发里面代表了什么。我们可以想象一下,就是有三座烽火台, a、 b、 c, 然后呢它们是互相能看见的,成了一个犄角之势。 那这个时候呢,有一个探马看到了有敌情,他就会把这个军令挂到 a 台上面,然后 a 台就会点燃烽烟。 那后面的大营看到了这个烽烟之后,他就会过来取这个军令,然后根据这个军令来调兵遣将。 其实这个就是一个非常简单的对并行系统里面核心问题的一个比喻,就是怎么去保证一个操作的结果,对另外一个操作是可见的,并且是有序的,听起来挺直观的,那如果说这个顺序颠倒了会怎么样?其实这里面是有一个很容易被忽视的风险的, 如果说这个传令兵他太着急了,他先点了封烟,然后才把这个军令挂上去。嗯,那这个时候呢,大营看到了封烟,他马上就过来取这个军令,结果发现这个木沟上面什么都没有,就导致 b、 c 两台按照一个错误的或者说不完整的信息去进行布防了, 对,那整个军队就会陷入到一个非常被动的局面。哦,那就是说为什么在现代的 cpu 和变器下面会出现这种操作顺序被打乱的情况,其实这个就跟 cpu 的 缓存以及变器的优化是非常相关的,就是, 呃,我们以为的这个指令的顺序,嗯哼,在实际的执行过程当中,或者说在这个最终的内存的结果上面,他未必会保持这个顺序。 就比如说这个挂军令和点封烟这两个操作,可能因为优化的原因,点封烟这个操作就被提前执行了。 对,然后这个时候呢,就会出现远处的人,他先看到了封烟,但是他却没有看到这个军令。听起来就很像烽火台的这个信号和这个命令不同步了吗?没错没错,这个就是所谓的可见性的问题,就是你这个信号已经发出去了, 但是我的数据还没有准备好。嗯,那这个时候大营就会以为说我看到了封烟就代表一定有军令,但其实这个军令可能还没有挂上去。在这种乱序其实就是由类似于风势也就是变易器的优化和这个烽火台也就是 cpu 的 缓存这两个底层的机制所导致的。 那我们下面就来谈谈怎么去避免这种混乱,嗯,对吧?就是我们在并行编程里面,这个所谓的 release 和 acquire 这两个内存序到底是怎么去约束这个操作的顺序的? 其实这个就像我们刚才说的那个烽火台的系统,它之所以会乱,就是因为这个操作的顺序没有被严格的遵守。嗯,那我们的这个 release 和 acquire 其实就是给这个系统立下了一个铁律,所以这个铁律是怎么应用到我们的这个传令兵和这个大营之间的呢?是这样的, 这个 release 他 就相当于给这个传令兵下了一个死命令,就是你必须要先把这个军令挂好,然后你才能点这个封烟啊,并且你在点这个封烟之前,你所有的准备工作都必须全部完成。 对,然后这个奥快瑞呢,就是反过来,他是要求这个大营你必须要等到看到了这个封烟之后,你才能去拿这个军令。嗯,而且你所有的调兵啊、备粮啊这些动作, 你都必须要等到拿到了这个军令之后你才能开始。哎,你说这个所谓的内存屏障,他在这个变形编程里面到底是扮演一个什么样的角色?其实你可以把这个内存屏障想象成是在这个信息流当中的一个坚固的栅栏,哦,就是他会确保在这个栅栏之前的所有的操作 都必须要完成,并且对其他的县城可见之后才可以执行这个栅栏之后的操作。那这个东西是怎么保证我们的这个信号和数据的同步的呢?就比如说我们的这个 release 栅栏,它会保证在点这个封烟之前的所有的操作,包括这个挂军令 都是对其他的县城可见的。嗯,然后这个 aquire 栅栏呢,就是会保证在你看到这个封缉之后的所有的操作,比如说读这个军令,他是不会被传排到这个获取点之前的。 对,所以这个就相当于在这个信号和数据之间建立了一个可靠的传递通道。原来是这样啊,那这个 release acquire 到底是给我们的这个操作的顺序带来了什么样的承诺?可以这样说,其实他是在这个弱内存序的硬件上面建立了一种逻辑上的同时性, 嗯,就只要这个大营看到了这个封烟,那他就一定可以读到完整的军令,即使说这个实际的挂令和点烟之间是有延迟的, 但是在这个逻辑上面,这两件事情是被绑在一起的,就是一个先因后果的关系。所以他其实保证的是这个因果关系的成立,而不是说这个时间上的完全一致。 对,他并不是说要保证说这个操作马上就被局都看到,他是保证说当这个信号到达的时候,这个数据一定是已经到达了。 嗯,所以他是在这个烽火台的故事串联起来 这个变形编程里面的这个乱序执行内存屏障背后的一些核心的思想。嗯,希望大家听完之后能对这些概念有一个比较直观的理解,咱们下次再见,拜拜。拜拜。

那么我们上一节课呢,已经做过了一个简单的测试,就是说我们现在的这个大模型啊,它是没有这个记忆功能的啊,那如果想让它和我们线上的,比如说 deepsea 一 样,能够 不断地去问它,然后不断地交互它有记忆功能,那我们需要做一些额外的一些设置,那我们来运行一下这样的一个案例 相关的这个依赖包呢,我们导进来一下, 嗯,这里呢,大家这个不要引错了,不要引错了,我们这里引的是 open i 的, 哎,不对,不是 open i。 等一下啊,这个 open i 的 刚才说错了,这里呢是引入这个啊, data message, 你 有同一类名,在不同的包下面都有啊,大家注意一下 data message 啊,像这种呢,就只有一个就可以,直接直接引就可以了。 好,嗯, 我们来看一下和我们和大模型的对话,我们做了什么工作能够让它具有这个记忆的功能呢?第一步呢,首先是我们创建了一个 user message 的 一个对象,这里封封装的参数呢,是我们向大模型发送的这个消息, 那么把这个消息呢,给到我们的千万叉的 model, 它会得到,会得到一个响应,对吧?我们会得到一个响应啊, 那么从这个 response 里面呢,我们去得到具体的回复内容, ai message, 然后呢通过 text 方法呢,得到大模型的文文本输出。那么第二轮对话的时候呢,我问他,你知道我是谁吗? 我们这个问题好,要和我们第一轮对话的问题 um 一 以及它的回复 ams 一 全部都要 传给我们第二次第二轮对话的这个模型,也就说我们把第一轮对话的问题以及回答的结果,再加上第二轮的问题都给到大模型,大模型才能够知道第一轮到底 问了什么问题,他才能基于之前的绘画,之前的这个对话来做进步的回答。所以说我们可以看到 所谓的记忆功能其实很简单,就是把第一轮对话的内容以结果全部给到第二轮。那如果你还有第三轮、第四轮,同样的道理,要把第一轮,第二轮他的相关的回复以及问题全部都给到下一轮,这样才可以。 ok, 那 我们来验证一下这个功能啊,这里呢,我们把内容输出一下, 这次呢他应该是能够知道啊,我是小新。 好,当然我们后面会有比如说简比稍微简单一点的方式来实现这样一个记忆功能。 你看第一次说啊,我是小新,对吧?他这是回答你。好,小新,那第二轮我问他,你知道我是谁吗?他说你刚刚提到你是小新,这样的话,我们大模型呢,他就具有了这个记忆的功能,对吧?那么 但是我们好,比如说有很多人都向我们的这个大模型问话,那他不能,对吧?他不能,比如说我去问我去,我去那个,呃,问他的时候说我是小习,那另外的人去问他的时候, 他不能直接回复他,他是小心啊,因为另外的人并没有给大模型这样的一个语言的设定啊,所以我们接下来要做的是说叫做啊 隔离记忆。什么是隔离记忆呢?就是就和我们嗯,雪茄 web 的 时候的一个绘画一样,不同的人所建立的这个绘画呢,它是相互独立的,不能够相互的受影响。好,那么 啊,这里还漏了一个知识点啊,是说使用 data memory 来实现一个聊天记忆。好,嗯,好,我们把这里也说一下吧,就是说 刚才我们讲使用这种方式相对比较复杂一些吗?对吧?比如说当你有多轮对话的时候呢?嗯,他就比较的这个麻烦啊。这里呢是说使用叉的 memory 呢,我们可以把我们的这个这个记忆功能呢做的简单一点,我们直接设定一下我们 需要记忆的他的这个对话的这个轮数,比如这里我设置的是十,比如说我们像大圆模型发送十次问题他都能够记录下来,那当你但,但是当你超过十次的时候呢,他就记不住了,所以这个大小呢,是我们可以去自己去设置的。 好,嗯,我们来测试一下这样一个功能,这样的话我们再去做交互的时候呢,就简单很多。 这里呢使用的是这个 data memory, 就是 是聊天的一个啊, memory 是 内存嘛,对吧? 使用这么一个对象呢,嗯,我们就可以去实现一个啊记忆的功能 啊。这里呢我们是直接使用的是这个啊, java, 纯 java 的 方式。嗯,直接创建了一个, 用 asus 直接创建了一个 assistant, assistant 的 一个代理对象,然后给它设置了一个我们的这个模型,然后以及 java memory 啊,后面我们这个会做一下优化,这就相当于是直接和我们的这个,嗯, spring boot 就 关系不是特别大了,对吧?不是特别大了,你像我们最开始的这个编码,你看我们直接用 assistant, 然后直接发起对话,它是用 spring boot 帮我们生成了一个代理。 啊,那这里呢?是我们直,我们是用显示的方式啊,构建了一个 assistant 的 一个代理对象啊,然后设置了一个 模型,并且设定了啊 data memory 啊,那两次绘画,第一次说我是小新,你看第二次说我是谁,这里呢,并没有把第一次的结果和问题给到第二次,但是呢,我们通过这种啊 data memory 的 方式呢,它仍然可以实现这样一个效果。好,我们来 发送一个问题。 好,嗯,第一次的,第一次的这个第一次的回答和刚开和刚才的基本是一样的,对吧?然后第二次呢,也基本差不多,你刚刚告诉我你是小新,说明这个记忆功能呢,仍然是实现了的,对吧? 那下面呢,我们来,嗯,使用我们的 asus, 因为刚才我们讲这里是用硬编码的方式,对吧?硬编码的方式去设置设定了 它的一个呃代理对象,那下面呢,我们还是用刚才的 asus 来来实现。那么我们重新创建一个 assistant, 这里呢?创建一个叫 memory chat, 对 吧?刚才的我们的这个是直接就叫 assistant, 对 吧? 创建一个 memory chart, 这里呢?我们去指定一下啊,指定一下我们的 data memory 是 谁就 ok 了。 好,那我们就可以使用 asus 的 方式啊,和大模型进行对话,显示的指定模型以及啊我们的这个 data memory, 这样的,我们需要,因为你要去指定这个 data memory, 那 你要去要把我们的 data memory 呢给到我们 spring, 对 吧?我们这里呢,是用 add bin 的 方式啊,创建了一个啊 data memory 的 对象啊,并且设定了十轮对话啊, 我们创建一个 config 的 包, 你看这里呢,其实就是呃个这这一行代码和刚才的手动的这个编码是类似的,对吧?是一样的,只不过呢,我们嗯又通过 add bin 以及 add configuration 把我们的这个 data memory 对 象呢给它注册到了 spring 容器中, 嗯,大家应该都能理解,对吧?能理解,因为你既然在学这个 spring, 你 对 spring 的 基本的这个特征应该是有所了解的。 ok, 这样呢,我们这个 memory chart 的 这个 service 呢就创建完成了,我们接下来做测试,就跟刚才呃回话的方式一样的,直接调用 ac 的 它的这个叉的方法就可以了, 插入 memory 里面把它给注入进来。 好,咱们来运行一下,效果呢,应该是一样的,只不过这样呢可能就更简单一点吧。 ok, 嗯,这里呢已经结果已经出来了,对吧?还是还是你刚刚告诉我你是小新? ok, 那 说明记忆功能呢是实现了的。 下面呢,我们来说一下这个隔离聊天,隔离聊天记录就是每个人呢,他需要有一个独立的绘画,你就不能说 a 用户告诉他我是小新,那 b 用户他就问他你是谁的时候,他就回答我是小新, 对吧?那这样就那如果 b 用户就给他设定了一下,说你是对吧,你是别的名字,那其他用户得到的也是这个名字,这是不合理的。所以说我们就要像以前的 java 外部一样,每个绘画是相互隔离的,你不能有影响啊, 那我们这里的这个隔离记忆呢啊,需要来单独创建一个啊, assistant 的 这样的一个接口啊,跟刚才的呢做一个简单的区分。 这里做做隔离记忆呢,主要是引入了一个叫啊 memory id 的 这样的一个啊属性叫做聊天的 id, 就 每个人呢,我们需要给它设定一个 memory id 啊, 这样的话就能实现我们的这个隔离聊天,其他的基本都是一样的。 好,另外呢,我们还要再去提供一个啊, data memory 的 一个 provider, 我们来看一下这个 provider, 它的这个功能跟刚才是类似的啊,主要的呢就是给每一个 memory id 啊,去设定一个聊天的轮数是十轮啊,就是每一个人啊,他都能够去 聊十次,聊十次呢,它是有,这说明这十次以内呢是有记忆功能的啊,这是跟刚才那个 config 有 点类似啊,有点类似,就是刚才我们的啊,比如说我们的这个 memory chart 森啊,不是这个,抱歉,是这个。嗯, config 里面 memory chart, 呃, system config config, 它这里呢只是简单地去设定了一下聊天的这个轮数啊,它和这个嗯 memory id 没有关系。 接下来呢,我们来创建的这个呢是和 memory id 相关的每一个 memory id 啊,有十轮,有十轮的这个记忆功能,当然你也可以设置成二十轮,三十轮, ok 的。 好,那么我们来做一个简单的测试啊, 好,这里呢,我们再去发起聊天的时候呢,给它指定了一个 memory id, 那 么相同的 id 呢,我们认为呢是一个绘画或者说一个用户啊,那么前两行呢,都是一啊,第三行呢是二, 那第三行的话相对他是一个,他肯定是个独立的一个绘画。那我们并没有告诉大模型 memory id 等于二的时候他的这个呃,他是谁,所以说他不会说他是小琴, 这样咱们就实现了一个隔离,那么你和这个隔离的这个功能和我们去用 deepsafe 或者是豆包都是一样的。你每次去我们来简单看一下, 在这里做对话的时候,你每次去开启一个新的绘画的时候,比如说你发起了一个一二一二一,然后你再去开启个新绘画,那么它就相当于是相当于是一个新的 member id 啊,相当于是个新的 member id, 就 这样一个,就这样一个概念。 好,这里有一个简单的错误,我们来看一下, 好说没有找到这样一个 bit 叉的 system 来瞅一眼啊, 是讲说它创建这个啊 separate 叉的啊, system 的 时候呢?报错了啊,那我们来看一下这个 system 啊,这里呢,我们是写错了,这里是你不能有两个叉的 memory 啊,我们这里是个叉的 model 啊,我们用的是千万叉的 model。 好,直接改一下就可以了啊,文档里面也是一样的。 好好,咱们再来运行一下。 嗯,可以看到应该是没有什么问题,这是第二次的回答, 好看下,第一次说你好,小新,第二次说你是小新,第三次说 啊,说的跟上面就没有关系了,对不对?所以说啊,我们这样的话就实现了一个啊白绘画的一个记忆的隔离。 那么这节课呢,我们就讲到这里,那下面呢,主要说的是说我们要把我们的记忆功能, 或者说我们的聊天的记录啊,有没有办法持久化下来啊?如果说你能持久化下来的话,那我们 第二次登录或者第三次登录的时候,或者你关机重启之后,你仍然能够获取到你之前的聊天的记忆啊?这是我们下节课的一个知识点。

我问你啊,给你一个百万行的 excel 文件,怎么高效稳定的去导入数据库?当你听到这个问题的时候,如果第一反应是用 p、 u i 这些工具来 读出来,然后一条一条的音色,那么你可能正在掉入一个导致内存溢出、数据库崩溃任务跑一整天的天坑。这不仅是考在 java 基础的 api 使用,更是一场对大数据的处理能力、工程化思维和系统稳定性设计的一个综合考验。今天我带你从零去 构建一个能扛住百万数据,保证数据一次性的企业级 excel 导入方案。对于这个问题,我们面临的挑战有五个,第一,如果把整个 excel 文件加载到内存,一个百万行的 excel 文件轻松占用数百兆甚至是上 g b 的 一个内存,直接可能会导致 o m 问题。 第二个,导入耗时的时间等于文件解析耗时,加上数据库写入耗时。如果是串行处理,单线成竹条插入,假设每秒插入一百条 一百万数据可能需要三个小时时间,而且数据连接池可能会被耗尽。如果你担心简历上的东西讲不出来,我已经把面试经常问到的一些技术站场景题都整理在两百万字的面试文档了,里面针对每个知识点都有很详细的解析思路,只要你是我的粉丝,留言六六六就可以打包带走。第三,导入过程中中途失败了怎么办? 是全部回滚还是部分回滚呢?如何保证不出现重复数据呢?第四个,如果 excel 数据本身的格式有问题,比如说日期格式错误、字符串超长、业务逻辑处理冲突等等,那程序如何优雅地去处理这些非直接的崩溃呢?第五个,导入任务动辄运行几十 分钟,用户无法感知到进度,也无法在失败之后快速定位问题,所以针对这些问题,我们需要全方位的考虑和解决。第一个工具选型,可以用 excel 使用简单,内容控制更稳定,非常适合海量数据。第二个,把文件分编,比如按照 shift 或者按域读的行范围,由不同的县城并行 解析。然后呢,使用生产者和消费者的模型解析县城,作为生产者,把数据快放入到有界组织队列,一组消费者的链子里,取出数据进行一个批量入库, 同时确保数据库连接池的最大连接数足以支撑你的并发写入限速,避免线层因为获取不到连接而产生等待。第三,导入任务应该提交到限层值,一步执行,立刻返回一个 test id 前端,在后续可以通过 test id 来去轮询后端获取导入进度成功或者失败的记录,这解决了用户体 验的问题。第四,对于超大数据量或者系统解偶的需求,可以将解析后的数据先发送到消息队列,然后由独立的消费者服务 来去消费入库,这样上传解析服务和入库服务完全隔离,抗压能力和可扩展性更强。最后,对于事物的处理,我们可以按照一个批次一个失误,失败以后回滚当前批次,并记录后续从事或者补偿。以上就是这个问题的分 析,我是麦关注,我每天收获一个加我硬核干货,如果内容对你有帮助,记得点赞收藏,我们下期再见!

好,我们来看一下文件操作基础。五、波异常已处理在写文件和提取文件时,异常就不是家常便饭,比如一期存在的文件不见了,或者程序对某个目录没有访问权限, 底层文件系统不支持某个功能,所以在写文件 i o 时处理异常绝对不能省略。 i o exception, 它是文件 i o 的 通用异常。 几乎所有访问文件系统都可能抛出 error exception, 我 们的代码呢,必须进行对它处理,或者会直接报错并中断处理程序。 try 位置 res 是 最推荐的写法,从 java s e 七,我们有了这种语法,它的好处呢,是不需要自己写。 final 编音器会自动帮你关闭资源,比如文件里有通道,让你的代码更简洁,不容易忘记关闭资源。我们来写个事例, 我们来编写如下代码,这个代码是创建一个文件,然后呢,便写入内容。我们通过 charset 来声明编版格式,然后声明你要写入的内容。通过 pass 点 get 声明文件的路径,如果不存在,则会自动创建。这里呢,我们通过 files 点 uplorer 来打开文件并输出流,根据指定的字母集创建带有一个 block writer 的 缓冲区。这里呢,我们通过 try with resource 语法进行一个包裹,然后通过 write 方法将字母进行写入。 这种方式呢,是用 try with resource 语法,这是一个简洁的方式,它可以自动地关闭资源。我们来看一下传统的写法 啊,这是传统的写法啊,必须要在 final 里手动地去关闭资源啊,如果忘记关程序,可能会占用文件不释放导致程序崩溃,看起来更麻烦,现在呢,基本上就只适用于老项目了。我们还可以使用更详细的一条信息来描述更具体的错误原因。 我们来便写如下代码,这里的 nonus file exception, 它是 file system exception 的 子类。该类呢,有一些很常用的方法,比如说 get file, 它指出出错的文件。 get reason 出错的原因。 get other file 是 否涉及到其的其他文件。 这样在我们程序运行时为指定具体的错误。最后我们来总结一下,在写任何 i o 代码时为指定具体的错误。最后我们来总结一下,在我们程序运行时为指定具体的错误。最后我们来总结一下,在我们程序运行时为指定具体的错误。 我需要更具体的错误,我们可以指定 fields system exception 之类。这样呢,在错误时就可以定位具体的位置。好,这是本章内容。

ok, 我 们回来接下来是我们的第五个章节,叫流程控制之判断结构。那么流程控制是干什么的呢?啊?就是流程,其实流程跟咱们日常生活当中啊,做任何事情都一样,都有流程是吧?程序啊,他也有,也不例外, 那你像这个程序当中的正常的顺序是什么样的?那比五点一,我们要讲的就顺序执行,从上到下,一般情况下从左到右, 我觉得这个应该都能理解,就像你平时写字似的,是吧?哎,从上到下,从头往上写啊,从左往右一行,然后再往下一行,再往下一行,那程序的运行正常情况下他的运行顺序也是这样的,那么针对于这个流程啊,如何去控制?那么在咱们这个开发语言当中啊,我们 有以下的一些控制手段啊,比如说流程控制的手段,分支啊,选择选择分支和循环三种不同的手段,那么选择使用的关键词,这里边需要你背几个单词了。 那如果说你感觉英语不好是吧?告诉你拿什么去记啊?咱们在记这个英文单词的时候,可能你这个拿手写是吧?有联想记忆法啊,或者是什么的哈,有一些学过记忆法则的,用联想记忆法,或者说有些用什么 这单词密码之类,这个完全不需要。这咱们学什么的?编程,编程用什么键盘对吧?有一种这个叫最 ok 啊,就地球上哈,你找不到更好的这种记忆方式了,就叫做肌肉记忆,一旦你记住了,你一辈子你都忘不了啊,肌肉记忆,你想想 有很多人玩魔方对吧?他玩魔方的时候他是拿脑子去想吗?我这个往左拧往右拧对吧?不是那些手速特别快的,一般情况下在看到某一个情况的时候就是,哎,他手就支配了他这个这个肌肉的记忆啊,去完成这个魔方的还原了。你包括我自己玩魔方的时候,也是 在我这个用一套这个,比如说公式或者是那快速的手法去操作魔方的时候,如果中间被打断了,可能我就就需要重新开下魔方另另外一个公式,或者说连续的怎么样把它去完成啊?或者说你吃饭的时候,为什么你能够拿筷子很轻松的找到嘴呢?是吧?为什么你不塞到鼻子或者眼睛里去呢? 其实这个都是肌肉帮我们来完成的记忆,那所以说像这些单词给你个最好的办法,比如 if 你 要记不住是吧?那你就敲一百遍, else 记不住,敲二百遍是吧? space 记不住对吧?敲三百遍,以此类推啊,我就不信你记不住,而且像这些关键词在未来也会经常的用到啊,那你也不用,我就是说我们特意的去背 啊, if, else, space, case, default 啊,然后这叫 break, 那 当然了,我的这个发音,我英文也是个二把刀啊,不要觉得这个 是吧,兵哥英文有多好,其实我也是二把刀,只不过就是这个开发当中的这些单词啊,已经用了十几年了,那所以很熟悉, 要 well 就 叫 do well 啊,都是表示循环的。 for 表示循环, continue 表示继续 return 啊,表示结束并返回啊,这是它们的这个具体含义,未来使用到哪个,然后咱们再具体的说,那。

继续,我们刚才玩了这个叫三元算符,接下来这个叫微元算符,实际上这个在这加两个字啊,叫了解。那什么叫了解?为什么就了解就行呢?因为这个微元算符他比较偏底层了 啊,实际上有很多,这个在讲扎娃课程的时候,可能微元算符都会跨过,压根就不讲了,可能会觉得比较难或者怎么样,因为什么呢?他要基于二进制 来进行相样的预算,那就我说的是了解,其实如果说你要是觉得怕难的话,你可以把它跨过啊,也不太影响咱们未来的开发。那以后真的用,到时候咱们也可以通过乘法计算器啊,帮我们解决相样的问题。 那这个是我们来一个 demo 啊,这叫 demo 零五,这叫未运算符,叫未运算符。这个未运算符一共有几个呢?其实一共就六个,倒也不多啊,分别是,这个叫 安慰语啊,然后呢?安慰或然后呢?这个叫安慰异,或,然后呢?这个叫安慰取反,然后呢?这个叫左移,然后呢?这个叫右移啊?一共就六个。那他们的运算规则咱们一个一个来,这个叫安慰语 啊,安慰语。那我们写一个安慰语,我们来个最简单的,那比如说我们说五,然后安慰语上啊,安慰语上六, 五,安慰语上语呢?安慰语上六,他得到的结果是几呢? 我们加上啊一个括号,这叫五,安慰语上六,那其实他得到的结果哈,我们正常算一下。那我们先说他的运算规则啊,安慰语是什么呢?叫我给你总结一个口诀啊,同一啊,为一否为零,其实这个跟那个什么 呃,叫逻辑算符啊,其实差不多啊,他的这个规则我们可以看一下,我在这里面给你一画,你也就明白了。那比如说啊,这个叫我们使用啊,当前的这个五 五对应的这个二阶制呢?是多少呢?是幺零幺六呢?他对应的二阶制是幺幺零啊,这咱就口算一下就行了啊,不信的话拿乘法计算器去算啊,按位操作, 上下对齐啊,同一为一。看了,这是一,这俩是不不一样,不都是一吧?零,这是不也不都是一啊?是零。那他对那二斤的结果呢?就是一零零。那一零零是多少呢?一二四,那结果就是四。所以说啊,在当前的这个遇上结果,我们得到的结果是几呢?就是四, 我们就先看一下那五安慰与上六得到的结果是四。那我把它 ctrl 第一份拿下来,把它变成安慰货。 那其实聪明的你啊,如果说啊,这个之前的那个逻辑算数要没问题的话,这个应该能猜到七七八八的,这个结果应该是几?应该是七啊,这个叫安慰货。这叫什么呢?有一则一啊, 有一则一,将无为零。那我们还是啊,按刚才的这个逻辑,我们来算一下啊,我们来算一下,比如说 啊,看看还是这个当前的这个效果啊,我们这个不要了,把它变成安慰货的这种运算。那这个是什么?我们往上算了啊?有没有一啊?有一,有没有一啊?有一,有没有一啊?有一。那三个一是多少呢?就是七啊?一加二加四结果就是七 啊,一加二加四就就是七,所以说我们运算看一下结果,对吧?读到结果是不是七没问题。那这个呢,叫暗位异货啊,他叫暗位异货。暗位异货的这个口诀是什么呢?叫做,嗯,叫相同为零, 然后不同为一啊,我这个我也是加一个逗号的, 相同为零,不同为一。那我们现在把它 ctrl 加 d, 我 拿来一份,把当前的这个运算符,我们改成这个尖,尖是六上面的那个符号啊,六数字六上面那符号,那他得到的结果是多少呢?其实应该是三,我们来看一下运行啊,是不是这个结果呢? 啊?我没算错的话,应该是三。哈,那这个结果我们是怎么得到的?哈,来,再来,同样还是刚才的这个写法哈, 幺零幺和幺幺零,对吧?我们做异货运算,相同为零,一和一相同不同为一,不同为一。那最后是一加上二,结果等于多少?等于三就是你看到的结果。 其实呢,这个异货啊,也有人把它叫做不进位的加法,什么意思啊?你看啊,就是一加零是不一,零加一是不也是一?一加一等于二是不应该进一写零呢?但是不进一直接写零, 对吧?就变成零幺幺了,就不记位的加法。其实你要是简单点说,就相同为零,不同为一啊,这是他的运算。 其实我们之前记不记得我们写过一个,那个叫啊交换两个变量的值,我们如何通过这个不声明第三个变量的方式,然后我们去交换两个变量的值,我其中就 a, 一 或等于 b, b 或等于 a, a 再一或等于 b, 记得吧,当时我的写法啊, 就是这个写法很非主流的啊,比如说 a 啊,等于五对吧?然后呢, b 呢?等于六对吧?我也想交换他们两个的值,那他们两个我怎么交换呢? a 或等于 b, 那 我这个就是怎么样 啊?这个地方,这个叫一零一,这个是一一零,我把他们两个 e 或的结果给 a 重新赋值啊,让 a 等于它, a 等于什么呢?相同为零,不同为一,不同为一。那 a 现在等于是零幺幺,咱不用管了。 然后呢,再做一次预算,那这个是 a 跟 b 再做一次异或预算,然后呢,给 b 进行赋值 啊,赋值复出来的结果是什么呢?对吧?不同,相同,不同,那是不幺。零幺幺,零幺是几?是不就是五?现在 b 的 时候就是五了,然后呢,再让他们两个做一次预算,然后再给 a 重新赋值, 那现在再看啊,零幺幺,对吧?得到结果呢?这是几?是不六?你看现在是不? b 的 值等于五了, a 的 值是不等于六了,看了吗?那原来 a 的 值是五, b 的 值六是不是实现了交换?那这个是一种啊,就是底层的交换算法啊,相对来说比较底层啊,你想不到是正常的 啊,有很多这个,比如说在做这个外部领域开发的啊,那可能做了多少年了,他也不见得能想到这种算法啊。未来如果说真的,你要是 感兴趣这种什么 c 源了,或者说底层的数学与算法这类的东西,在你真正能靠这个啊,编程吃饭能赚钱的时候,你真的有兴趣啊,你可以深入的往底层去研究,对于我们现在来讲,我们看现象就可以啊,底层的这些比如原理之类的东西,那是你未来需要深造的内容啊,老习也说过,对吧?兵哥以前就捣鼓过这个问题, 先出效果,再研究理论都来得及,现在我们是看效果的时候啊,先把代码抄熟,比什么都强。那, 那我这个还是把它关了。那这个是易货啊,易货取反是什么意思呢?叫安慰取反,其实安慰取反就是一变零,零变一,你知道吗?那你比如说啊,我这个安慰取反啊,就是 这叫安慰啊,叫取反,安慰取反,他的这个运算叫一,然后变零啊,叫变 零,然后呢?零变一,那这个我们正常输出啊,你比如说我按位取反一个,我这么写啊,这么写我按位 啊,安慰取反一个,安慰取反一个,谁呢?安慰取反一个,这个这个这个五吧啊?安慰取反一个五啊,我让他等于等于我们这个就加上,这叫安慰取反一个五啊,然后我们现在在运行,我们看到的这个结果是什么啊? 叫做负六啊,叫做负六,从二进至底层来讲啊,叫一变零,零变一,但是我们从数学的角度来讲,怎么的叫做加一啊?然后天赋号, 明白吗?加一天号,什么五加一?五加一是几啊?是六六天符号是不就是负六,明白吗?我们再尝试一个看看是不是这个原理啊?我来一个九啊,我来一个九, 那就多少?是不是就负十啊?对吧?就负十啊,我们来一个复数看看行不行啊?你比如说我来一个这个叫啊负七,那我们来一个负七,然后呢?我们把负七按位取反啊,得到结果我们来看一下 多少呢?是不是正六啊?那怎么是正六呢?负七加一是不是等于负六,对吧?负六,负负六,前面我再加一个符号,负负得正,是不就变成正六了? 记着啊,这是按内取反的运算结果啊,叫加一天符号。嗯,底层二进制的这个结果呢,实际上他就是 一变零,零变一啊,就是一变零、零变一,因为这个玩意他是怎么算的?我们拿一个最简单的这个,嗯,底层原理啊,其实我也不爱讲这个东西啊,但是就既然说到了啊,你们肯定也会好奇,你要好奇呢,那我就给你嘚鼓嘚鼓。 你比如说我们现在这个是一个拜特类型,拜特类型是一个字节,一个字节呢是八个二分之 一啊,一个字节是八个二分之一,八个二分之一,我们画出来就是一一一一,然后呢一这几个了,六个、七个,是吧?零,这是他的最大曲值范围是多少?这是一百二十七,那这个比如说我们现在说这个五啊,五的这个表现形式是多少?这是一百二十七。那这个比如说我们现在说这个五啊,五的这个表现形式是一百二十七。那这个比如说我们现在说这个五啊,五的这个表现形式是一百二十七。那这个比如说我们现在说这个五啊,前面全是零, 看了吗?前面全是零,其中这七位啊,其中这七位表示的是数据位,最后一位表示的是这个叫什么?这一位呢?叫做符号位,如果是零呢?表示的是正数,如果是一呢?表示的是负数, 明白吗?那所以这个现在是五一变零,零变一,你看它变完的结果变成的是这样的, 明白吗?所以说首先这个数字变成了负数,因为最高的符号位变成了一,那所以说他变成了负数,那么现在在这种情况啊,我们就得这么看了,当前的这一位啊,是一,这一位是几啊? 那这一位呢?这一位是一二,这一位是四吧,是吧?一加四,现在这么看是不是五,对吧?针对于这个值,我们需要再加一,那得到的就是六,那负数写在前面,那就是负六, 然后所以说这个地方啊,我们看到最后得到结果就是负六,对吧?在这呢,那这个是底层,他算的时候啊,这个是二进制啊,这是二进制, 就简单的了解一下就行。我们平时啊,就想算的话,就是我们算数,怎么算呢?就是按照这种加一天炮形式数值,我们这么去算,但如果在底层单面积,像这种按这种按位取反的这种,这个微云算符, 我们算的时候可能是什么算一些什么跑马灯或者什么东西啊,就像很多时候我们在屏幕上这个,这个叫马路上啊,看那种霓虹灯, 然后凝红灯,应该都见过吧?啊?就是一个炮一个炮的,对吧?一个炮一个炮,然后你看他就是一会这个亮,他这亮的顺序好像是这么从这侧走上,这侧这么亮, 其实这个亮的过程就是他们交替才亮,知道吗?这个是亮,这个是灭,这个是亮,这个是灭啊,这个是亮,这个是灭,通过循环的手段啊,让他不断的做取反操作,是吧?就是他亮的时候他灭啊,然后呢?他灭的时候他就亮, 对吧?就是这种感觉,让咱们交替的闪烁,最后你能看到的这个效果,就是啊,就好像这个灯从这边一直往那边走似的,但你会发现你闭眼睛哈,你晃一晃,一会回来再看,你可能还会觉得往回走 啊,就像是我们看汽车轮子一样,记得往前转,有时候你看他是反转,其实差不多一个道理啊,那是视觉给我们的这种冲击,但是呢,我们对于这个底层数据来讲啊,其实就是在循环的做这种按微取反的操作啊,就说这个有点说远了,这是有点太底层东西了,咱们扎网这块基本上用不到这种预算关系。 那么这个叫左移啊,左移是什么意思呢?那,那这个是。 哎,我们可以这样的,你比如说我们现在还是 s alt 哈,我们左移,比如说写一个三十七啊,我们就写个三十七,向左移,向左移几位呢?向左移两位,他等于几呢?我们加上啊,来,我们来写个三十七,然后向左移两位, 其实实际上哈,这个向左移怎么个移法?我们给他画出来啊,我给你画出来 当前这个三十七啊,这个换算成二零四,就是幺零零啊,这么写啊,粗点啊,这个,这个不要了,这个是幺零零啊,幺零幺啊,这也是口算的结果啊,然后呢,把当前的这个这个结果,不信你就拿那个计算器算去,无所谓啊 啊,就你要做底乘,做时间长的,你这都能口算出来?把这个数向左移两位,什么意思呢?相当于在后面填两个零,知道吗? 啊?那每添一个零,这个数相当于扩大了二倍,我添两个零,相当于扩大了四倍,那实际上这个叫向左移两位,那就是三十七乘以四,那这个是四七二十八,三四一十二,一百四十八,如果我没算错的话 啊,你说我算错没运行啊?是不是?一百四十八,那没问题,对吧?这是一百四十八啊,得到结果,那我们这么说啊,这个就是向左移的话,相当于每向左移 左移动一位,相当于扩大二倍,你知道吧?二倍,每向左移一位,相当于扩大二倍。每啊,每向左移移一位,那这个你猜呢?每向右移动一位,相当于怎么的? 小于二倍,是不是除以二啊,对吧?缩小二倍 啊,然后还还得怎么的?取整?括号会加一个叫取整啊,计算机里边二阶式它不支持小数的匀算啊,那我现在我把这个同样来一份, 那这个是把它变成向右移啊,把它变成向右移,然后呢?把它也变成啊向右移, 那我们这个运算的结果应该是多少呢?我们也是啊,那二人制我们来画一遍啊,画一遍的结果最准多少呢?幺零零啊,幺零幺向右移,那比如说他要是一个内存的话啊,如果说这侧是边界向右移,那边界就变到这了,这两位相当于扔了就没有了, 那没有了,咱看没幺零零啊,这个幺零零幺是多少呢?就是九幺零幺九,这位是一二四八。一加八等于几啊?等于九,那就多少。九就是九。那我们这么算啊,三十七啊,比如除以二, 除以二等于多少是不等于十八于一,一,不要了,知道吧?不要余数。那十八再除以二等于几呢?是不是等于九啊?没有余数,正常是零,那所以得到就是九,他俩是不是一样的?没问题吧?那我们最后啊,把九放到这运行一下,看看是不是我想要的结果 啊,最后得到是九以上的这些就是微余算数,一共有六个。那这个我可以很负责任说啊,这个张华程序员对于 微运算符的要求不高啊,甚至有很多的渣运算符不说,甚至有很多的渣运算符他可能连微运算符都不会用,我说的一点不夸张,但是如果啊,通过这次课,你要是看明白了,或者说你知道他怎么算了 啊,我觉得也算是一个不小的收获,那未来在这个你深入研究底层算法的时候,这些微运算符他应该会帮得上你。

面试官问你, a 证的长短期记忆在生产环境里到底怎么落地实战?这是一道让百分之九十后人都会翻车的面试题。如果你张口就答,短期记忆靠滑动窗口上下文,长期记忆用下量库,那面试官多半已经在心里给你打叉了。 因为这种理论派的标准答案,在千万级并发症的生产环境里,纯粹是给自己挖坑。这坑有多深?咱看看这些纸上谈兵的惨痛代价。 用户报了个手机号,是幺三八开头,系统给错误召回成幺三九,这就叫事实扭曲。 再比如,用户第一轮明确说对花生过敏,聊到第五十一轮,系统把这事给忘了, 反手推荐个海鲜花生拼盘,这种致命遗忘,你敢直接上生产线吗?为什么会犯这种低级错误?因为大家都上了第一个伪命题的当。滑动窗口, 大家看这把剪刀, f i f o, 先进先出,他如果只顾着局部连贯,就会无情斩断用户的核心状态。像林冲花生过敏这种开局的核心信息, 聊着聊着就被剪掉扔进垃圾桶了。那大厂是怎么抢救失忆症的?大家看这儿,用 res 做双层动态缓存,第一层高频对话流, 保留最近五到十轮原始对话,负责聊天不冷场。最核心的是第二层动态状态机 stat, 咱们用轻量模型实时抽取关键事实, 像钉子一样钉死在 system prompt 里。只要绘画不断,核心记忆永远不丢,短期记忆保住了,咱们再看长期的坑。第二个伪命题来了,千万别迷信项链库,大家一定要记住,与意相似,绝不等于事实准确。 在姓名、手机号这种零容错场景下,纯靠模糊的项链解锁,那就是一场召回灾难。 既然单靠项链库不行,那大厂的长期记忆底线是什么?本视频的代码笔记,我放在这份两百万字 java 与 ai 模型学习笔记里了,里面包含 jvm、 reddit、 mq、 微服务、 ai 模型等三十多个技术站与一百多个项目场景实战笔记,还有不同工作年限同学的简历模板,以及一份 java 加 ai 的 三十天面试突击学习路线。需要的话再直接拿去 看。这座易购混合存储金字塔,塔尖必须是 mycircle 或 mongod db 存强势式标签要求零容错,腰部是 elastic search, 用 bm 二五算法做长文本的精确召回。最底层才是 vector db 向量库, 它只配做模糊语义的发散补充,强势时必须依赖精准读写下,量库绝不是核心塔建好了,咱们得拉开工业级引擎盖儿,看看真实的数据流转。第一波同步组装, 用户请求进来, readme recycle e s 并发去查主链路 r t 必须严格控制在五百毫秒以内。这时候大模型只是个没有感情的计算 cpu, 真正的记忆过滤全是成熟的存储组件干的。组装完,大模型给出回复了,那新记忆怎么存?第二波异步落盘,为了保障接口高可用,主链路必须零等待。我们把事件投递进 m q 消息队列, 走旁路去做意图分类和易购路由更新。把更新扔给 m q 之后,这步非常关键,千万别做算力刺客,如果让大模型定时定量去无脑总结,算力成本会直接爆炸。优 雅的解法是引入清量意图识别,只有当发现状态变更或新事实时,才事件驱动出发落盘,做到资源极简。 为了让这套架构坚不可摧,生产环境还会加上四大进阶防御。引入图数据库解决多实体关联,用标量前置过滤,极大降低召回错误,配合实时加离线,双链路覆盖隐性偏好。最后用严格的分级, t t l 控制成本 解锁和性能都优化到极致了。但在千万级病发下,还一个终极拷问等着你, 如果 ai 状态不一致怎么办?假设 m q 积压了,长期记忆没落盘,用户又提问了大模型读到了旧数据,产生致命幻觉,这就是经典的脏毒危机。别慌,咱们用分布式架构的四层兼顾防线来兜底, 第一, session 内状态永久优先。第二, res 双写临时兜底。第三,核心数据切分走同步入库。第四,套用版本号乐观锁加标记重试,彻底闭环兜底完成,闭环形成。到这里,咱们复盘一下,退去 ai 的 这层滤镜, 你看透它的系统本质了吗? a 阵的记忆本质上就是多级缓存加一勾,同步加读写分离加事件驱动 进位,数据一致性。记住,大模型只是个计算节点,绝不是万能存储。这张图是工业级 agent 架构的全景阵型, 建议直接截图保存,从打破误区到一致性兜底,以图甚千言。我是 fox, 专注真实的硬核实战坑,觉得有用的兄弟点赞关注,咱们下期见!