个人简历网站模板源码分享(简历模板网址)

大家好,今天来为大家分享个人简历网站模板源码分享的一些知识点,和简历模板网址的问题解析,大家要是都明白,那么可以忽略,如果不太清楚的话可以看看本篇文章,相信很大概率可以解决您的问题,接下来我们就一起来看看吧!

掌握了本篇知识之后,简历上就可以多加一条个人技能了:

熟悉Java/Android中常见的集合源码,包括List、Set、Map、Queue/Deque等….

下面就是正文了,欢迎讨论~:

前言

起初想看集合源码是因为,一次偶然的机会,一位同事跟我说ArrayList的初始容量为空,第一次add时才会扩容至10。我当时就觉得我的知识体系有点落后了,就决定去看一遍集合源码。在看的过程中呢,可以用惊喜+收获满满来形容了。惊喜是指对于Stack、HashSet、LinkedHashMap等等的实现方式感觉很震惊,看完这些集合源码有种那种你感觉很难但是其实很简单的感觉,不仅如此,熟悉集合源码对实际开发也是有帮助的,主要体现在ArrayListremove时可能抛出的ConcurrentModificationException、Collections使用不当造成的UnsupportedOperationException、ArrayMap的缓存机制等等。

在看集合源码时,Debug源码有时并不直接,可以使用OpenJDK开源的JOL工具查看对象的内存布局,这对分析集合扩容、HashMap树化非常有用。

目录

ArrayListVectorStackLinkedListHashMapHashtableTreeMapLinkedHashMapHashSetTreeSetCopyOnWriteArrayListConcurrentHashMapLinkedBlockingQueueSynchronousQueueSparseArrayArrayMap

486页超全面Android开发相关源码精编解析:

ArrayList

ArrayList实现了List接口,RandomAccess接口,可以插入空数据以及支持随机访问。它相当于一个动态数组,初始化时是一个空数组,在第一次add时设置初始容量为10,每次扩容都增加到原来的1.5倍。简单的add就是在elementData数组末尾添加一个数据,size++;指定index添加数据,就需要拷贝index后面的数据后移一位。在删除的时,如果是删除null,就遍历数组找到第一个null值删除,否则就遍历比较equals删除指定index的数据,其实也就是拷贝index后面的数据前移一位。删除数据时,最好使用迭代器来做,避免ConcurrentModificationException,它并不只是在并发时才会抛出的,单线程也可能抛出,其实内部是比较expectedModCount和modCount是否相等来判断的。ArrayList的性能损耗就来源于数组拷贝,在适当情况下,可以初始化时指定容量大小,避免不必要的扩容操作。其实呢,ArrayList还有一个缺点,就是不能自动缩容,但是我们可以手动调用trimToSize来缩容至当前size大小。

还有一点是elementData是用transient修饰的,也就是拒绝数据被自动序列化,因为ArrayList并不是所有位置都有数据,所以没必要全部序列化,应该只序列化有数据的部分,所以它重写了writeObject/readObjet方法。

Vector

Vector感觉是一个被人抛弃的类,它在初始化时直接设置了数组初始容量为10,在它的get/add等方法都加了synchronized,完全可以看成是一个加锁的ArrayList。

不同的是,它在扩容时默认是直接加倍的,当然这个是可以控制的,在构造方法中可以传一个增长数,这个数是需要大于1的,不然就按1处理。比如是0.75,每次扩容就加1,是5呢每次扩容就加5。

当然,想让ArrayList变成线程安全的,还可以使用Collections.synchronizedList来做,或者呢,使用CopyOnWriteArrayList。

Vector基本上没人用过,但是它的一个子类大家可能会用过,那就是Stack。

Stack

Stack是继承于Vector的,所以它也是线程安全的,它总共代码就二三十行。所有实现都是在父类Vector中,在我们调用push时就是往数组末尾add一个数据,pop时就是获取数组的最后一个元素。和Vector不同的是,它的初始容量为空。还有需要注意的是,在调用peek/pop时,如果栈为空,是会抛EmptyStackException的。

LinkedList

LinkedList实现了Deque接口,说明它是一个双向链表,每一个Node节点都有prev和next指针,每次add或者remove时都需要更新前驱和后指针,在指定index位置删除时,会区分index如果是靠头部比较近,就从头first节点遍历删,否则从尾部last节点删。使用迭代器时,也是可以使用ListIterator从头或从尾遍历。

它在新增和删除时,时间复杂度O(1),而在查询时时间复杂度O(n)。

HashMap

HashMap底层数据结构是数组+链表+红黑树。数组的主要作用是方便快速查找,时间复杂度是O(1),默认大小是16,数组的下表索引是通过key的hashCode计算出来的,数组元素叫做Node,当多个key的hashCode一致,但key值不相同时,即发生了hash冲突时,单个Node就会转化为链表,链表的查询复杂度是O(n),当链表的长度大于等于8并且数组的大小超过64时,链表就会转化为红黑树,红黑树的查询复杂度是O(log(n)),简单来说,最坏的查询次数相当于红黑树的最大深度。

HashMap非线程安全,如果需要满足线程安全,可以用Collections.synchronizedMap使得HashMap具有线程安全的能力,或者使用ConcurrentHashMap。

上面已经说清楚HashMap的大致实现原理了,下面就说一些细节的东西。

在HashMap中,哈希桶数组的长度大小必须是2的n次方,这是一种非常规的设计,常规的做法是把桶大小设计为素数。相对来说,素数导致冲突的概率要小于合数。HashTable初始化桶的大小为11就是把桶大小设计为素数的典型应用。HashMap采用这种非常规的设计,主要是为了在取模和扩容时做优化,同时为了减少冲突,HashMap在定位哈希桶索引位置时,也加入了高位参与运算的过程。

在链表长度大于等于8并且数组长度大于等于64时,才会进行树化。如果数组长度小于64,则只会扩容而不会树化。为什么是8呢?这个在源码注释中说的比较清楚,大致意思是,在链表数据不多的时候,使用链表进行遍历也比较快,只有当链表数据比较多才会转化为红黑树,但红黑树的占用空间是链表的两倍,考虑到转化时间和空间消耗,所以我们需要定义出转化的边界值。在考虑设计8这个值的时候,参考了泊松分布概率函数,得出的结论就是当链表长度为8的时候,出现的概率不到千万分之一,所以说,正常情况下链表的长度不可能到达8。

接下来讲确定哈希桶索引位置的做法。

简单的做法就是通过hash对数组长度取模运算得到索引,这样元素分布相对来说也是比较均匀的,但是取模运算不及位运算,HashMap采用的是(n-1)&hash,当n为2的次方时,(n-1)&hash等价于取模运算,但是位运算执行效率显然是高于取模运算的。

同时,取hash的时候是通过hashCode的高十六位和低十六位异或得到,这样做在数组长度比较小的时候也能保证高位bit都能参与到hash计算中,同时不会有太大开销。

然后再讲一下扩容机制,扩容的时候是容量翻倍,也就是x2,这也同时保证了长度依旧是2的次方,所以前面基于位运算取索引的优化得以保留。既然数组长度改变了,那么肯定需要重新计算索引位置呀?这里又有一个优化点,当n为2次方时,x2不过是在高位补1,然后在进行与运算,与1进行与运算就是它本身嘛。所以这时只需要看hash的高位是1还是0,如果是0,索引不变,如果是1,新索引就是原索引加旧桶值。这也就避免了重新计算索引,只需要看hash的高位是1还是0即可。但是你可能会说,这必须得保证数组长度为n次方呀,我们可以在初始化HashMap时传一个非2的n次方的数,这就炸了。其实呢,HashMap会根据你数组容量,自动调节到2的n次方上,比如传15就是16,传17呢就是32,向上转一个最接近的2次幂数。

最后,在这里面我并没有将HashMap的具体put/remove/get的实现,这些其实就是数组或链表或红黑树的操作,数组和链表大家都很熟悉了,红黑树我也不是很懂,只需要记得每次put/remove时,都会进行着色和旋转,使得红黑树更加平衡。再不济就把红黑树看成一颗二叉搜索树也行趴。

Hashtable

首先这个Hashtable的命名就有点离谱,没有遵循驼峰命名法。它的实现是通过一个Entry数组来做的,put/remove/get都加了synchronized,是线程安全的,它的取index是(hash&0x7FFFFFFF)%tab.length,前面和0x7FFFFFF是为了让hash值变为正数,那你可能会问,为啥不用Math.abs呢,其实在数值溢出时,abs也是可能会得到负值的;HashMap的可以只有一个key为null,多个value为null的,而Hashtable是不允许key/value为null的,不然直接抛空指针。

Hashtable在扩容时,是x2+1的。Hashtable在处理hash冲突时,是插入到链表的头部,即头插法。

TreeMap

TreeMap底层的数据结构就是红黑树,和HashMap的红黑树结构一样。不同的是,TreeMap通过compare来比较key的大小,然后利用红黑树左小右大的特性,为每个key找到自己的位置,维护了key的大小关系,适用于key需要排序的场景。

因为底层使用的是红黑树的结构,所以它的put/remove/get等方法时间复杂度都是log(n)的。

LinkedHashMap

HashMap是无序的,TreeMap可以按照key进行排序,那有没有Map是可以维护插入顺序的呢?这就是LinkedHashMap。

LinkedHashMap本身是继承HashMap的,所以它拥有HashMap的所有特性,在此基础上还提供了两大特性,第一个是按照插入顺序访问,第二个呢是实现了最近最少使用策略,这个需要在构造方法中传一个accessOrder=true,默认是false也就是按照插入顺序访问。

在我们调用put/remove方法时,其实是调用的HashMap的put/remove方法,而重写了get方法实现了以上特性。那么它是如何基于HashMap实现了以上特性的呢?其实是通过重写HashMap里面的三个方法,这个三个方法是每当HashMap调用put/get/remove时都会调用的,只不过在HashMap中是一个空实现,而在LinkedHashMap中它被用来记录插入顺序,其实也就是扩展HashMap的Node使其具备链表结构,也就是每个数组元素增加befor和after属性。

但是呢,LinkedHashMap只提供了单向访问,即按照插入的顺序从头到尾进行访问,不能像LinkedList那样可以双向访问。

在使用LRU策略时,可以覆写删除策略的方法removeEldestEntry方法,比如可以指定当节点数大于10就开始删除头结点(size()>10)等等。

HashSet

HashSet的源码还是很少的,它保证了每个元素是不会重复的。在它的构造方法中,其实是newHashMap来做的,一般我们只使用它的add和contains方法,其实都是调用HashMap来实现的。

这个类也没说可说的了,代码就那几十行。

TreeSet

TreeSet大致的结构和HashSet相似,底层组合的是TreeMap,所以继承了TreeMapkey能够排序的功能,同时也具有Set不可重复的属性。

TreeSet的add方法是调用NavigableMap的put方法的,它是一个接口,它的实现是在TreeMap中,也就是说,TreeSet定义了接口的规范,TreeMap负责去实现。

TreeSet基本上不常用,一般需要不重复元素且能根据元素进行排序的时候,才会用到TreeSet,使用时需要我们注意最好实现Comparable接口,这样方便底层的TreeMap根据key进行排序。

CopyOnWriteArrayList

CopyOnWriteArrayList是一个线程安全的ArrayList,使用了写时复制策略,它是通过synchronized+数组拷贝+volatile关键字保证了线程安全。在它addvalue时,是先synchronized加锁保证同一时刻只有一个线程执行add方法,再拷贝一份长度加一的新数组,把value加到新数组的末尾,最后再把新数组赋值给原数组。你可能会问了,这都加锁了,为啥不在原数组上面直接操作呢?原因主要有两个:

在新的数组上进行拷贝,对老数组没有任何影响,只有新数组完全拷贝完成之后,外部才能访问到,降低了在赋值过程中,老数组数据变动的影响volatile关键字修饰的是数组,如果只是简单的在原数组上修改其中某几个元素的值,是无法触发可见性的,我们必须通过修改数组的内存地址才行,也就说要对数组进行重新赋值才行

数组拷贝也有一点好处,那就是没有空间损耗,元素是占满数组的。

remove时和add相似,就不多说了。

CopyOnWriteArrayList读的时候不需要加锁,适合读多写少的场景,这同时也会带来一个弱一致性的问题,即获取迭代器后迭代元素,其他线程对该list进行的增删改不可见,因为它们操作的是两个不同的数组,这就是弱一致性。简单来说,就是CopyOnWriteArrayList提供了弱一致性的迭代器,从而保证获取迭代器后,其他线程对list的修改是不可见的,迭代器遍历的数组是一个快照。

CopyOnWriteArraySet的内部实现就是在其构造方法中newCopyOnWriteArrayList来实现的。

ConcurrentHashMap

ConcurrentHashMap和HashMap相比,它的底层也是使用数组+链表+红黑树来实现的,不过新增了转移节点(ForwardingNode),是为了保证扩容时的线程安全的节点。而且红黑树结构略有不同,HashMap的红黑树节点叫做TreeNode,TreeNode不仅仅有属性,还维护着红黑树的结构,比如说查找、新增等;ConcurrentHashMap中红黑树被拆分成两块,TreeNode仅仅维护属性和查找功能,新增了TreeBin,来维护红黑树结构,并负责根节点的加锁和解锁。

和HashTable相比,它也是线程安全的,但是ConcurrentHashMap多个线程同时进行put、remove等操作时并不会阻塞,可以同时进行;HashTable在操作时,会锁住整个Map。

ConcurrentHashMap在put时,在一个for死循环里面,也就是一定能保证put成功。第一次put时,会先通过CAS保证只有一个线程扩容,如果有其他线程在执行扩容操作,就会调用Thread.yield释放当前CPU调度权,重新发起锁的竞争,这一步是在一个while循环里面去做的只要容量为空,就一直循环。再求出table索引之后,如果该槽点为空并不是直接新增,而是通过CAS新增。如果判断当前是转移节点(转移节点的hash值固定为MOVED为-1),表示该槽点正在扩容,就会一直等待扩容完成;如果当前槽点有值,就是key的hash冲突的情况,此时槽点上可能是链表或红黑树,这时候就会通过synchronized锁住槽点,来保证同一时刻只会有一个线程能对槽点进行修改。也就是说,put是通过自旋+CAS+锁来实现线程安全的。

在进行扩容时,首先需要把老数组的值全部拷贝到新数组上,先从数组的队尾开始拷贝,拷贝数组的槽点时,会先把原数组的槽点锁住,保证原数组槽点不能操作,成功拷贝到新数组时,把原数组槽点赋值为转移节点。如果此时有数据要put到此槽点就会一直等待。拷贝时也是扩容原数组两倍大小。

ConcurrentHashMap的get和HashMap基本无差。

LinkedBlockingQueue

LinkedBlockingQueue即链表阻塞队列,它实现了BlockingQueue接口,BlockingQueue在Queue的基础上加上了阻塞的概念;链表维护了先入先出的队列,新元素被放在队尾,获取元素从队头拿;当我们调用put时,队列满了就一直阻塞,在阻塞时被其他线程设置了中断标志,则被阻塞的线程会抛出InterruptedException异常而返回,也可以调用非阻塞的offer方法,如果队列满了就放弃当前元素返回false;调用take时,如果队列为空也是一直阻塞,也可以调用非阻塞的poll方法,如果队列为空就直接返回null。

LinkedBlockingQueue内部构成简单来说,可以分为三个部分:链表存储+锁+迭代器。它内部有两把ReentrantLock锁,分别是put锁和take锁,保证了队列操作的线程安全,设计两把锁,是为了put和take两种操作可以同时进行,互不影响。

在put时,先调用ReentrantLock的lockInterruptibly设置可中断锁,然后判断队列是否满了,如果满了就调用Condition的await函数无限等待,否则就添加到队列尾部;如果这时队列不为空,就唤醒一个take的等待线程,最后在解锁。

在take时,也是先加take锁,然后判断队列是否为空,为空就无限等待,否则就删除链表头节点返回,如果这时队列没有满,就唤醒一个put的等待线程,最后在解锁。

在remove时会获取双重锁,获取后,其他线程进行入队或出队操作都会被阻塞挂起,然后遍历队列找到要删除的元素删除,找到了就删除并唤醒一个阻塞的put线程,否则返回false。

LinkedBlockingQueue主要用在线程池中,当我们使用Executors的newFixedThreadPool或newSingleThreadPool创建线程池时,阻塞队列用的就是LinkedBlockingQueue,但是通常我们不建议这样直接使用Executors创建线程池,因为LinkedBlockingQueue的默认队列大小是Integer.MAX_VALUE,可能会爆内存。

在Android中,AsyncTask在核心线程池不够用的情况下触发任务拒绝策略时,会用到LinkedBlockingQueue。

SynchronousQueue

SynchronousQueue是一个不存储元素的阻塞队列,每一个put操作必须等待take操作,否则不能添加元素。在使用Executors的newCachedThreadPool创建线程池用的就是它,在AsyncTask创建的一个单线程池用的也是它,核心线程数为1,最大线程数为20,非核心线程空闲存活时间为三秒,适合任务多但是执行比较快的场景中。

源码水太深了,还没看明白,待定。

SparseArray

SparseArray是Android中一种特有的数据结构,用来替代HashMap的。初始化时容量为10,它里面有两个数组,一个是int[]数组存放key,一个是Object[]value数组。也就是它的key只能为int;在put时,会根据传入的key进行二分查找找到合适的插入位置,如果当前位置有值或者是DELETED节点,就直接覆盖,否则就需要拷贝数组后移一位,空出一个位置让其插入。如果数组满了但是还有DELETED节点,就需要调用gc方法,gc方法所做的就是把DELETED节点后面的数前移,也就是真正的把DELETED节点删掉然后在插入,否则就只能扩容了。

调用remove时,并不会直接把key从int[]数组里面删掉,而是把当前key指向的value设置成DELETED节点,这样做是为了减少int[]数组的结构调整,结构调整就意味着数据拷贝。但是当我们调用keyAt/valueAt获取索引时,如果有DELETED节点就必须得调用gc,不然获得的index肯定不对。延迟回收的好处适合频繁删除和插入来回执行的场景,性能很好。

get方法就比较简单了,二分查找获取key对应的索引index,返回values[index]即可。

可以看到,SparseArray比HashMap少了基本数据的自动装箱操作,而且不需要额外的结构体,单个元素存储成本低,在数据量小的情况下,随机访问的效率很高。但是缺点也是显而易见的,就是增删的效率比较低,在数据量比较大的时候,调用gc拷贝数组成本巨大。

除了SparseArray,Android还提供了SparseIntArray(int:int)、SparseBooleanArray(int:boolean)、SparseLongArray(int:long)等,其实就是把对应的value换成基本数据类型。

ArrayMap

SparseArray只能存key为int的键值对,如果需要存其他类型的key,就可以使用ArrayMap了。ArrayMap的底层和SparseArray类似,都是有两个数组,不过ArrayMap的一个数组存hashCode值,一个数组存key-value键值对,键值对数组的大小显然要是hashCode数组大小的两倍。为了减少频繁的创建和回收Map对象,ArrayMap还采用了两个大小为10的缓存队列来分别保存大小为4和8的ArrayMap对象。为了节省内存,还有内存扩张和内存收缩策略。

ArrayMap在put/remove时,和SparseArray基本一致,也是通过二分查找求数组索引,然后在执行相应的操作。不同的是ArrayMap的扩容机制和缩容机制。

在put需要扩容时,如果容量小于4就给4,小于8就给8,其次就是扩容1.5倍。之所以给4或8是因为可以利用缓存的ArrayMap对象;在remove时,如果数组长度大于8但是存储的数据不足数据大小的1/3时,就会缩容,mSize小于等于8,则设置新大小为8,否则就设置为mSize的1.5倍,也就是说在内存使用量不足1/3时,内存数据收紧50%。

这个缓存还是很有必要的,毕竟ArrayMap的使用量还是蛮大的,Bundle的底层就是用ArrayMap来存数据的,可想而知了。但是可以思考一下Bundle为啥用ArrayMap而不用SparseArray呢?

除了put方法,ArrayMap和SparseArray都有一个append方法,它和put很相似,append的差异在于该方法不会去做扩容操作,是一个轻量级的插入方法。在明确知道肯定会插入队尾的情况下使用append性更好,因为put一上来就做二分查找,时间复杂度O(logn),而append时间复杂度为O(1)。

ArraySet也是Android特有的数据结构,用来替代HashSet的,和ArrayMap几乎一致,包含了缓存机制、扩容机制等。

Android及相关开发源码学习资源

其实客户端开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。

这里再分享一下我面试期间的复习路线:(以下体系的复习资料是我从各路大佬收集整理好的)

《Android开发七大模块核心知识笔记》

《379页Android开发面试宝典》

历时半年,我们整理了这份市面上最全面的安卓面试题解析大全包含了腾讯、百度、小米、阿里、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。

如何使用它?

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

《486页超全面Android开发相关源码精编解析》

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

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

资料太多,全部展示会影响篇幅,暂时就先列举这些部分截图;

需要的朋友,直接转发+点赞+私信回复【资料】一键领取!!!

好了,本文到此结束,如果可以帮助到大家,还望关注本站哦!

Published by

风君子

独自遨游何稽首 揭天掀地慰生平