重善奉行 2020-06-10
? 同步性:
Vector 是线程安全的,也就是说是它的方法之间是线程同步的,而 ArrayList 是线
程序不安全的,它的方法之间是线程不同步的。如果只有一个线程会访问到集合,那
最好是使用 ArrayList,因为它不考虑线程安全,效率会高些;如果有多个线程会访问
到集合,那最好是使用 Vector,因为不需要我们自己再去考虑和编写线程安全的代
码。
备注:对于 Vector&ArrayList、Hashtable&HashMap,要记住线程安全的问题,
记住 Vector 与 Hashtable 是旧的,是 java 一诞生就提供了的,它们是线程安全
的,ArrayList 与 HashMap 是 java2 时才提供的,它们是线程不安全的。所以,我
们讲课时先讲老的。
? 数据增长:
ArrayList 与 Vector 都有一个初始的容量大小,当存储进它们里面的元素的个数超
过了容量时,就需要增加 ArrayList 与 Vector 的存储空间,每次要增加存储空间
时,不是只增加一个存储单元,而是增加多个存储单元,每次增加的存储单元的个数
在内存空间利用与程序效率之间要取得一定的平衡。Vector 默认增长为原来两倍,而
ArrayList 的增长策略在文档中没有明确规定(从源代码看到的是增长为原来的 1.5
倍)。ArrayList 与 Vector 都可以设置初始的空间大小,Vector 还可以设置增长的
空间大小,而 ArrayList 没有提供设置增长空间的方法。
总结:即 Vector 增长原来的一倍,ArrayList 增加原来的 0.5 倍。
说说 ArrayList,Vector, LinkedList 的存储性能和特性。
ArrayList 和 Vector 都是使用数组方式存储数据,此数组元素数大于实际存储的数
据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组
元素移动等内存操作,所以索引数据快而插入数据慢,Vector 由于使用了
synchronized 方法(线程安全)。
通常性能上较 ArrayList 差,而 LinkedList 使用双向链表实现存储,按序号索引数
据需要进行前向或后向遍历,但是插入数据时只需要记录本项的前后项即可,所以插
入速度较快 。
ArrayList 在查找时速度快,LinkedList 在插入与删除时更具优势.
快速失败 (fail-fast) 和安全失败 (fail-safe) 的区别是什么?
Iterator 的安全失败是基于对底层集合做拷贝,因此,它不受源集合上修改的影响。
java.util 包下面的所有的集合类都是快速失败的,而 java.util.concurrent 包下面的
所有的类都是安全失败的。快速失败的迭代器会抛出
ConcurrentModificationException 异常,而安全失败的迭代器永远不会抛出这样的
异常。
hashmap 的数据结构。
在 java 编程语言中,最基本的结构就是两种,一个是数组,另外一个是模拟指针
(引用),所有的数据结构都可以用这两个基本结构来构造的,hashmap 也不例
外。Hashmap 实际上是一个数组和链表的结合体(在数据结构中,一般称之为 “链
表散列 “)
enter image description here
HashMap 的工作原理是什么?
Java 中的 HashMap 是以键值对 (key-value) 的形式存储元素的。HashMap 需要
一个 hash 函数,它使用 hashCode()和 equals()方法来向集合 / 从集合添加和检
索元素。当调用 put() 方法的时候,HashMap 会计算 key 的 hash 值,然后把键
值对存储在集合中合适的索引上。 如果 key 已经存在了,value 会被更新成新值。
HashMap 的一些重要的特性是它的容量 (capacity),负载因子 (load factor) 和扩
容极限(threshold resizing)。
Hashmap 什么时候进行扩容呢?
当 hashmap 中的元素个数超过数组大小 loadFactor 时,就会进行数组扩容,
loadFactor 的默认值为 0.75,也就是说,默认情况下,数组大小为 16,那么当
hashmap 中元素个数超过 160.75=12 的时候,就把数组的大小扩展为 216=32,
即扩大一倍,然后重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操
作,所以如果我们已经预知 hashmap 中元素的个数,那么预设元素的个数能够有效
的提高 hashmap 的性能。比如说,我们有 1000 个元素 new HashMap(1000),
但是理论上来讲 new HashMap(1024) 更合适,不过上面 annegu 已经说过,即使
是 1000,hashmap 也自动会将其设置为 1024。 但是 new HashMap(1024) 还
不是更合适的,因为 0.751000 < 1000, 也就是说为了让 0.75 size > 1000, 我们
必须这样 new HashMap(2048) 才最合适,既考虑了 & 的问题,也避免了 resize
的问题。
List 表示有先后顺序的集合, 注意,不是那种按年龄、按大小、按价格之类的排序。
当我们多次调用 add(Obj e) 方法时,每次加入的对象就像火车站买票有排队顺序一
样,按先来后到的顺序排序。有时候,也可以插队,即调用 add(int index,Obj e) 方
法,就可以指定当前对象在集合中的存放位置。一个对象可以被反复存储进 List 中,
每调用一次 add 方法,这个对象就被插入进集合中一次,其实,并不是把这个对象
本身存储进了集合中,而是在集合中用一个索引变量指向这个对象,当这个对象被
add 多次时,即相当于集合中有多个索引指向了这个对象,如图 x 所示。List 除了
可以以 Iterator 接口取得所有的元素,再逐一遍历各个元素之外,还可以调用
get(index i) 来明确说明取第几个。
Map 与 List 和 Set 不同,它是双列的集合,其中有 put 方法,定义如下:
put(obj key,obj value),每次存储时,要存储一对 key/value,不能存储重复的
key,这个重复的规则也是按 equals 比较相等。取则可以根据 key 获得相应的
value,即 get(Object key) 返回值为 key 所对应的 value。另外,也可以获得所有
的 key 的结合,还可以获得所有的 value 的结合,还可以获得 key 和 value 组合
成的 Map.Entry 对象的集合。
List 以特定次序来持有元素,可有重复元素。Set 无法拥有重复元素, 内部排序。
Map 保存 key-value 值,value 可多值。
HashSet 按照 hashcode 值的某种运算方式进行存储,而不是直接按 hashCode
值的大小进行存储。例如,"abc" ---> 78,"def" ---> 62,"xyz" ---> 65 在hashSet 中的存储顺序不是 62,65,78,这些问题感谢以前一个叫崔健的学员提出,
最后通过查看源代码给他解释清楚,看本次培训学员当中有多少能看懂源码。
LinkedHashSet 按插入的顺序存储,那被存储对象的 hashcode 方法还有什么作用
呢?学员想想! hashset 集合比较两个对象是否相等,首先看 hashcode 方法是否相
等,然后看 equals 方法是否相等。new 两个 Student 插入到 HashSet 中,看
HashSet 的 size,实现 hashcode 和 equals 方法后再看 size。
同一个对象可以在 Vector 中加入多次。往集合里面加元素,相当于集合里用一根绳
子连接到了目标对象。往 HashSet 中却加不了多次的。
Set 里的元素是不能重复的,元素重复与否是使用 equals() 方法进行判断的。
equals() 和 == 方法决定引用值是否指向同一对象 equals() 在类中被覆盖,为的是
当两个分离的对象的内容和类型相配的话,返回真值。
两个对象值相同 (x.equals(y) == true),但却可有不同的 hash code,这句话对不对?
对。如果对象要保存在 HashSet 或 HashMap 中,它们的 equals 相等,那么,它
们的 hashcode 值就必须相等。
如果不是要保存在 HashSet 或 HashMap,则与 hashcode 没有什么关系了,这时
候 hashcode 不等是可以的,例如 arrayList 存储的对象就不用实现 hashcode,当
然,我们没有理由不实现,通常都会去实现的。
heap 和 stack 有什么区别。
Java 的内存分为两类,一类是栈内存,一类是堆内存。栈内存是指程序进入一个方法
时,会为这个方法单独分配一块私属存储空间,用于存储这个方法内部的局部变量,
当这个方法结束时,分配给这个方法的栈会释放,这个栈中的变量也将随之释放。
堆是与栈作用不同的内存,一般用于存放不放在当前方法栈中的那些数据,例如,使
用 new 创建的对象都放在堆里,所以,它不会随方法的结束而消失。方法中的局部
变量使用 final 修饰后,放在堆中,而不是栈中。