数组
简述
数组是一种效率最高的存储和随机访问对象引用的一个简单的线性序列,虽然访问快速,但为之付出的代价是数组的大小固定,并且在其生命周期中不可改变。数组与其他容器之间的区别在于:效率、类型和保存基本类型的能力。但随着自动包装机制的出现,容器已经可以与数组几乎一样方便,而数组仅存的优点就是效率。
应该“优先选择容器而不是数组”,只有在已经证明性能称为问题(并且切换到数组对性能提高有所帮助)时,你才应该将程序重构为使用数组
数组标识符就是一个引用,用以保存指向其他对象的引用,数组的一个单元指向在堆中创建的一个真实对象。对像数组与基本类型数组的区别在于,对象保存的是引用,基本类型数组保存的是基本类型的值。
数组的 length 属性,表示数组的大小,而不是实际保存的元素个数。新生成的对象数组,会被默认的初始化为 null,基本类型数组则会初始化为对应基本类型的默认值,对于对象数组,可以检测其中的引用是否为 null,进而确定某个位置是否存在对象。当数组不被需要时,垃圾回收器会负责清理数组,否则将一直存在。
实用数组方法
System.arraycopy():复制一个数组比用 for 循环复制要快得多,且该方法对所有类型做了重载。当复制对象数组时,只是复制对象的引用(属浅拷贝)。
Arrays.asList(T… a):将多个元素转换成 List 列表,底层表示的还是数组,当尝试进行 add()、delete() 操作时将在运行时报错。
Arrays.sort(T[] a, Comparator<? super T> c):按照给定的比较器对指定的数组进行排序。
Arrays.binarySearch(T[] a,T key, Comparator<? super T> c):对一个有序的数组采用二分查找获取对象下标,如果数组存在重复元素,可能返回的不精确。<span color=”red”> 注:如果使用了 Comparator 排序了一个对象数组,则调用此方法时传入的 Comparator 必须是同一个。</span>
Comparator 比较器
comparator 用于指定元素的排列顺序,在常见的数据类型中已经被默认实现,对于需要按照某种规则进行排序的时候,提供 Comparator 或者实现 java.lang.Comparable 接口。
class DemoComparator implements Comparator<Demo> {
@Override
public int compare(Demo o1, Demo o2) {
if (o1.value == o2.value) {
return 0;
}
return o1.value < o2.value ? 1 : -1;
}
}
容器
Collection
一个独立元素的序列,这些元素都服从一条或多条规则,提供了存放一组对象的方式。List 必须按照插入的顺序保存元素,而 Set 不能有重复元素。Queue 按照队列排队规则来确定对象产生的顺序。
PriorityQueue:优先级队列,声明下一个弹出最需要的元素(具有最高的优先级),通过默认排序或提供 Comparator。当调用 peek()、poll()、remove() 方法时,获取的元素是队列中优先级最高的。
List
类
描述
ArrayList
常用于随机访问元素,但是在 List 的中间插入和删除元素时较慢。
LinkedList
对中间插入和删除元素的操作中提供了优化的顺序列表,在随机访问方面相对比较慢,但是它的特性集较 ArrayList 更大。
ListIterator:Iterator 的子类,只用于对各种 List 类的访问,可以实现双向移动。hasNext() / next()、hasPrevious() / previous()
对于随机访问的 get/set 操作,背后有数组支持的 List 比 ArrayList 稍快一点,但是对于 LinkedList,将会变得很慢,因为它不是针对随机访问操作而设计的。最佳的做法是将 ArrayList 作为默认首选,当因为经常插入和删除操作而性能下降时,才去选择 LinkedList。如果使用的是固定数量的元素,既可以选择 List,也可以选择数组。
Set
类
描述
Set (interface)
存入 Set 的每一个元素都必须要唯一,因为 Set 不允许重复元素。加入 Set 的元素必须定义 equals() 方法以确保对象的唯一性。Set 和 Collection 有完全一样的接口。Set 接口不保证维护元素的次序。
HashSet
为快速查找而设计的 Set。存入 HashSet 的元素必须定义 hashCode()。
TreeSet
保持次序的 Set,底层为数据结构。使用它可以从 Set 中提取有序的序列。元素必须实现 Comparator 接口。
LinkedHashSet
具有 HashSet 的查询速度,且内部使用链表维护元素的顺序(插入的次序)。于是在使用迭代遍历 Set 时,结果会按元素插入的次序显示。元素必须定义 hashCode() 方法。
HashSet 以某种顺序保存元素,LinkedHashSet 按照元素插入的顺序保存元素,而 TressSet 按照排序维护元素。HashSet 的性能基本上总是比 TreeSet 好,特别在添加和查询元素时。TreeSet 存在的唯一原因是它维持元素的排序顺序,因此迭代的时候,TreeSet 通常比用 HashSet 要快。
Map
Map 的各个实现类的行为特性的不同在于,效率、键值对的保存及呈现次序、对象的保存周期、映射表如何在多线程程序中工作和判定”键“等价的策略等方面。
类
描述
HashMap
Map 基于散列表的实现(取代 HashTable)。插入和查询“键值对”的开销是固定的。可以通过构造器设置容量和负载因子,以调整容器的性能。
LinkedHashMap
类似于 HashMap,但是迭代遍历它时,取得“键值对”的顺序就是其插入次序,或者是最近最少使用(LRU)的次序。只比 HashMap 慢一点;而在迭代访问时反而快,因为它使用链表维护内部次序。
TreeMap
基于红黑树的实现。查看“键”或“键值对”时,它们会被排序。TreeMap 的特点在于,所得到的的结果是经过排序的。TreeSet 是唯一一个带有 subMap() 方法的 Map,它可以返回一个子树。
WeekHashMap
弱键映射,允许释放映射所指向的对象,这是为解决某类特殊问题而设计的。如果映射 之外没有引用指向某个“键”,则此“键”可以被垃圾收集器回收。
ConcurrentHashMap
一种线程安全的 Map,它不涉及同步加锁。
IdentityHashMap
使用 == 代替 equals() 对“键”进行比较的散列映射。专为解决特殊问题而设计的
使用 Map 时,首选 HashMap,TreeMap 维护着散列数据结构的同时还要维护链表,在迭代的时候速度快,其余方面比 HashMap 要慢。插入操作随着 Map 尺寸的变大,速度会明显变慢(除了 IdentityHashMap),但是查找所耗费的代价比插入小很多。对于插入操作,由于维护链表所带来的的开销,导致 LinkedHashSet 比 HashSet 的代价高很多。
HashMap 的性能因子:
容量:表中的桶位数。
初始容量:表在创建时所拥有的桶位数。
尺寸:表中当前存储的项数。
负载因子:尺寸 / 容量。空表的负载因子为 0,半满表为 0.5。负载越轻的表产生的冲突性越小,HashMap 默认负载因子为 0.75,达到默认值时,将进行散列。
散列与散列码
散列码是一个无符号的十六进制数,散列的目的在于使用一个对象来查找另一个对象;散列的价值在于能够快速进行查询。
在 Map 上的实现:通过键对象生成一个数字,这个数字就是散列码。将散列码作为数组的下标,该数组的一个单元指向一个链表,链表上的一个节点就是一个 Entry 对象。数组的容量是固定的,不同的键可以产生相同的下标,这将导致“冲突”。
Example:参考 Java 编程思想 P493
static final int SIZE = 1024;
LinkedList<Entry<K, V>>[] buckets = new LinkedList[SIZE];
public V put(K key, V value) {
V oldValue = null;
// 对散列值取余获取数组下标
int index = Math.abs(key.hashCode()) % SIZE;
if (buckets[index] == null) {
buckets[index] = new LinkedList<Entry<K,V>>();
}
// 获取下标所指向的链表
LinkedList<Entry<K, V>> bucket = buckets[index];
Entry<K, V> pair = new Entry<>();
boolean found = false;
// 获取迭代器进行迭代,判断是否存在,存在则覆盖
ListIterator<Entry<K, V>> it = bucket.listIterator();
while (it.hasNext()) {
Entry<K, V> iPair = it.next();
if (iPair.getKey().equals(key)) {
oldValue = iPair.getValue();
it.set(pair);
found = true;
break;
}
}
// 没有找到就添加
if (!found) {
buckets[index].add(pair);
}
return oldValue;
}
equals()
自反性。
对称性。
传递性。
一致性。
对任何不是 null 的 x,x.equals(null) 一定返回 false。
hashcode()
对同一个对象调用 hashcode() 都应该生成同样的值。
基于对象的内容生成散列码,让其富有意义。
散列码不必独一无二,应关注生成速度。
通过 hashCode() 和 equals(),必须能够完全确定对象的身份。
好的 hashCode() 应该产生分布均匀的散列码。赋予一个非零常量值。
public int hashCode() {
int result = 19;
return result * field.hashCode();
}