1、缓存一般最好使用HashMap,LinkHashMap
Hashmap 是一个 最常用的Map,它根据键的HashCode 值存储数据,根据键可以直接获取它的值,具有很快的访问速度。
HashMap最多只允许一条记录的键为Null;允许多条记录的值为Null;HashMap不支持线程的同步,即任一时刻可以有多个线程同时写HashMap;可能会导致数据的不一致。如果需要同步,可以用Collections的synchronizedMap方法使HashMap具有同步的能力.用synchronized修饰操作HashMap的语句块或方法。
public static void testHashMap(){
Map myMap = new HashMap();
init(myMap);
//HashMap的键可以为null
myMap.put(null,"ddd");
//HashMap的值可以为null
myMap.put("aaa", null);
output(myMap);
}
Hashtable 与 HashMap类似,不同的是:它不允许记录的键或者值为空;它支持线程的同步,即任一时刻只有一个线程能写Hashtable,因此也导致了Hashtale在写入时会比较慢。
public static void testHashtable(){
Map myMap = new Hashtable();
init(myMap);
//Hashtable的键不能为null
//myMap.put(null,"ddd");
//Hashtable的值不能为null
//myMap.put("aaa", null);
output(myMap);
}
TreeMap能够把它保存的记录根据键排序,默认是按升序排序,也可以指定排序的比较器,当用Iterator 遍历TreeMap时,得到的记录是排过序的,它不允许记录的键或者值为空。
。
public static void testTreeMap(){
Map myMap = new TreeMap();
init(myMap);
//TreeMap的键不能为null
//myMap.put(null,"ddd");
//TreeMap的值不能为null
//myMap.put("aaa", null);
output(myMap);
}
LinkedHashMap,键和值可以为null,可以使插入的顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的.在遍历的时候会比HashMap慢。也可以是基于访问顺序,其中一个构造器:
public LinkedHashMap (int initialCapacity, float loadFactor, boolean accessOrder);
initialCapacity 初始容量
loadFactor 加载因子,一般是 0.75f
accessOrder false 基于插入顺序true基于访问顺序(get一个元素后,这个元素被加到最后,使用了LRU 最近最少被使用的调度算法)
如 boolean accessOrder = true;
Map
m.put("1", "my"));
m.put("2", "map"));
m.put("3", "test"));
m.get("1");
m.get("2");
Log.d("tag", m);
若 accessOrder == true; 输出 {3=test, 1=my, 2=map}
accessOrder == false; 输出 {1=my, 2=map,3=test}
顾名思义LinkedHashMap是比HashMap多了一个链表的结构。与HashMap相比LinkedHashMap维护的是一个具有双重链表的HashMap,LinkedHashMap支持2中排序一种是插入排序,一种是使用排序,最近使用的会移至尾部例如 M1 M2 M3 M4,使用M3后为 M1 M2 M4 M3了,LinkedHashMap输出时其元素是有顺序的,而HashMap输出时是随机的,如果Map映射比较复杂而又要求高效率的话,最好使用LinkedHashMap,但是多线程访问的话可能会造成不同步,所以要用Collections.synchronizedMap来包装一下,从而实现同步。其实现一般为:
Map
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
public class TestLinkedHashMap {
public static void main(String args[])
{
System.out.println("*************************LinkedHashMap*************");
Map
map.put(6, "apple");
map.put(3, "banana");
map.put(2,"pear");
for (Iterator it = map.keySet().iterator();it.hasNext();)
{
Object key = it.next();
System.out.println( key+"="+ map.get(key));
}
System.out.println("*************************HashMap*************");
Map
map1.put(6, "apple");
map1.put(3, "banana");
map1.put(2,"pear");
for (Iterator it = map1.keySet().iterator();it.hasNext();)
{
Object key = it.next();
System.out.println( key+"="+ map1.get(key));
}
}
}
1. LinkedHashMap概述:
LinkedHashMap是HashMap的一个子类,它保留插入的顺序,如果需要输出的顺序和输入时的相同,那么就选用LinkedHashMap。
LinkedHashMap是Map接口的哈希表和链接列表实现,具有可预知的迭代顺序。此实现提供所有可选的映射操作,并允许使用null值和null键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。
LinkedHashMap实现与HashMap的不同之处在于,后者维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,该迭代顺序可以是插入顺序或者是访问顺序。
注意,此实现不是同步的。如果多个线程同时访问链接的哈希映射,而其中至少一个线程从结构上修改了该映射,则它必须保持外部同步。
根据链表中元素的顺序可以分为:按插入顺序的链表,和按访问顺序(调用get方法)的链表。
默认是按插入顺序排序,如果指定按访问顺序排序,那么调用get方法后,会将这次访问的元素移至链表尾部,不断访问可以形成按访问顺序排序的链表。 可以重写removeEldestEntry方法返回true值指定插入元素时移除最老的元素。
2. LinkedHashMap的实现:
对于LinkedHashMap而言,它继承与HashMap、底层使用哈希表与双向链表来保存所有元素。其基本操作与父类HashMap相似,它通过重写父类相关的方法,来实现自己的链接列表特性。下面我们来分析LinkedHashMap的源代码:
类结构:
public class LinkedHashMap
1) 成员变量:
LinkedHashMap采用的hash算法和HashMap相同,但是它重新定义了数组中保存的元素Entry,该Entry除了保存当前对象的引用外,还保存了其上一个元素before和下一个元素after的引用,从而在哈希表的基础上又构成了双向链接列表。看源代码:
//true表示按照访问顺序迭代,false时表示按照插入顺序
private final boolean accessOrder;
/**
* 双向链表的表头元素。
*/
private transient Entry
/**
* LinkedHashMap的Entry元素。
* 继承HashMap的Entry元素,又保存了其上一个元素before和下一个元素after的引用。
*/
private static class Entry
Entry
……
}
HashMap.Entry:
static class Entry
final K key;
V value;
Entry
final int hash;
Entry(int h, K k, V v, Entry
value = v;
next = n;
key = k;
hash = h;
}
}
2) 初始化:
通过源代码可以看出,在LinkedHashMap的构造方法中,实际调用了父类HashMap的相关构造方法来构造一个底层存放的table数组。如:
public LinkedHashMap(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
accessOrder = false;
}
HashMap中的相关构造方法:
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
// Find a power of 2 >= initialCapacity
int capacity = 1;
while (capacity < initialCapacity)
capacity <<= 1;
this.loadFactor = loadFactor;
threshold = (int)(capacity * loadFactor);
table = new Entry[capacity];
init();
}
我们已经知道LinkedHashMap的Entry元素继承HashMap的Entry,提供了双向链表的功能。在上述HashMap的构造器中,最后会调用init()方法,进行相关的初始化,这个方法在HashMap的实现中并无意义,只是提供给子类实现相关的初始化调用。
LinkedHashMap重写了init()方法,在调用父类的构造方法完成构造后,进一步实现了对其元素Entry的初始化操作。
void init() {
header = new Entry
header.before = header.after = header;
}
3) 存储:
LinkedHashMap并未重写父类HashMap的put方法,而是重写了父类HashMap的put方法调用的子方法void recordAccess(HashMap m) ,void addEntry(int hash, K key, V value, int bucketIndex) 和void createEntry(int hash, K key, V value, int bucketIndex),提供了自己特有的双向链接列表的实现。
HashMap.put:
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
for (Entry
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
重写方法:
void recordAccess(HashMap
LinkedHashMap
if (lm.accessOrder) {
lm.modCount++;
remove();
addBefore(lm.header);
}
}
void addEntry(int hash, K key, V value, int bucketIndex) {
// 调用create方法,将新元素以双向链表的的形式加入到映射中。
createEntry(hash, key, value, bucketIndex);
// 删除最近最少使用元素的策略定义
Entry
if (removeEldestEntry(eldest)) {
removeEntryForKey(eldest.key);
} else {
if (size >= threshold)
resize(2 * table.length);
}
}
void createEntry(int hash, K key, V value, int bucketIndex) {
HashMap.Entry
Entry
table[bucketIndex] = e;
// 调用元素的addBrefore方法,将元素加入到哈希、双向链接列表。
e.addBefore(header);
size++;
}
private void addBefore(Entry
after = existingEntry;
before = existingEntry.before;
before.after = this;
after.before = this;
}
4) 读取:
LinkedHashMap重写了父类HashMap的get方法,实际在调用父类getEntry()方法取得查找的元素后,再判断当排序模式accessOrder为true时,记录访问顺序,将最新访问的元素添加到双向链表的表头,并从原来的位置删除。由于的链表的增加、删除操作是常量级的,故并不会带来性能的损失。
HashMap.containsValue:
public boolean containsValue(Object value) {
if (value == null)
return containsNullValue();
Entry[] tab = table;
for (int i = 0; i < tab.length ; i++)
for (Entry e = tab[i] ; e != null ; e = e.next)
if (value.equals(e.value))
return true;
return false;
}
/*查找Map中是否包含给定的value,还是考虑到,LinkedHashMap拥有的双链表,在这里Override是为了提高迭代的效率。
*/
public boolean containsValue(Object value) {
// Overridden to take advantage of faster iterator
if (value==null) {
for (Entry e = header.after; e != header; e = e.after)
if (e.value==null)
return true;
} else {
for (Entry e = header.after; e != header; e = e.after)
if (value.equals(e.value))
return true;
}
return false;
}
/*该transfer()是HashMap中的实现:遍历整个表的各个桶位,然后对桶进行遍历得到每一个Entry,重新hash到newTable中,
//放在这里是为了和下面LinkedHashMap重写该法的比较,
void transfer(Entry[] newTable) {
Entry[] src = table;
int newCapacity = newTable.length;
for (int j = 0; j < src.length; j++) {
Entry
if (e != null) {
src[j] = null;
do {
Entry
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
} while (e != null);
}
}
}
*/
/**
*transfer()方法是其父类HashMap调用resize()的时候调用的方法,它的作用是表扩容后,把旧表中的key重新hash到新的表中。
*这里从写了父类HashMap中的该方法,是因为考虑到,LinkedHashMap拥有的双链表,在这里Override是为了提高迭代的效率。
*/
void transfer(HashMap.Entry[] newTable) {
int newCapacity = newTable.length;
for (Entry
int index = indexFor(e.hash, newCapacity);
e.next = newTable[index];
newTable[index] = e;
}
}
public V get(Object key) {
// 调用父类HashMap的getEntry()方法,取得要查找的元素。
Entry
if (e == null)
return null;
// 记录访问顺序。
e.recordAccess(this);
return e.value;
}
void recordAccess(HashMap
LinkedHashMap
// 如果定义了LinkedHashMap的迭代顺序为访问顺序,
// 则删除以前位置上的元素,并将最新访问的元素添加到链表表头。
if (lm.accessOrder) {
lm.modCount++;
remove();
addBefore(lm.header);
}
}
/**
* Removes this entry from the linked list.
*/
private void remove() {
before.after = after;
after.before = before;
}
/**clear链表,设置header为初始状态*/
public void clear() {
super.clear();
header.before = header.after = header;
}
5) 排序模式:
LinkedHashMap定义了排序模式accessOrder,该属性为boolean型变量,对于访问顺序,为true;对于插入顺序,则为false。
private final boolean accessOrder;
一般情况下,不必指定排序模式,其迭代顺序即为默认为插入顺序。看LinkedHashMap的构造方法,如:
public LinkedHashMap(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
accessOrder = false;
}
这些构造方法都会默认指定排序模式为插入顺序。如果你想构造一个LinkedHashMap,并打算按从近期访问最少到近期访问最多的顺序(即访问顺序)来保存元素,那么请使用下面的构造方法构造LinkedHashMap:
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
该哈希映射的迭代顺序就是最后访问其条目的顺序,这种映射很适合构建LRU缓存。LinkedHashMap提供了removeEldestEntry(Map.Entry
当有新元素加入Map的时候会调用Entry的addEntry方法,会调用removeEldestEntry方法,这里就是实现LRU元素过期机制的地方,默认的情况下removeEldestEntry方法只返回false表示元素永远不过期。
/**
* This override alters behavior of superclass put method. It causes newly
* allocated entry to get inserted at the end of the linked list and
* removes the eldest entry if appropriate.
*/
void addEntry(int hash, K key, V value, int bucketIndex) {
createEntry(hash, key, value, bucketIndex);
// Remove eldest entry if instructed, else grow capacity if appropriate
Entry
if (removeEldestEntry(eldest)) {
removeEntryForKey(eldest.key);
} else {
if (size >= threshold)
resize(2 * table.length);
}
}
/**
* This override differs from addEntry in that it doesn't resize the
* table or remove the eldest entry.
*/
void createEntry(int hash, K key, V value, int bucketIndex) {
HashMap.Entry
Entry
table[bucketIndex] = e;
e.addBefore(header);
size++;
}
protected boolean removeEldestEntry(Map.Entry
return false;
}
此方法通常不以任何方式修改映射,相反允许映射在其返回值的指引下进行自我修改。如果用此映射构建LRU缓存,则非常方便,它允许映射通过删除旧条目来减少内存损耗。
例如:重写此方法,维持此映射只保存100个条目的稳定状态,在每次添加新条目时删除最旧的条目。
private static final int MAX_ENTRIES = 100;
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > MAX_ENTRIES;
}
使用LinkedHashMap构建LRU的Cache
LinkedHashMap的特性:
Linked内部含有一个private transient Entry header;来记录元素插入的顺序或者是元素被访问的顺序。利用这个线性结构的对象,可以帮助记录entry加入的前后顺序或者记录entry被访问的 频率(最少被访问的entry靠前,最近访问的entry靠后)。大致的过程如下:
new LinkedHashMap(10, 0.75, true);
其中前面两个参数就是HashMap构造函数需要的参数,后面的true表明LinkedHashMap按照访问的次序来排序。
按照访问的次序来排序的含义:当调用LinkedHashMap的get(key)或者put(key, value)时,碰巧key在map中被包含,那么LinkedHashMap会将key对象的entry放在线性结构的最后。
按照插入顺序来排序的含义:调用get(key), 或者put(key, value)并不会对线性结构产生任何的影响。
正是因为LinkedHashMap提供按照访问的次序来排序的功能,所以它才需要改写HashMap的get(key)方法(HashMap不需要排序)和HashMap.Entry的recordAccess(HashMap)方法
public Object get(Object key) {
Entry e = (Entry)getEntry(key);
if (e == null)
return null;
e.recordAccess(this);
return e.value;
}
void recordAccess(HashMap m) {
LinkedHashMap lm = (LinkedHashMap)m;
if (lm.accessOrder) {
lm.modCount++;
remove();
addBefore(lm.header);
}
}
注 意addBefore(lm.header)是将该entry放在header线性表的最后。(参考LinkedHashMap.Entry extends HashMap.Entry 比起HashMap.Entry多了before, after两个域,是双向的)
至于put(key, value)方法, LinkedHashMap不需要去改写,用HashMap的就可以了,因为HashMap在其put(key, value)方法里边已经预留了e.recordAccess(this);
还有一个方法值得关注:
protected boolean removeEldestEntry(Map.Entry eldest) {
return false;
}
当 调用put(key, value)的时候,HashMap判断是否要自动增加map的size的作法是判断是否超过threshold, LinkedHashMap则进行了扩展,如果removeEldestEntry方法return false;(默认的实现),那么LinkedHashMap跟HashMap处理扩容的方式一致;如果removeEldestEntry返回 true,那么LinkedHashMap会自动删掉最不常用的那个entry(也就是header线性表最前面的那个)。
这会造成严重的性能问题吗?答案当然是否定的。因为在这儿的链表操作是常量级的。这也是LinkedHashMap/Set在这儿比TreeMap/Set性能更高的原因。
同样,LinkedHashMap/Set也不是thread-safe的。如果在多线程下访问,是需要进行外部同步,或者使用Collections.synchronizedMap()的方法包装成一个thread-safe的Map/Set。
特别需要注意的是,在使用“访问顺序”时,读取节点操作也是“结构变化”的操作。因为,这会改变元素遍历的顺序。所以,在使用 LinkedHashMap的iterator()方法,遍历元素时,如果其它线程有读取操作,也要进行同步。否则,也会抛出同其它fail-fast一 样的由于删除或增加操作而引起的CurrentModificationException的例外。
最后,LinkedHashMap缺省是使用插入顺序的,如何构造一个访问顺序的LinkedHashMap呢?很简单: public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) accessOrder = true 即可。
回来补充一个利用LinkedHashMap来实现LRU的Cache类,看了上面的特性,实现起来实在太简单了!
package lru;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
/**
*
*
Test
Description:
Company:Cisco CAS
Department:CAS
public class LRUCache
private LinkedHashMap
private int cacheSize = 0;
public LRUCache(int cacheSize){
this.cacheSize = cacheSize;
int hashTableCapacity = (int) Math.ceil (cacheSize / 0.75f) + 1;
cache = new LinkedHashMap
{
// (an anonymous inner class)
private static final long serialVersionUID = 1;
@Override
protected boolean removeEldestEntry (Map.Entry
{
System.out.println("size="+size());
return size () > LRUCache.this.cacheSize;
}
};
}
public V put(K key,V value){
return cache.put(key, value);
}
public V get(Object key){
return cache.get(key);
}
public static void main(String[] args) {
LRUCache
lruCache.put("1", "1");
lruCache.put("2", "2");
lruCache.put("3", "3");
lruCache.put("4", "4");
System.out.println(lruCache.get("2"));
lruCache.get("2");
lruCache.put("6", "6");
lruCache.put("5", "5");
System.out.println(lruCache.get("1"));
}
}
基于LinkedHashMap实现LRU缓存调度算法原理及应用
LinkedHashMap已经为我们自己实现LRU算法提供了便利。
LinkedHashMap继承了HashMap底层是通过Hash表+单向链表实现Hash算法,内部自己维护了一套元素访问顺序的列表。
Java代码
/**
* The head of the doubly linked list.
*/
private transient Entry
.....
/**
* LinkedHashMap entry.
*/
private static class Entry
// These fields comprise the doubly linked list used for iteration.
Entry
HashMap构造函数中回调了子类的init方法实现对元素初始化
Java代码
void init() {
header = new Entry
header.before = header.after = header;
}
LinkedHashMap中有一个属性可以执行列表元素的排序算法
Java代码
/**
* The iteration ordering method for this linked hash map: true
* for access-order, false for insertion-order.
*
* @serial
*/
private final boolean accessOrder;
注释已经写的很明白,accessOrder为true使用访问顺序排序,false使用插入顺序排序那么在哪里可以设置这个值。
Java代码
/**
* Constructs an empty LinkedHashMap instance with the
* specified initial capacity, load factor and ordering mode.
*
* @param initialCapacity the initial capacity.
* @param loadFactor the load factor.
* @param accessOrder the ordering mode - true for
* access-order, false for insertion-order.
* @throws IllegalArgumentException if the initial capacity is negative
* or the load factor is nonpositive.
*/
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
那么我们就行有访问顺序排序方式实现LRU,那么哪里LinkedHashMap是如何实现LRU的呢?
Java代码
//LinkedHashMap方法
public V get(Object key) {
Entry
if (e == null)
return null;
e.recordAccess(this);
return e.value;
}
//HashMap方法
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
for (Entry
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
当调用get或者put方法的时候,如果K-V已经存在,会回调Entry.recordAccess()方法
我们再看一下LinkedHashMap的Entry实现
Java代码
/**
* This method is invoked by the superclass whenever the value
* of a pre-existing entry is read by Map.get or modified by Map.set.
* If the enclosing Map is access-ordered, it moves the entry
* to the end of the list; otherwise, it does nothing.
*/
void recordAccess(HashMap
LinkedHashMap
if (lm.accessOrder) {
lm.modCount++;
remove();
addBefore(lm.header);
}
}
/**
* Remove this entry from the linked list.
*/
private void remove() {
before.after = after;
after.before = before;
}
/**
* Insert this entry before the specified existing entry in the list.
*/
private void addBefore(Entry
after = existingEntry;
before = existingEntry.before;
before.after = this;
after.before = this;
}
recordAccess方法会accessOrder为true会先调用remove清楚的当前首尾元素的指向关系,之后调用addBefore方法,将当前元素加入header之前。
当有新元素加入Map的时候会调用Entry的addEntry方法,会调用removeEldestEntry方法,这里就是实现LRU元素过期机制的地方,默认的情况下removeEldestEntry方法只返回false表示元素永远不过期。
Java代码
/**
* This override alters behavior of superclass put method. It causes newly
* allocated entry to get inserted at the end of the linked list and
* removes the eldest entry if appropriate.
*/
void addEntry(int hash, K key, V value, int bucketIndex) {
createEntry(hash, key, value, bucketIndex);
// Remove eldest entry if instructed, else grow capacity if appropriate
Entry
if (removeEldestEntry(eldest)) {
removeEntryForKey(eldest.key);
} else {
if (size >= threshold)
resize(2 * table.length);
}
}
/**
* This override differs from addEntry in that it doesn't resize the
* table or remove the eldest entry.
*/
void createEntry(int hash, K key, V value, int bucketIndex) {
HashMap.Entry
Entry
table[bucketIndex] = e;
e.addBefore(header);
size++;
}
protected boolean removeEldestEntry(Map.Entry
return false;
}
基本的原理已经介绍完了,那基于LinkedHashMap我们看一下是该如何实现呢?
Java代码
public static class LRULinkedHashMap
/** serialVersionUID */
private static final long serialVersionUID = -5933045562735378538L;
/** 最大数据存储容量 */
private static final int LRU_MAX_CAPACITY = 1024;
/** 存储数据容量 */
private int capacity;
/**
* 默认构造方法
*/
public LRULinkedHashMap() {
super();
}
/**
* 带参数构造方法
* @param initialCapacity 容量
* @param loadFactor 装载因子
* @param isLRU 是否使用lru算法,true:使用(按方案顺序排序);false:不使用(按存储顺序排序)
*/
public LRULinkedHashMap(int initialCapacity, float loadFactor, boolean isLRU) {
super(initialCapacity, loadFactor, true);
capacity = LRU_MAX_CAPACITY;
}
/**
* 带参数构造方法
* @param initialCapacity 容量
* @param loadFactor 装载因子
* @param isLRU 是否使用lru算法,true:使用(按方案顺序排序);false:不使用(按存储顺序排序)
* @param lruCapacity lru存储数据容量
*/
public LRULinkedHashMap(int initialCapacity, float loadFactor, boolean isLRU, int lruCapacity) {
super(initialCapacity, loadFactor, true);
this.capacity = lruCapacity;
}
/**
* @see java.util.LinkedHashMap#removeEldestEntry(java.util.Map.Entry)
*/
@Override
protected boolean removeEldestEntry(Entry
System.out.println(eldest.getKey() + "=" + eldest.getValue());
if(size() > capacity) {
return true;
}
return false;
}
}
测试代码:
Java代码
public static void main(String[] args) {
LinkedHashMap
map.put("a", "a"); //a a
map.put("b", "b"); //a a b
map.put("c", "c"); //a a b c
map.put("a", "a"); // b c a
map.put("d", "d"); //b b c a d
map.put("a", "a"); // b c d a
map.put("b", "b"); // c d a b
map.put("f", "f"); //c c d a b f
map.put("g", "g"); //c c d a b f g
map.get("d"); //c a b f g d
for (Entry
System.out.print(entry.getValue() + ", ");
}
System.out.println();
map.get("a"); //c b f g d a
for (Entry
System.out.print(entry.getValue() + ", ");
}
System.out.println();
map.get("c"); //b f g d a c
for (Entry
System.out.print(entry.getValue() + ", ");
}
System.out.println();
map.get("b"); //f g d a c b
for (Entry
System.out.print(entry.getValue() + ", ");
}
System.out.println();
map.put("h", "h"); //f f g d a c b h
for (Entry
System.out.print(entry.getValue() + ", ");
}
System.out.println();
}
其实LinkedHashMap几乎和HashMap一样,不同的是它定义了一个Entry
2、JVM 与CPU亲近的代码
多路分支逻辑代码的测试
2015-12-19 郭哲永 悠云闲语
多路分支,在开发中十分常见。做了个测试,对比了几种写法对性能的消耗。
switch模式
if-else模式
反射模式,method存于hashmap
OO模式,对象存于hashmap
OO模式,对象存于数组
作为对比,再放了一个不经判断直接调用的模式
测试代码中,是传入一个int,基于这个int,做多路判断。该int为五位数,形式为XX0YY,即自定交易码模式。XX为大类,YY为具体交易。
第一种模式,switch模式
switch(code){
case X10Y1:
X1Y1();
case X10Y2:
X1Y2();
......
}
第二种模式,if-else模式
if(code == X10Y1){
X10Y1();
}else if(code == X10Y2){
X10Y2();
}
......
第三种模式,反射模式,method存于hashmap
Method method = null;
if(methodMap.containsKey(code)){
method = methodMap.get(code);
}else{
method = this.getClass().getMethod("M"+code);
methodMap.put(code, method);
}
method.invoke(this);
第四种模式,OO模式,对象存于hashmap
提前将各个具体分支转化为对象,基于交易码存于hashmap
OBJ_MAP.put(X10Y1, new TradeHandlerX10Y1());
调用
TradeHandler trdHandler = trdMap.get(code);
trdHandler.exec();
第五种模式,OO模式,对象存于数组
观察到交易码可以拆分为XX和0YY,提前构建二维数组,存储对象
OBJ_ARRAY[XX] = new TradeHandler[2];
OBJ_ARRAY[XX][0YY] = new TradeHandlerX10Y1();
调用
int idx1 = code / 1000;
int idx2 = code % 1000;
TradeHandler trdHandler = OBJ_ARRAY[idx1][idx2];
trdHandler.exec();
结果
switch cost = [4008.333702]ms
if-else cost = [4013.53896]ms
reflect cost = [49526.138794]ms
oo cost = [5299.526924]ms
oo2 cost = [3147.320315]ms----这个性价比比较好
direct cost = [2837.397087]ms
结论
数组形式存储并调用,得到了近似直接调用的结果。这样写,代码拆分得更细,一个方法的长度也可控,也更面向对象,效率也能保障。是一个不错的选择。
switch模式和if-else模式,单就效率而言,并不低。但代码形式上,就有点差距,特别是分支多的情况,可能需要滚屏才能看全。
反射模式预计到慢了,但没想到能慢成这样,整整高了一个数量级。
OO加hash是我以为会比switch和if-else快的,从测试发现,并没有,反而慢。查看bytecode可以发现,指令比switch和if-else模式要复杂,存在对象多次调用,慢的确在所难免。
单纯看这个测试,意义并不是很大,因为只有大量调用才能看出之间的差距。这个例子中,我执行了1000000000L次。
如果if-else用法太多的情况下,这种时候我更喜欢使用MAP+反射的方式来避免使用if-else, 比如需要通过传入参数的值,来判断需要执行的操作,我更偏向选择下面的方式:
Java代码 收藏代码
private static final Map
static {
optionsMap = new HashMap
optionsMap.put("delete", "doDeleteCat");
optionsMap.put("modify", "doModifyCat");
optionsMap.put("create", "doCreateCat");
optionsMap.put("addCondMap", "doAddCondMap");
optionsMap.put("delCondMap", "doDelCondMap");
}
public void execute(RunData rundata, TemplateContext context) {
rundata.getModuleInfo().setLayoutTemplate("emptyLayout.vm");
boolean rs = false;
String optionMethod = optionsMap.get(rundata.getParameters().getString("option"));
if (StringUtils.isNotBlank(optionMethod)) {
try {
Method method = this.getClass()
.getMethod(optionMethod, RunData.class);
rs = (Boolean) method.invoke(this, rundata);
} catch (Throwable e) {
this.getLogger().error("invoke method:" + optionMethod + " failure!", e);
}
}
context.put("status", rs ? STATUS_SECCESS : STATUS_FAILURE);
}
public boolean doCreateCat(RunData rundata) {
long parentId = rundata.getParameters().getLong("parent_id", -1L);
int nsId = rundata.getParameters().getInt("ns_id", -1);
String catName = rundata.getParameters().getString("cat_name", "");
return vtdCategoriesAdminAO.createVtdCategory(nsId, parentId, catName);
}
.....
在这里,即使以后增加操作也只需要改变MAP不需要对调用代码进行任何修改
3、Get,set多次使用时需定义成变量,量大成多,时间上,对于我们来说,肉再小也是肉,积少成多
当HashMap使用前如果确定好期内既定的数量的话,需new时定义好其内值数量,其内存放的数量依此递增的初始为16…32…64…128…256…
4、StringBuffer与StringBuilder区别,新代码一般建议使用StringBuilder,线程不安全,但速度快,而StringBuffer线程安全,时间上没StringBuilder快。
5、日志的输出,打印针对性的重要信息,不需直接打印对象,否则,对象中的属性等相关会遵循for循环打印内容,易耗时间。
6、代码不允许用stdout这些打印,logback中设置也不能有stdout这些易耗时间的设置
7、Jdk1.6和jdk1.7新特性比较好的,多关注,比如:Queue,Quece这些
8、Thread.sleep(long time)方法时,如果此方法用到某种循环中时不要固定其睡眠时间,而应该以梯度数值作为sleep时间
比如:
逻辑代码实现机制时,当不满足某个条件时,代码走Thread.sleep(time),time数值应该初始为1ms,第二次进入调用该方法时,Thread.sleep(2的(2-1)次方值),依此类推。