1. List
List 是有序的 @H_404_7@Collection。@H_404_7@Java List 一共三个实现类:
分别是 ArrayList、@H_404_7@Vector 和 @H_404_7@LinkedList
ArrayList
ArrayList 是最常用的 @H_404_7@List 实现类,内部是通过数组实现的,它允许对元素进行快速随机访问。数组的缺点是每个元素之间不能有间隔,当数组大小不满足时需要增加存储能力,就要将已经有数组的数据复制到新的存储空间中。当从 @H_404_7@ArrayList 的中间位置插入或者删除元素时,需要对数组进行复制、移动、代价比较高。因此,它适合随机查找和遍历,不适合插入和删除。排列有序,可重复,容量不够的时候,当前容量*1.5+1。
Vector(数组实现、线程同步)
Vector 与 @H_404_7@ArrayList 一样,也是通过数组实现的,不同的是它支持线程的同步,即某一时刻只有一个线程能够写 @H_404_7@Vector,避免多线程同时写而引起的不一致性,但实现同步需要很高的花费,因此,访问它比访问 @H_404_7@ArrayList 慢。默认扩展一倍容量。
LinkList(链表)
LinkedList 是用链表结构存储数据的,很适合数据的动态插入和删除,随机访问和遍历速度比较慢。另外,他还提供了 @H_404_7@List 接口中没有定义的方法,专门用于操作表头和表尾元素,可以当作堆栈、队列和双向队列使用。底层使用双向循环链表数据结构。线程不安全。
2. ArrayList和linkedList的线程安全处理
ArrayList是线程不安全的,因此在并发编程时,经常会使用@H_404_7@Collections.synchronizedList与@H_404_7@CopyOnWriteArrayList来替代@H_404_7@ArrayList,接下来对这两种@H_404_7@list进行性能的比较。其中@H_404_7@Collections.synchronizedLis在更新操作中使用了同步锁,而@H_404_7@CopyOnWriteArrayList在更新操作中不仅使用了可重入锁,而且还需要进行数组的复制。
方法一:List<String> list = Collections.synchronizedList(new LinkedList<String>());
方法二:将@H_404_7@LinkedList全部换成@H_404_7@ConcurrentLinkedQueue;
3. Set
Set 注重独一无二的性质@H_404_7@,该体系集合用于存储无序@H_404_7@(存入和取出的顺序不一定相同@H_404_7@)元素,值不能重复。对象的相等性本质是对象 @H_404_7@hashCode 值(@H_404_7@java 是依据对象的内存地址计算出的此序号)判断的,如果想要让两个不同的对象视为相等的,就必须覆盖 @H_404_7@Object 的 @H_404_7@hashCode 方法和 @H_404_7@equals 方法。
HashSet(@H_404_7@Hash 表)
哈希表边存放的是哈希值。HashSet 存储元素的顺序并不是按照存入时的顺序(和 @H_404_7@List 显然不同) 而是按照哈希值来存的所以取数据也是按照哈希值取得。元素的哈希值是通过元素的@H_404_7@hashcode 方法来获取的@H_404_7@,HashSet 首先判断两个元素的哈希值,如果哈希值一样,接着会比较@H_404_7@equals 方法 如果 @H_404_7@equls 结果为 @H_404_7@true ,@H_404_7@HashSet 就视为同一个元素。如果 @H_404_7@equals 为 @H_404_7@false 就不是同一个元素。
哈希值相同 equals 为 @H_404_7@false 的元素是怎么存储呢@H_404_7@,就是在同样的哈希值下顺延(可以认为哈希值相同的元素放在一个哈希桶中)。也就是哈希一样的存一列。如图 @H_404_7@1 表示 @H_404_7@hashCode 值不相同的情况;图 @H_404_7@2 表示 @H_404_7@hashCode 值相同,但 @H_404_7@equals 不相同的情况。
HashSet 通过 @H_404_7@hashCode 值来确定元素在内存中的位置。一个 @H_404_7@hashCode 位置上可以存放多个元素。
TreeSet(二叉树)
1. TreeSet()是使用二叉树的原理对新 @H_404_7@add()的对象按照指定的顺序排序(升序、降序),每增
加一个对象都会进行排序,将对象插入的二叉树指定的位置。
2. Integer 和 @H_404_7@String 对象都可以进行默认的 @H_404_7@TreeSet 排序,而自定义类的对象是不可以的,自己定义的类必须实现 @H_404_7@Comparable 接口,并且覆写相应的 @H_404_7@compareTo()函数,才可以正常使用。
3. 在覆写 @H_404_7@compare()函数时,要返回相应的值才能使 @H_404_7@TreeSet 按照一定的规则来排序
4. 比较此对象与指定对象的顺序。如果该对象小于、等于或大于指定对象,则分别返回负整数、零或正整数。
LinkHashSet(@H_404_7@HashSet+LinkedHashMap)
对于 LinkedHashSet 而言,它继承与 @H_404_7@HashSet、又基于 @H_404_7@LinkedHashMap 来实现的。
LinkedHashSet 底层使用 @H_404_7@LinkedHashMap 来保存所有元素,它继承与 @H_404_7@HashSet,其所有的方法操作上又与 @H_404_7@HashSet 相同,因此 @H_404_7@LinkedHashSet 的实现上非常简单,只提供了四个构造方法,并通过传递一个标识参数,调用父类的构造器,底层构造一个 @H_404_7@LinkedHashMap 来实现,在相关操作上与父类 @H_404_7@HashSet 的操作相同,直接调用父类 @H_404_7@HashSet 的方法即可。
4. Map
HashMap(数组@H_404_7@+链表@H_404_7@+红黑树)
HashMap 根据键的 @H_404_7@hashCode 值存储数据,大多数情况下可以直接定位到它的值,因而具有很快的访问速度,但遍历顺序却是不确定的。 @H_404_7@HashMap 最多只允许一条记录的键为 @H_404_7@null,允许多条记录的值为 @H_404_7@null。@H_404_7@HashMap 非线程安全,即任一时刻可以有多个线程同时写 @H_404_7@HashMap,可能会导致数据的不一致。如果需要满足线程安全,可以用 @H_404_7@Collections 的 @H_404_7@synchronizedMap 方法使@H_404_7@HashMap 具有线程安全的能力,或者使用 @H_404_7@ConcurrentHashMap。
java7的@H_404_7@hashmap实现
大方向上,HashMap 里面是一个数组,然后数组中每个元素是一个单向链表。上图中,每个绿色的实体是嵌套类 @H_404_7@Entry 的实例,@H_404_7@Entry 包含四个属性:@H_404_7@key,value,hash 值和用于单向链表的 @H_404_7@next。
1. capacity:当前数组容量,始终保持 @H_404_7@2^n,可以扩容,扩容后数组大小为当前的 @H_404_7@2 倍。
2. loadFactor:负载因子,默认为 @H_404_7@0.75。
3. threshold:扩容的阈值,等于 @H_404_7@capacity * loadFactor
java8的@H_404_7@hashmap实现
Java8 对 @H_404_7@HashMap 进行了一些修改,最大的不同就是利用了红黑树,所以其由 数组@H_404_7@+链表@H_404_7@+红黑树 组成。
根据 Java7 HashMap 的介绍,我们知道,查找的时候,根据 @H_404_7@hash 值我们能够快速定位到数组的具体下标,但是之后的话,需要顺着链表一个个比较下去才能找到我们需要的,时间复杂度取决于链表的长度,为 @H_404_7@O(n)。为了降低这部分的开销,在 @H_404_7@Java8 中,当链表中的元素超过了 @H_404_7@8 个以后,会将链表转换为红黑树,在这些位置进行查找的时候可以降低时间复杂度为 @H_404_7@O(logN)。
ConcurrentHashMap
Segment 段
ConcurrentHashMap 和 @H_404_7@HashMap 思路是差不多的,但是因为它支持并发操作,所以要复杂一些。整个 @H_404_7@ConcurrentHashMap 由一个个 @H_404_7@Segment 组成,@H_404_7@Segment 代表@H_404_7@”部分@H_404_7@“或@H_404_7@”一段@H_404_7@“的
意思,所以很多地方都会将其描述为分段锁。注意,行文中,我很多地方用了“槽@H_404_7@”来代表一个@H_404_7@segment。
线程安全(Segment 继承 @H_404_7@ReentrantLock 加锁)
简单理解就是,ConcurrentHashMap 是一个 @H_404_7@Segment 数组,@H_404_7@Segment 通过继承@H_404_7@ReentrantLock 来进行加锁,所以每次需要加锁的操作锁住的是一个 @H_404_7@segment,这样只要保证每个 @H_404_7@Segment 是线程安全的,也就实现了全局的线程安全。
并行度(默认 16)
concurrencyLevel:并行级别、并发数、@H_404_7@Segment 数,怎么翻译不重要,理解它。默认是 @H_404_7@16,
也就是说 ConcurrentHashMap 有 @H_404_7@16 个 @H_404_7@Segments,所以理论上,这个时候,最多可以同时支持 @H_404_7@16 个线程并发写,只要它们的操作分别分布在不同的 @H_404_7@Segment 上。这个值可以在初始化的时候设置为其他值,但是一旦初始化以后,它是不可以扩容的。再具体到每个 @H_404_7@Segment 内部,其实每个 @H_404_7@Segment 很像之前介绍的 @H_404_7@HashMap,不过它要保证线程安全,所以处理起来要麻烦些。
java8实现
Java8 对 @H_404_7@ConcurrentHashMap 进行了比较大的改动@H_404_7@,Java8 也引入了红黑树。通过对链表的头加锁实现。
HashTable(线程安全)
Hashtable 是遗留类,很多映射的常用功能与 @H_404_7@HashMap 类似,不同的是它承自 @H_404_7@Dictionary 类,并且是线程安全的,任一时间只有一个线程能写 @H_404_7@Hashtable,并发性不如 @H_404_7@ConcurrentHashMap,因为 @H_404_7@ConcurrentHashMap 引入了分段锁。@H_404_7@Hashtable 不建议在新代码中使用,不需要线程安全的场合可以用 @H_404_7@HashMap 替换,需要线程安全的场合可以用 @H_404_7@ConcurrentHashMap 替换。
TreeMap(可排序)
TreeMap 实现 @H_404_7@SortedMap 接口,能够把它保存的记录根据键排序,默认是按键值的升序排序,也可以指定排序的比较器,当用 @H_404_7@Iterator 遍历 @H_404_7@TreeMap 时,得到的记录是排过序的。
如果使用排序的映射,建议使用 TreeMap。
在使用 TreeMap 时,@H_404_7@key 必须实现 @H_404_7@Comparable 接口或者在构造 @H_404_7@TreeMap 传入自定义的
Comparator,否则会在运行时抛出 @H_404_7@java.lang.ClassCastException 类型的异常。
LinkHashMap(记录插入顺序)
LinkedHashMap 是 @H_404_7@HashMap 的一个子类,保存了记录的插入顺序,在用 @H_404_7@Iterator 遍历
LinkedHashMap 时,先得到的记录肯定是先插入的,也可以在构造时带参数,按照访问次序排序。
5. HashMap中,初始化设置长度时,容量自动转成2的幂次长度的算法剖析
默认情况下HashMap的容量是@H_404_7@16,但是,如果用户通过构造函数指定了一个数字作为容量,那么@H_404_7@Hash会选择大于该数字的第一个@H_404_7@2的幂作为容量。@H_404_7@(3->4、@H_404_7@7->8、@H_404_7@9->16)
我们可以知道,在已知HashMap中将要存放的@H_404_7@KV个数的时候,设置一个合理的初始化容量可以有效的提高性能。因为减少扩容,提高效率。
HashMap有扩容机制,就是当达到扩容条件时会进行扩容。@H_404_7@HashMap的扩容条件就是当@H_404_7@HashMap中的元素个数(@H_404_7@size)超过临界值(@H_404_7@threshold)时就会自动扩容。在@H_404_7@HashMap中,@H_404_7@threshold = loadFactor * capacity。
所以,如果我们没有设置初始容量大小,随着元素的不断增加,HashMap会发生多次扩容,而@H_404_7@HashMap中的扩容机制决定了每次扩容都需要重建@H_404_7@hash表,是非常影响性能的。
默认情况下,当我们设置HashMap的初始化容量时,实际上@H_404_7@HashMap会采用第一个大于该数值的@H_404_7@2的幂作为初始化容量。
在Jdk 1.7和@H_404_7@Jdk 1.8中,@H_404_7@HashMap初始化这个容量的时机不同。@H_404_7@jdk1.8中,在调用@H_404_7@HashMap的构造函数定义@H_404_7@HashMap的时候,就会进行容量的设定。而在@H_404_7@Jdk 1.7中,要等到第一次@H_404_7@put操作时才进行这一操作。
不管是Jdk 1.7还是@H_404_7@Jdk 1.8,计算初始化容量的算法其实是如出一辙的,主要代码如下:
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n <>0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
根据用户传入的容量值(代码中的cap),通过计算,得到第一个比他大的@H_404_7@2的幂并返回。
通过几次无符号右移和按位或运算,我们把1100 1100 1100转换成了@H_404_7@1111 1111 1111 ,再把@H_404_7@1111 1111 1111加@H_404_7@1,就得到了@H_404_7@1 0000 0000 0000,这就是大于@H_404_7@1100 1100 1100的第一个@H_404_7@2的幂。
但是还有一种特殊情况套用以上公式不行,这些数字就是2的幂自身。如果数字@H_404_7@4 套用公式的话。得到的会是 @H_404_7@8 。为了解决这个问题,JDK的工程师把所有用户传进来的数在进行计算之前先@H_404_7@-1。
最好的初始化容量机制:expectedSize / 0.75F + 1.0F
6. 如果HashMap的大小超过了负载因子(load factor)定义的容量,怎么办?
默认的负载因子大小为0.75,也就是说,当一个@H_404_7@map填满了@H_404_7@75%的@H_404_7@bucket时候,和其它集合类@H_404_7@(如@H_404_7@ArrayList等@H_404_7@)一样,将会创建原来@H_404_7@HashMap大小的两倍的@H_404_7@bucket数组,来重新调整@H_404_7@map的大小,并将原来的对象放入新的@H_404_7@bucket数组中。这个过程叫作@H_404_7@rehashing,因为它调用@H_404_7@hash方法找到新的@H_404_7@bucket位置。这个值只可能在两个地方,一个是原下标的位置,另一种是在下标为@H_404_7@<原下标@H_404_7@+原容量@H_404_7@>的位置。
7. 重新调整HashMap大小存在什么问题吗?
当重新调整HashMap大小的时候,确实存在条件竞争,因为如果两个线程都发现@H_404_7@HashMap需要重新调整大小了,它们会同时试着调整大小。在调整大小的过程中,存储在链表中的元素的次序会反过来,因为移动到新的@H_404_7@bucket位置的时候,@H_404_7@HashMap并不会将元素放在链表的尾部,而是放在头部,这是为了避免尾部遍历@H_404_7@(tail traversing)。如果条件竞争发生了,那么就死循环了。@H_404_7@(多线程的环境下不使用@H_404_7@HashMap)
8. 为什么hashmap多线程会进入死循环?
HashMap的容量是有限的。当经过多次元素插入,使得@H_404_7@HashMap达到一定饱和度时,@H_404_7@Key映射位置发生冲突的几率会逐渐提高。这时候,@H_404_7@HashMap需要扩展它的长度,也就是进行@H_404_7@Resize。
由于扩容采用的是尾插法。会造成环形的情况,第一个线程 是 2指向@H_404_7@3。然后@H_404_7@cpu执行另一个线程,尾插法 变成了@H_404_7@3指向@H_404_7@2。此时回到线程@H_404_7@1。@H_404_7@2指向@H_404_7@3 3又指向@H_404_7@2。就找不到尾部数据,产生死循环。
9. hashmap的工作原理是什么?
HashMap是基于@H_404_7@hashing的原理,我们使用@H_404_7@put(key,value)存储对象到@H_404_7@HashMap中,使用@H_404_7@get(key)从@H_404_7@HashMap中获取对象。当我们给@H_404_7@put()方法传递键和值时,我们先对键调用@H_404_7@hashCode()方法,计算并返回的@H_404_7@hashCode是用于找到@H_404_7@Map数组的@H_404_7@bucket位置来储存@H_404_7@Node 对象。这里关键点在于指出,@H_404_7@HashMap是在@H_404_7@bucket中储存键对象和值对象,作为@H_404_7@Map.Node 。
以下是具体的put过程(@H_404_7@JDK1.8版)
1.对@H_404_7@Key求@H_404_7@Hash值,然后再计算下标
2.如果没有碰撞,直接放入桶中(碰撞的意思是计算得到的@H_404_7@Hash值相同,需要放到同一个@H_404_7@bucket中)
3.如果碰撞了,则调用@H_404_7@equals() 比较@H_404_7@value,相同则替换旧值,不同则以链表的方式链接到后面
4.如果链表长度超过阀值@H_404_7@( TREEIFY THRESHOLD==8),就把链表转成红黑树,链表长度低于@H_404_7@6,就把红黑树转回链表
5.如果桶满了@H_404_7@(容量@H_404_7@16*加载因子@H_404_7@0.75),就需要 @H_404_7@resize(扩容@H_404_7@2倍后重排)
以下是具体get过程@H_404_7@(考虑特殊情况如果两个键的@H_404_7@hashcode相同,你如何获取值对象?@H_404_7@)
当我们调用get()方法,@H_404_7@HashMap会使用键对象的@H_404_7@hashcode找到@H_404_7@bucket位置,找到@H_404_7@bucket位置之后,会调用@H_404_7@keys.equals()方法去找到链表中正确的节点,最终找到要找的值对象。
10. HashMap中hash函数怎么是是实现的?
我们可以看到在hashmap中要找到某个元素,需要根据@H_404_7@key的@H_404_7@hash值来求得对应数组中的位置。 所以我们首先想到的就是把@H_404_7@hashcode对数组长度取模运算,这样一来,元素的分布相对来说是比较均匀的。但是,@H_404_7@“模@H_404_7@”运算的消耗还是比较大的,能不能找一种更快速,消耗更小的方式,我们来看看@H_404_7@JDK1.8的源码是怎么做的。
11. 拉链法导致的链表过深问题为什么不用二叉查找树代替,而选择红黑树?为什么不一直使用红黑树?
之所以选择红黑树是为了解决二叉查找树的缺陷,二叉查找树在特殊情况下会变成一条线性结构(这就跟原来使用链表结构一样了,造成很深的问题),遍历查找会非常慢。而红黑树在插入新数据后可能需要通过左旋,右旋、变色这些操作来保持平衡,引入红黑树就是为了查找数据快,解决链表查询深度的问题,我们知道红黑树属于平衡二叉树,但是为了保持“平衡@H_404_7@”是需要付出代价的,但是该代价所损耗的资源要比遍历线性链表要少,所以当长度大于@H_404_7@8的时候,会使用红黑树,如果链表长度很短的话,根本不需要引入红黑树,引入反而会慢。
12. 说说你对红黑树的见解?
每个节点非红即黑
根节点总是黑色的
如果节点是红色的,则它的子节点必须是黑色的(反之不一定)
每个叶子节点都是黑色的空节点(NIL节点)
从根节点到叶节点或空子节点的每条路径,必须包含相同数目的黑色节点(即相同的黑色高度)
13. 解决hash 碰撞还有那些办法?
开放定址法
当冲突发生时,使用某种探查技术在散列表中形成一个探查(测@H_404_7@)序列。沿此序列逐个单元地查找,直到找到给定的地址。
按照形成探查序列的方法不同,可将开放定址法区分为线性探查法、二次探查法、双重散列法等。
再哈希法
Hi = RHi(@H_404_7@key),@H_404_7@i=1,2,...k
RHi均是不同的哈希函数,即在同义词产生地址冲突时计算另一个哈希函数地址,直到不发生冲突为止。这种方法不易产生聚集,但是增加了计算时间。
缺点:增加了计算时间。
建立一个公共溢出区
假设哈希函数的值域为[0,m-1],则设向量@H_404_7@HashTable[0...m-1]为基本表,每个分量存放一个记录,另设立向量@H_404_7@OverTable[0....v]为溢出表。所有关键字和基本表中关键字为同义词的记录,不管他们由哈希函数得到的哈希地址是什么,一旦发生冲突,都填入溢出表。
简单地说就是搞个新表存冲突的元素。
链地址法(拉链法)
将所有关键字为同义词的记录存储在同一线性链表中,也就是把冲突位置的元素构造成链表。
14. 为什么要用扰动函数?
扰动函数就是解决碰撞问题。若不使用扰动函数,则直接将key.hashCode()和下面的步骤@H_404_7@2做与运算,则会有以下情景。
以初始长度16为例,@H_404_7@16-1=15。@H_404_7@2进制表示是@H_404_7@00000000 00000000 00001111。和某散列值做@H_404_7@“与@H_404_7@”操作如下,结果就是截取了最低的四位值。
这样就算散列值分布再松散,要是只取最后几位的话,碰撞也会很严重。如果散列本身做得不好,分布上成等差数列的漏洞,恰好使最后几个低位呈现规律性重复,则碰撞会更严重。
15. 扰动函数如何实现?
由扰动函数源码可知,会有以下步骤:①使用@H_404_7@key.hashCode()计算@H_404_7@hash值并赋值给变量@H_404_7@h;@H_404_7@②将@H_404_7@h向右移动@H_404_7@16位;@H_404_7@③将变量@H_404_7@h和向右移@H_404_7@16位的@H_404_7@h做异或运算(二进制位相同为@H_404_7@0,不同为@H_404_7@1)。此时得到经过扰动函数后的@H_404_7@hansh值。
右移16位正好为@H_404_7@32bit的一半,自己的高半区和低半区做异或,是为了混合原始哈希吗的高位和低位,来加大低位的随机性。而且混合后的低位掺杂了高位的部分特征,使高位的信息也被保留下来。
若直接使用key.hashCode()计算出@H_404_7@hash值,则范围为:@H_404_7@-2147483648到@H_404_7@2147483648,大约@H_404_7@40亿的映射空间。若映射得比较均匀,是很难出现碰撞的。但是这么大范围无法放入内存中,况且@H_404_7@HashMap的 初始容量为@H_404_7@16。所以必须要进行与运算取模。
16. HashMap的负载因子初始值为什么是0.75?
比如说当前的容器容量是16,负载因子是@H_404_7@0.75,16*0.75=12,也就是说,当容量达到了@H_404_7@12的时候就会进行扩容操作。
当负载因子是1.0的时候,也就意味着,只有当数组的@H_404_7@8个值(这个图表示了@H_404_7@8个)全部填充了,才会发生扩容。这就带来了很大的问题,因为@H_404_7@Hash冲突时避免不了的。当负载因子是@H_404_7@1.0的时候,意味着会出现大量的@H_404_7@Hash的冲突,底层的红黑树变得异常复杂。对于查询效率极其不利。这种情况就是牺牲了时间来保证空间的利用率。
因此一句话总结就是负载因子过大,虽然空间利用率上去了,但是时间效率降低了。
负载因子是0.5的时候,这也就意味着,当数组中的元素达到了一半就开始扩容,既然填充的元素少了,@H_404_7@Hash冲突也会减少,那么底层的链表长度或者是红黑树的高度就会降低。查询效率就会增加。
但是,兄弟们,这时候空间利用率就会大大的降低,原本存储1M的数据,现在就意味着需要@H_404_7@2M的空间。
一句话总结就是负载因子太小,虽然时间效率提升了,但是空间利用率降低了。
大致意思就是说负载因子是0.75的时候,空间利用率比较高,而且避免了相当多的@H_404_7@Hash冲突,使得底层的链表或者是红黑树的高度比较低,提升了空间效率。
17. 为什么hashMap的容量扩容时一定是2的幂次?
HashMap的容量为@H_404_7@16转化成二进制为@H_404_7@10000,@H_404_7@length-1得出的二进制为@H_404_7@01111
哈希值为1111
可以得出索引的位置为15
假设 HashMap的容量为@H_404_7@15转化成二进制为@H_404_7@1111,@H_404_7@length-1得出的二进制为@H_404_7@1110
哈希值为1111和@H_404_7@1110
那么两个索引的位置都是14,就会造成分布不均匀了,
总结:
因为2的幂-1都是@H_404_7@11111结尾的,所以碰撞几率小。
判断位置的时候代替取模运算,效率高。
18. linkedhashmap如何保证有序性?
继承自hashmap,内部增加了@H_404_7@head tail 和@H_404_7@accessorder。
19. Comparable 和 Comparator 的区别?
如果在定义类时,就实现了Comparable接口,直接在里面重写@H_404_7@compareTo()方法,如果没实现,后面在业务开发中需要有比较排序的功能,就再单独写一个类实现@H_404_7@Comparator接口,在里面重写@H_404_7@compare()方法,然后这个类需要作为参数传入到工具类@H_404_7@Collections.sort和@H_404_7@Arrays.sort方法中。最主要的区别就是一个一开始就实现,一个是后期实现。
20. Array 和 ArrayList 有何区别?什么时候更适合用 Array?
Array 可以容纳基本类型和对象,而 @H_404_7@ArrayList 只能容纳对象。
Array 是指定大小的,而 @H_404_7@ArrayList 大小是固定的,可自动扩容。
Array 没有提供 @H_404_7@ArrayList 那么多功能,比如 @H_404_7@addAll、@H_404_7@removeAll 和 @H_404_7@iterator 等。
21. ArrayList是如何扩容的?
如果通过无参构造的话,初始数组容量为 0 ,当真正对数组进行添加时,才真正分配容量。每次按照 @H_404_7@1.5 倍(位运算)的比率通过 @H_404_7@copeOf 的方式扩容。
在 JKD6 中实现是,如果通过无参构造的话,初始数组容量为@H_404_7@10,每次通过 @H_404_7@copeOf 的方式扩容后容量为原来的 @H_404_7@1.5 倍。