2024年安卓最全Android内存优化神器——MAT入门使用,2024年最新10个面试问题及回答技巧

尾声

对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。 整理的这些架构技术希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。

最后想要拿高薪实现技术提升薪水得到质的飞跃。最快捷的方式,就是有人可以带着你一起分析,这样学习起来最为高效,所以为了大家能够顺利进阶中高级、架构师,我特地为大家准备了一套高手学习的源码和框架视频等精品Android架构师教程,保证你学了以后保证薪资上升一个台阶。

  • 思维脑图
  • 性能优化学习笔记


  • 性能优化视频

    当你有了学习线路,学习哪些内容,也知道以后的路怎么走了,理论看多了总要实践的。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

来向ListView提供每一个item所需要的view对象。初始时ListView会从BaseAdapter中根据当前的屏幕布局实例化一定数量的view对象,同时ListView会将这些view对象缓存起来。当向上滚动ListView时,原先位于最上面的list item的view对象会被回收,然后被用来构造新出现的最下面的list item。这个构造过程就是由getView()方法完成的,getView()的第二个形参 View convertView就是被缓存起来的list item的view对象(初始化时缓存中没有view对象则convertView是null)。

由此可以看出,如果我们不去使用convertView,而是每次都在getView()中重新实例化一个View对象的话,即浪费资源也浪费时间,也会使得内存占用越来越大。ListView回收list item的view对象的过程可以查看:android.widget.AbsListView.java —> void addScrapView(View scrap) 方法。

示例代码:

+
 <div class="inline-comment" style="width:350px; position:absolute; left:750px"></div><span class="line" style="">1</span><br style=""><span class="line" style="">2</span><br style=""><span class="line" style="">3</span><br style=""><span class="line" style="">4</span><br style=""><span class="line" style="">5</span><br style=""></pre></td><td class="code" style="padding:8px; line-height:20px; vertical-align:top; border-top-width:1px; border-top-style:solid; border-top-color:rgb(239,239,239); background-color:rgb(246,246,246)"><pre class="disqus" style="font-family:monospace,serif; font-size:0.8em; margin-top:1.6em; margin-bottom:1.6em; border:1px solid rgb(227,237,243); width:636px; padding:10px; overflow:auto; background:rgb(247,250,251)"><span class="ds-thread-count" id="comment28" style="">+</span>

 <div class="inline-comment" style="width:350px; position:absolute; left:750px"></div><span class="line" style=""><span class="function" style=""><span class="keyword" style="color:rgb(51,51,51); font-weight:bold">public</span> View <span class="title" style="color:rgb(153,0,0); font-weight:bold">getView</span><span class="params" style="">(<span class="keyword" style="color:rgb(51,51,51); font-weight:bold">int</span> position, View convertView, ViewGroup parent)</span> </span>{<!-- --></span><br style=""><span class="line" style=""> View view = <span class="keyword" style="color:rgb(51,51,51); font-weight:bold">new</span> Xxx(...);</span><br style=""><span class="line" style=""> ... ...</span><br style=""><span class="line" style=""> <span class="keyword" style="color:rgb(51,51,51); font-weight:bold">return</span> view;</span><br style=""><span class="line" style="">}</span><br style=""></pre></td></tr></tbody></table>

`

示例修正代码:

+
 <div class="inline-comment" style="width:350px; position:absolute; left:750px"></div><span class="line" style="">1</span><br style=""><span class="line" style="">2</span><br style=""><span class="line" style="">3</span><br style=""><span class="line" style="">4</span><br style=""><span class="line" style="">5</span><br style=""><span class="line" style="">6</span><br style=""><span class="line" style="">7</span><br style=""><span class="line" style="">8</span><br style=""><span class="line" style="">9</span><br style=""><span class="line" style="">10</span><br style=""><span class="line" style="">11</span><br style=""><span class="line" style="">12</span><br style=""></pre></td><td class="code" style="padding:8px; line-height:20px; vertical-align:top; border-top-width:1px; border-top-style:solid; border-top-color:rgb(239,239,239); background-color:rgb(246,246,246)"><pre class="disqus" style="font-family:monospace,serif; font-size:0.8em; margin-top:1.6em; margin-bottom:1.6em; border:1px solid rgb(227,237,243); width:629px; padding:10px; overflow:auto; background:rgb(247,250,251)"><span class="ds-thread-count" id="comment32" style="">+</span>

 <div class="inline-comment" style="width:350px; position:absolute; left:750px"></div><span class="line" style=""><span class="function" style=""><span class="keyword" style="color:rgb(51,51,51); font-weight:bold">public</span> View <span class="title" style="color:rgb(153,0,0); font-weight:bold">getView</span><span class="params" style="">(<span class="keyword" style="color:rgb(51,51,51); font-weight:bold">int</span> position, View convertView, ViewGroup parent)</span> </span>{<!-- --></span><br style=""><span class="line" style=""> View view = <span class="keyword" style="color:rgb(51,51,51); font-weight:bold">null</span>;</span><br style=""><span class="line" style=""> <span class="keyword" style="color:rgb(51,51,51); font-weight:bold">if</span> (convertView != <span class="keyword" style="color:rgb(51,51,51); font-weight:bold">null</span>) {<!-- --></span><br style=""><span class="line" style=""> view = convertView;</span><br style=""><span class="line" style=""> populate(view, getItem(position));</span><br style=""><span class="line" style=""> ...</span><br style=""><span class="line" style=""> } <span class="keyword" style="color:rgb(51,51,51); font-weight:bold">else</span> {<!-- --></span><br style=""><span class="line" style=""> view = <span class="keyword" style="color:rgb(51,51,51); font-weight:bold">new</span> Xxx(...);</span><br style=""><span class="line" style=""> ...</span><br style=""><span class="line" style=""> }</span><br style=""><span class="line" style=""> <span class="keyword" style="color:rgb(51,51,51); font-weight:bold">return</span> view;</span><br style=""><span class="line" style="">}</span><br style=""></pre></td></tr></tbody></table>

关于ListView的使用和优化,可以参考这两篇文章:

Bitmap对象不在使用时调用recycle()释放内存

描述:有时我们会手工的操作Bitmap对象,如果一个Bitmap对象比较占内存,当它不在被使用的时候,可以调用Bitmap.recycle()方法回收此对象的像素所占用的内存。

另外在最新版本的Android开发时,使用下面的方法也可以释放此Bitmap所占用的内存

+
 <div class="inline-comment" style="width:350px; position:absolute; left:750px"></div><span class="line" style="">1</span><br style=""><span class="line" style="">2</span><br style=""><span class="line" style="">3</span><br style=""><span class="line" style="">4</span><br style=""><span class="line" style="">5</span><br style=""></pre></td><td class="code" style="padding:8px; line-height:20px; vertical-align:top; border-top-width:1px; border-top-style:solid; border-top-color:rgb(239,239,239); background-color:rgb(246,246,246)"><pre class="disqus" style="font-family:monospace,serif; font-size:0.8em; margin-top:1.6em; margin-bottom:1.6em; border:1px solid rgb(227,237,243); width:557px; padding:10px; overflow:auto; background:rgb(247,250,251)"><span class="ds-thread-count" id="comment38" style="">+</span>

 <div class="inline-comment" style="width:350px; position:absolute; left:750px"></div><span class="line" style="">Bitmap bitmap ;</span><br style=""><span class="line" style=""> ...</span><br style=""><span class="line" style=""> bitmap初始化以及使用</span><br style=""><span class="line" style=""> ...</span><br style=""><span class="line" style="">bitmap = <span class="keyword" style="color:rgb(51,51,51); font-weight:bold">null</span>;</span><br style=""></pre></td></tr></tbody></table>
释放对象的引用

描述:这种情况描述起来比较麻烦,举两个例子进行说明。

示例A:

假设有如下操作

+
 <div class="inline-comment" style="width:350px; position:absolute; left:750px"></div><span class="line" style="">1</span><br style=""><span class="line" style="">2</span><br style=""><span class="line" style="">3</span><br style=""><span class="line" style="">4</span><br style=""><span class="line" style="">5</span><br style=""><span class="line" style="">6</span><br style=""><span class="line" style="">7</span><br style=""><span class="line" style="">8</span><br style=""><span class="line" style="">9</span><br style=""><span class="line" style="">10</span><br style=""><span class="line" style="">11</span><br style=""><span class="line" style="">12</span><br style=""><span class="line" style="">13</span><br style=""><span class="line" style="">14</span><br style=""><span class="line" style="">15</span><br style=""></pre></td><td class="code" style="padding:8px; line-height:20px; vertical-align:top; border-top-width:1px; border-top-style:solid; border-top-color:rgb(239,239,239); background-color:rgb(246,246,246)"><pre class="disqus" style="font-family:monospace,serif; font-size:0.8em; margin-top:1.6em; margin-bottom:1.6em; border:1px solid rgb(227,237,243); width:606px; padding:10px; overflow:auto; background:rgb(247,250,251)"><span class="ds-thread-count" id="comment43" style="">+</span>

 <div class="inline-comment" style="width:350px; position:absolute; left:750px"></div><span class="line" style=""><span class="keyword" style="color:rgb(51,51,51); font-weight:bold">public</span> <span class="class" style=""><span class="keyword" style="color:rgb(51,51,51); font-weight:bold">class</span> <span class="title" style="color:rgb(153,0,0); font-weight:bold">DemoActivity</span> <span class="keyword" style="color:rgb(51,51,51); font-weight:bold">extends</span> <span class="title" style="color:rgb(153,0,0); font-weight:bold">Activity</span> </span>{<!-- --></span><br style=""><span class="line" style="">	... ...</span><br style=""><span class="line" style="">	<span class="keyword" style="color:rgb(51,51,51); font-weight:bold">private</span> Handler mHandler = ...</span><br style=""><span class="line" style="">	<span class="keyword" style="color:rgb(51,51,51); font-weight:bold">private</span> Object obj;</span><br style=""><span class="line" style="">	<span class="function" style=""><span class="keyword" style="color:rgb(51,51,51); font-weight:bold">public</span> <span class="keyword" style="color:rgb(51,51,51); font-weight:bold">void</span> <span class="title" style="color:rgb(153,0,0); font-weight:bold">operation</span><span class="params" style="">()</span> </span>{<!-- --></span><br style=""><span class="line" style="">	 obj = initObj();</span><br style=""><span class="line" style="">	 ...</span><br style=""><span class="line" style="">	 [Mark]</span><br style=""><span class="line" style="">	 mHandler.post(<span class="keyword" style="color:rgb(51,51,51); font-weight:bold">new</span> Runnable() {<!-- --></span><br style=""><span class="line" style="">	        <span class="function" style=""><span class="keyword" style="color:rgb(51,51,51); font-weight:bold">public</span> <span class="keyword" style="color:rgb(51,51,51); font-weight:bold">void</span> <span class="title" style="color:rgb(153,0,0); font-weight:bold">run</span><span class="params" style="">()</span> </span>{<!-- --></span><br style=""><span class="line" style="">	         useObj(obj);</span><br style=""><span class="line" style="">	        }</span><br style=""><span class="line" style="">	 });</span><br style=""><span class="line" style="">	}</span><br style=""><span class="line" style="">}</span><br style=""></pre></td></tr></tbody></table>

我们有一个成员变量 obj,在operation()中我们希望能够将处理obj实例的操作post到某个线程的MessageQueue中。在以上的代码中,即便是mHandler所在的线程使用完了obj所引用的对象,但这个对象仍然不会被垃圾回收掉,因为DemoActivity.obj还保有这个对象的引用。所以如果在DemoActivity中不再使用这个对象了,可以在[Mark]的位置释放对象的引用,而代码可以修改为:

+
 <div class="inline-comment" style="width:350px; position:absolute; left:750px"></div><span class="line" style="">1</span><br style=""><span class="line" style="">2</span><br style=""><span class="line" style="">3</span><br style=""><span class="line" style="">4</span><br style=""><span class="line" style="">5</span><br style=""><span class="line" style="">6</span><br style=""><span class="line" style="">7</span><br style=""><span class="line" style="">8</span><br style=""><span class="line" style="">9</span><br style=""><span class="line" style="">10</span><br style=""><span class="line" style="">11</span><br style=""></pre></td><td class="code" style="padding:8px; line-height:20px; vertical-align:top; border-top-width:1px; border-top-style:solid; border-top-color:rgb(239,239,239); background-color:rgb(246,246,246)"><pre class="disqus" style="font-family:monospace,serif; font-size:0.8em; margin-top:1.6em; margin-bottom:1.6em; border:1px solid rgb(227,237,243); width:590px; padding:10px; overflow:auto; background:rgb(247,250,251)"><span class="ds-thread-count" id="comment47" style="">+</span>

 <div class="inline-comment" style="width:350px; position:absolute; left:750px"></div><span class="line" style=""><span class="function" style=""><span class="keyword" style="color:rgb(51,51,51); font-weight:bold">public</span> <span class="keyword" style="color:rgb(51,51,51); font-weight:bold">void</span> <span class="title" style="color:rgb(153,0,0); font-weight:bold">operation</span><span class="params" style="">()</span> </span>{<!-- --></span><br style=""><span class="line" style="">	obj = initObj();</span><br style=""><span class="line" style="">	...</span><br style=""><span class="line" style="">	<span class="keyword" style="color:rgb(51,51,51); font-weight:bold">final</span> Object o = obj;</span><br style=""><span class="line" style="">	obj = <span class="keyword" style="color:rgb(51,51,51); font-weight:bold">null</span>;</span><br style=""><span class="line" style="">	mHandler.post(<span class="keyword" style="color:rgb(51,51,51); font-weight:bold">new</span> Runnable() {<!-- --></span><br style=""><span class="line" style="">	    <span class="function" style=""><span class="keyword" style="color:rgb(51,51,51); font-weight:bold">public</span> <span class="keyword" style="color:rgb(51,51,51); font-weight:bold">void</span> <span class="title" style="color:rgb(153,0,0); font-weight:bold">run</span><span class="params" style="">()</span> </span>{<!-- --></span><br style=""><span class="line" style="">	        useObj(o);</span><br style=""><span class="line" style="">	    }</span><br style=""><span class="line" style="">	}</span><br style=""><span class="line" style="">}</span><br style=""></pre></td></tr></tbody></table>

示例B:

假设我们希望在锁屏界面(LockScreen)中,监听系统中的电话服务以获取一些信息(如信号强度等),则可以在LockScreen中定义一个PhoneStateListener的对象,同时将它注册到TelephonyManager服务中。对于LockScreen对象,当需要显示锁屏界面的时候就会创建一个LockScreen对象,而当锁屏界面消失的时候LockScreen对象就会被释放掉。

但是如果在释放LockScreen对象的时候忘记取消我们之前注册的PhoneStateListener对象,则会导致LockScreen无法被垃圾回收。如果不断的使锁屏界面显示和消失,则最终会由于大量的LockScreen对象没有办法被回收而引起OutOfMemory,使得system_process进程挂掉。

总之当一个生命周期较短的对象A,被一个生命周期较长的对象B保有其引用的情况下,在A的生命周期结束时,要在B中清除掉对A的引用。

其他

Android应用程序中最典型的需要注意释放资源的情况是在Activity的生命周期中,在onPause()、onStop()、onDestroy()方法中需要适当的释放资源的情况。由于此情况很基础,在此不详细说明,具体可以查看官方文档对Activity生命周期的介绍,以明确何时应该释放哪些资源。

另外一些其他的例子,将会在补充版本加入。

使用MAT进行内存调试


获取HPROF文件

HPROF文件是MAT能识别的文件,HPROF文件存储的是特定时间点,java进程的内存快照。有不同的格式来存储这些数据,总的来说包含了快照被触发时java对象和类在heap中的情况。由于快照只是一瞬间的事情,所以heap dump中无法包含一个对象在何时、何地(哪个方法中)被分配这样的信息。

这个文件可以使用DDMS导出:

    DDMS中在Devices上面有一排按钮,选择一个进程后(即在Devices下面列出的列表中选择你要调试的应用程序的包名),点击Dump HPROF file 按钮:

    image

选择存储路径保存后就可以得到对应进程的HPROF文件。eclipse插件可以把上面的工作一键完成。只需要点击Dump HPROF file图标,然后MAT插件就会自动转换格式,并且在eclipse中打开分析结果。eclipse中还专门有个Memory Analysis视图

  1. 得到对应的文件后,如果安装了Eclipse插件,那么切换到Memory Analyzer视图。使用独立安装的,要使用Android SDK自带的的工具(hprof-conv 位置在sdk/platform-tools/hprof-conv)进行转换

+
 <div class="inline-comment" style="width:350px; position:absolute; left:750px"></div><span class="line" style="">1</span><br style=""></pre></td><td class="code" style="padding:8px; line-height:20px; vertical-align:top; border-top-width:1px; border-top-style:solid; border-top-color:rgb(239,239,239); background-color:rgb(246,246,246)"><pre class="disqus" style="font-family:monospace,serif; font-size:0.8em; margin-top:1.6em; margin-bottom:1.6em; border:1px solid rgb(227,237,243); width:613px; padding:10px; overflow:auto; background:rgb(247,250,251)"><span class="ds-thread-count" id="comment61" style="">+</span>

 <div class="inline-comment" style="width:350px; position:absolute; left:750px"></div><span class="line" style="">hprof-conv xxx.xxx.xxx.hprof xxx.xxx.xxx.hprof</span><br style=""></pre></td></tr></tbody></table>

转换过后的.hprof文件即可使用MAT工具打开了。

MAT主界面介绍

这里介绍的不是MAT这个工具的主界面,而是导入一个文件之后,显示OverView的界面。

  • 打开经过转换的hprof文件:

    image

如果选择了第一个,则会生成一个报告。这个无大碍。

image

    选择OverView界面:

    Image

我们需要关注的是下面的Actions区域

    Histogram:列出内存中的对象,对象的个数以及大小

    image

    Dominator Tree:列出最大的对象以及其依赖存活的Object (大小是以Retained Heap为标准排序的)

    image

    Top Consumers : 通过图形列出最大的object

    image

    Duplicate Class:通过MAT自动分析泄漏的原因

一般Histogram和 Dominator Tree是最常用的。

MAT中一些概念介绍

要看懂MAT的列表信息,Shallow heap、Retained Heap、GC Root这几个概念一定要弄懂。

3.3.1 Shallow heap

Shallow size就是对象本身占用内存的大小,不包含其引用的对象。

  • 常规对象(非数组)的Shallow size有其成员变量的数量和类型决定。

  • 数组的shallow size有数组元素的类型(对象类型、基本类型)和数组长度决定

因为不像c++的对象本身可以存放大量内存,java的对象成员都是些引用。真正的内存都在堆上,看起来是一堆原生的byte[], char[], int[],所以我们如果只看对象本身的内存,那么数量都很小。所以我们看到Histogram图是以Shallow size进行排序的,排在第一位第二位的是byte,char 。

3.3.2 Retained Heap

Retained Heap的概念,它表示如果一个对象被释放掉,那会因为该对象的释放而减少引用进而被释放的所有的对象(包括被递归释放的)所占用的heap大小。于是,如果一个对象的某个成员new了一大块int数组,那这个int数组也可以计算到这个对象中。相对于shallow heap,Retained heap可以更精确的反映一个对象实际占用的大小(因为如果该对象释放,retained heap都可以被释放)。

这里要说一下的是,Retained Heap并不总是那么有效。例如我在A里new了一块内存,赋值给A的一个成员变量。此时我让B也指向这块内存。此时,因为A和B都引用到这块内存,所以A释放时,该内存不会被释放。所以这块内存不会被计算到A或者B的Retained Heap中。为了纠正这点,MAT中的Leading Object(例如A或者B)不一定只是一个对象,也可以是多个对象。此时,(A, B)这个组合的Retained Set就包含那块大内存了。对应到MAT的UI中,在Histogram中,可以选择Group By class, superclass or package来选择这个组。

为了计算Retained Memory,MAT引入了Dominator Tree。加入对象A引用B和C,B和C又都引用到D(一个菱形)。此时要计算Retained Memory,A的包括A本身和B,C,D。B和C因为共同引用D,所以他俩的Retained Memory都只是他们本身。D当然也只是自己。我觉得是为了加快计算的速度,MAT改变了对象引用图,而转换成一个对象引用树。在这里例子中,树根是A,而B,C,D是他的三个儿子。B,C,D不再有相互关系。把引用图变成引用树,计算Retained Heap就会非常方便,显示也非常方便。对应到MAT UI上,在dominator tree这个view中,显示了每个对象的shallow heap和retained heap。然后可以以该节点位树根,一步步的细化看看retained heap到底是用在什么地方了。要说一下的是,这种从图到树的转换确实方便了内存分析,但有时候会让人有些疑惑。本来对象B是对象A的一个成员,但因为B还被C引用,所以B在树中并不在A下面,而很可能是平级。

为了纠正这点,MAT中点击右键,可以List objects中选择with outgoing references和with incoming references。这是个真正的引用图的概念,

  • outgoing references :表示该对象的出节点(被该对象引用的对象)。

  • incoming references :表示该对象的入节点(引用到该对象的对象)。

为了更好地理解Retained Heap,下面引用一个例子来说明:

把内存中的对象看成下图中的节点,并且对象和对象之间互相引用。这里有一个特殊的节点GC Roots,这就是reference chain(引用链)的起点:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

最后

答应大伙的备战金三银四,大厂面试真题来啦!

这份资料我从春招开始,就会将各博客、论坛。网站上等优质的Android开发中高级面试题收集起来,然后全网寻找最优的解答方案。每一道面试题都是百分百的大厂面经真题+最优解答。包知识脉络 + 诸多细节。
节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

《960全网最全Android开发笔记》

《379页Android开发面试宝典》

包含了腾讯、百度、小米、阿里、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。

如何使用它?
1.可以通过目录索引直接翻看需要的知识点,查漏补缺。
2.五角星数表示面试问到的频率,代表重要推荐指数

《507页Android开发相关源码解析》

只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。

真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。

腾讯、字节跳动、阿里、百度等BAT大厂 2020-2021面试真题解析

资料收集不易,如果大家喜欢这篇文章,或者对你有帮助不妨多多点赞转发关注哦。文章会持续更新的。绝对干货!!!

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

存中…(img-aPKIPMkZ-1715737732695)]

《507页Android开发相关源码解析》

只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。

真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。

[外链图片转存中…(img-QtAxmKE7-1715737732696)]

腾讯、字节跳动、阿里、百度等BAT大厂 2020-2021面试真题解析

[外链图片转存中…(img-6iKSUY9k-1715737732696)]

资料收集不易,如果大家喜欢这篇文章,或者对你有帮助不妨多多点赞转发关注哦。文章会持续更新的。绝对干货!!!

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值