00:00
好,那么我们就简单来说一下这个它的底层的一种实现啊,这呢又是偏向这个源码级别的了,这个内存的一个解析啊,不是这个源码分析,首先咱们先多说一句这个string啊,String如果我们通过这样的方式造了一个对象,它底层呢,实际上是帮我们用了一个差型数组,这个长度呢就是零,好,这个呢很容易说啊,接着呢,我再去声明一个string类型的叫ABC。看诶这样啊,起了个ABC,那它底层呢,其实帮我们做了一个这样的事,哎,对。这个我就最终的结果呢,它呈现出来的就是哎,有一个数组,这个底层的这个差异数组呢,有三个元素。三个元素呢,就是ABC,长度就是三,这是对于咱们说的这个死锥来讲的。
01:01
现在呢,我们来说这个,嗯,我以这个string buffer来说了,String buffer和string builder呢,它俩都可变啊,区别呢,你看他们的副类都一样,就是你这个子类它这是同步的啊,所以质量我们说谁其实都一样,它们的结构,这个内存的这个结构呢是相同的,那我这呢写一个,哎,总是感觉怪怪的啊,哎,这个string的一个buffer,首先我也跟你一样,我也用过空残的。啊,你要是用空散呢,你呢,底层是一个差应数组,数组的length是零。那我呢?看一下。String f找他的CR空参构造器,就这个呗,大家看这个呢,叫capacity。容量是吧,你看这个呃注解啊呃注解这个注释说呢,创建一个string的一个buff,没有任何的字符在里边,因为你这不是空的嘛,啊没有任何在里边,And说有一个初始化的容量是16个字符。
02:06
相当于呢,我们创建了一个长度为16的叉型数组,这个你要看的还不清楚,点super再过来,这不就看到了吗?给了谁给了我们这个Y6 Y6不就是我们那会儿说到这个负类中的这个Y6吗?好,这呢我们就知道是怎么回事了啊,这个情况下呢,相当于我们是拗了一个,哎叉,这个呢,因为你没有负具体的数据了,我们这呢,写的是个16,相当于我们底层诶创建了一个。长度是16的速度。那我创建这个速度以后,咱们呢,一会呢,讲具体它的添加方法啊,它呢叫做end的方法,在这个基础之上,比如我们添加了一个词符A。这个呢,就相当于咱们这个,呃,这个数字它是叫Y6啊,我这样把它标识出来吧,这样子啊,上面的也一样。
03:06
哎,这是它的底层结构啊,那么对于我们这个string buffer来讲,我现在往里边去添加一个字符A,相当于呢,咱们就把这个value呢,首位置呢,哎,就给它复制为一个A了。啊,那类似的,你这个后边呢在判,哎,我这呢给一个B。相当于呢,就是我们把它的这个第二个元素复制为一个B,那这时候呢,你就发现了,我们都是用的同一个底层的数组,这个数组没变,我们这个对象呢也没有变。啊,这个没变,其实反馈的叫可变哈,这个你别蒙了,这个我们这个结构没有变,呃,正说明了就是里边呢,你这个数组呢,我们呃就是它的呃数据啊,都可以都往他这去添加是吧,不像原来一样呢,你这个就是死的,要想改就得重新造,我们叫不可变,这呢,我们还可以在线的里边去加,所以叫可变啊这就是这样的一个情况,能理解吧。
04:07
看正解啊好,这是我们说的这个事,那如果说我们建一个你看啊,String b for sb2又一个string,诶八分在这里边呢,比如我写上了一个这个叫ABC啊,那这个呢,又长什么样子,还是看构造器,嗯,那相当于咱们ctrl o,我掉的呢就是这个了呗。掉这个,我这呢写一个ABCA这呢是一个CAC的长度,额外呢,哎,又加了16了。那额外呢,这就相当于它呢,是这个这个19了,成了是吧?哎,成了这个19了,那这个你创建完这个数组以后呢,你是不是还把我们这个字符串呢,你得给我加进去啊。哎,就这么着了,哎,那这呢就很清楚了,像这种情况呢,咱们相当于底层呢,我造了一个呃叉型数组,嗯,这个长度呢,咱们是这个这个相当于是ABC,它点LAS啊对,加了一个这个16。
05:12
哎,就是这么个情况,这是它的一个底层。哎,它的一个底层情况好,那么这个你就知道啊,相当于每次我们造完的时候呢,都会给你额外的空出来16个啊空16个,嗯,先说第一个小问题。哎,问题一什么呢,我这时候呢,我去输出一下。Print LA啊,我输出谁呢?那咱们现在在SB2这块,我就输出SB2.les吧,它呢也有一个方法叫做长度,问大家你说这个长度是几呢?三十六十九。那我要是打印这个SB1SB1我我不在这打啊,我在这个位置打。
06:12
SB一点是多少啊?16对吧。对吗?不对吧,长度多少?哎,大家一定会出错的啊,这个都讲了这么多遍了,那肯定知道大家这块会出错是吧?嗯,SBR等于new一个408分,我这样写的,我现在呢,S out一下SB2.2nice。哎呦,点单词是吧。点是16,哎,凡是说16呢,就说明你懂太多了,知道的太多了是吧,太多了导致你出错了就零啊,你就单纯一点什么也没有嘛,什么也没有就零呗。
07:11
这不就是咱们原来讲项目的时候说个什么事呢,我们造了一个数组,长度是五啊,说那个组建开发团队吧,就是你这个团队最多五个人,然后实际上呢,咱们就俩人。那你说我这时候呢,想输出长度,我这时候关心的不是你这个五,我想关心你到底里边填了几个啊,所以这个LS呢,它返回的是咱们相当于原来那个total是吧?啊,那这个lengths你看你点开它返回的是count count呢就是你a pen的一个字符,我就countt加一个一,相当于就是咱们的total,这可不是Y6.lengths啊。你要Y6.la是不是才是16啊,哎,对啊行,这个呢就明白了,所以这块呢输出的,因为你就ABC3嘛,他反过来是三啊,这个呢是大家一定很多同学是这个容易出错的啊,写16呢还特别的高兴啊,说你看很多人都不知道底层是造了长度是16的是吧?嗯,知道了反而错了啊嗯,这是第一个问题,那么问题二问题二大家想这个事,我现在呢往里边去添加,你这呢长度是16,我加加加加加加加加着加着好像就快到16了,只要你没到呢,其实就没事,那假设呢,万一现在我加了16个呢,我又来了一个openend的。
08:41
不够了是吧,这点是不是就涉及到这种扩容的问题了,就是我现在呢,又往里边加了不够了,那这时候怎么办呢?啊这呢就涉及到说如果啊,要添加的数据啊,底层数组啊,这个盛不下了。
09:04
乘不下来啊,那就需要扩容这个底层的数组,扩容底层的这个数组啊,那么具体这个扩容的方式,这个你说我说都不算,那就看源码,源码怎么写的,那人家就是怎么扩的,那咱们找从哪儿去找呢?咱们就还是以死林八为例,你你上哪去看它底层怎么扩展,对,找个判断方法呗,你往里边加的时候呢,其实你就应该每次加的时候都得考虑一下是不是会超是吧?嗯,咱们找一个比较方便看的,比如这个string吧。我往里边呢,加了一个新的字符串,呃,每次加的时候呢,呃,这个就不用管了啊,哎,我们呢,就掉了负类里边这个openend的方法了,你看这个string barer跟string builder呢,它俩其实都是调到负类的,只是说呢,我们在string barer这块呢,我给你包了一下,让你是一个同步的了。
10:05
哎,这样啊,点一下过来了,嗯,过来以后啊,大家看这块啊,这是一个你要添加的这个字符串,先看一下你是不是空啊,你要空的话呢,我得这么着处理,就避免了一些控制帧是吧,你要不空是不是才走到这儿啊。诶,你要不空走到这儿,我呢先获取一下你要添加的这个字符串,它的一个长度,获取它的长度以后,不能马上来说,我就开始给你去做添加了啊,我呢得先叫inure capacity eternal,确保一下我这个容量呢是够的。就好比咱们比如说现在呢,你已经假设啊,底层已经存了15个了,我现在呢又想存一个ABC,那这个时候呢,你就相当于是不是这个是三啊,这个抗呢,就是咱们刚才说的已经存了几个了,这是15个,你现在又想存三个,那就15加三等于18,好,这个呢,你就定到这个方法里边。
11:05
这呢,这不就是15加三吗?哎,这呢,我们去判断一下啊,说这个15加三呢,这是18,而我们这个Y6底层数组呢,目前没有扩容过,就是十六十八减16是不是大于零了,你要大于零说明呢,是不是不够了,那不够呢就得考虑扩容了,那你要是这个小于零,小于零那就不用再去调这个衣服里边的逻辑了,说明你这个还够啊,哎,只要大于零说明就不够了,不够怎么办呢?你看这有一个。哎,这个这个有个这个叫copy是吧,Copy这里边涉及到它这个copy,你你这这点就别点它了哈,这不对了,它这是我们A瑞里边,呃,把你这个呃原有的数据呢,Copy到一个新的里边了,你主要看你得看这。整体呢,这不是作为我们这个copy方法的一个参数二了啊,看这这个去new一个新的capacity,把你这个15加三这个数呢放进去,点进来在这儿在这儿,这是呢,我们原有的这个16向左移位哇,终于见到一个微运算符了,咱们以前说过,说大家在这个源码中可能会看到是吧,这就是啊,向左移位对你得知道啥意思啊。
12:19
什么意思啊,差200,哎,这个呢,比你写乘以二呢效率高。所以呢,它是用左一这个位运算符呢,就是凡是用位运算的伏的地方呢,通常就是向左移右移这种啊效率高,嗯,我呢向左一位相当于乘以二了,嗯,它它又给加了个二是吧?啊又加了个二啊就这就相当于看到我们所说的扩容的方式了,那就是扩容为原来的一倍,再加个二。呃,应该说扩容为原来的二倍是吧。扩容为原来一倍,相当于乘以一了啊,你增加了一倍啊,扩容为二倍是吧?哎,那扩容完以后把它呢,就作为我们的一个新的一个capacity派,哎,新的完了以后呢,这个呢,就像咱们刚才看到你回去啊,回去再把我们原有的这个里边的数据呢,再给我copy进去,得到咱们这个新的value是这个意思啊,你会发现后边呢,好像还有一些其他的逻辑哈。
13:14
那这个逻辑呢,是因为还会有些特殊情况,比如说呢,你后边要新加的这个字符串呢,特别特别长,我呢乘以二以后呢,再加个二发现还不够是吧,就是你这个新的这个呢,发现的还不满足,你现在要新加的这个子串,那干脆我就拿你当我这个新的容量吧。这个也能理解是吧,啊,这是一种情况,还有一种情况呢,就是我这个往左移乘二,咱们不是也说过这有一个范围是吧,超这个范围的话呢,可能就变成负的了,这不也会有这种情况吗。哎,还有其他情况,这个我就不说那么细了啊,这个大家下来完全可以自己去理解,哎这块呢,我们就说,哎整体来看回过来啊,那就需要扩容底层的数组,那我们说默认情况下,这个扩容为原来容量的二倍,哎,再加上一个二这样个长度,那同时呢。
14:11
哎,这个逗号啊,同时将原有数组啊中的这个元素啊,我们要复制给,哎,复制到。哎,新的这个数组中,哎,就是原来你这个长度是16不够了,16乘以二三十二,它变成34了,把原来你这个数据呢,再一个一个的我们给它装过来,这就涉及到数组的复制了,装过来以后这个呢就接着往后存,哎,再不过了,再去扩容。哎,就这样子啊,那这呢,就是我们说到这个叫扩容的一个情况,还有那些特殊情况啊,特殊情况呢,我就不说了,这个是一个默认的一个情况,呃,那么我们讲这个意义是什么呢?奖励意义呢,就是大家知道它底层呢要怎么去设计啊,呃,这个当然功利点讲呢,就面试的时候,这是一道高频的面试题。
15:02
啊,如果呢,你仅仅说这几句话呢,大家都会说,也凸显不出来你跟别人不一样是吧,你是不是得再来点深的东西啊,啊就得说说这些内容啊,这是功利点讲啊,那另外来说的话呢,我们讲它的意义就在于,如果咱们开发当中,大家呢,哎,确实呢,需要对一个字符串频繁的进行修改,那此时呢,你尽可能的时候不要选词string。此令不可变,它的效率其实最差。每次都得新造一个对吧?哎,你像RI barber和RI builder,好歹呢,它是不是在原有的基础上还能够用了一些,哎,不够用了,它才扩容的采取新造了啊,所以我们会优先用s string buffer和builder,再根据这个是否线程安全的问题呢,你去挑它俩用谁啊,那它俩呢也有讲究,我们讲刚才这个源码的意义就是如果大家确定咱们呢,比如说后边呢,咱们造完这个string buffer或以后,后边呢,可能不停的会去调一些所谓的openend的方法啊,Openend openend openend,而且你也大概知道可能会有openend多少次。
16:05
那很有可能,比如说你超过16次了。那就意味着你超出了他给你最开始空闲的这16个了啊,那你就要扩容,咱们尽可能的避免让他扩容,因为扩容毕竟扩容一次呢,这个还得复制一次,这个呢会影响我们的效率,所以呢,给我们的一个指导意义就是在开发当中,哎建议大家哎使用这个哪个构造器呢,我们这个砖啊。诶4BUFFER这个在这,那我还得回到这儿来看,我们out它,诶ctrl o一下看它这个构造器,它有一个这个构造器。就是指定一个容量,这个容量呢,就是你底层数组的容量。所以说呢,呃,比如说你呃默认是16了,咱们是不是尽可能的,你要知道后边open喷的次数还挺多的,为了避免它的扩容,那你一开始用的时候呢,是不是就使用这种带参数的构造器啊,哎,造一个长度,比如说三十四十,避免它后边再去扩容,避免再去复制,这样的话呢,效率高一些啊。
17:14
哎,建议呢,使用它,对,那对应的呢,那就是string这个build了,关键的你就看到底是安不安全啊,决定选谁。哎,String builder它行,这呢,咱们就把关于呃,String buffer string builder,包括string它们三者的对比,呃,以及呢,他们其实呢,相当于二者就都讲了啊,哎,他们的底层的这个源码磁轮build呢,也是这样扩容的,哎,也是这样扩容的啊。
我来说两句