LinkedHashMap应用实现LRU:
import java.util.LinkedHashMap;
import java.util.Collection;
import java.util.Map;
import java.util.ArrayList;
/**
* An LRU cache, based on <code>LinkedHashMap</code>.
*
* <p>
* This cache has a fixed maximum number of elements (<code>cacheSize</code>).
* If the cache is full and another entry is added, the LRU (least recently used) entry is dropped.
*
* <p>
* This class is thread-safe. All methods of this class are synchronized.
*
* <p>
* Author: Christian d'Heureuse, Inventec Informatik AG, Zurich, Switzerland<br>
* Multi-licensed: EPL / LGPL / GPL / AL / BSD.
*/
public class LRUCache<K,V> {
private static final float hashTableLoadFactor = 0.75f;
private LinkedHashMap<K,V> map;
private int cacheSize;
/**
* Creates a new LRU cache.
* @param cacheSize the maximum number of entries that will be kept in this cache.
*/
public LRUCache (int cacheSize) {
this.cacheSize = cacheSize;
int hashTableCapacity = (int)Math.ceil(cacheSize / hashTableLoadFactor) + 1;
map = new LinkedHashMap<K,V>(hashTableCapacity, hashTableLoadFactor, true) {
// (an anonymous inner class)
private static final long serialVersionUID = 1;
@Override protected boolean removeEldestEntry (Map.Entry<K,V> eldest) {
return size() > LRUCache.this.cacheSize; }}; }
/**
* Retrieves an entry from the cache.<br>
* The retrieved entry becomes the MRU (most recently used) entry.
* @param key the key whose associated value is to be returned.
* @return the value associated to this key, or null if no value with this key exists in the cache.
*/
public synchronized V get (K key) {
return map.get(key); }
/**
* Adds an entry to this cache.
* The new entry becomes the MRU (most recently used) entry.
* If an entry with the specified key already exists in the cache, it is replaced by the new entry.
* If the cache is full, the LRU (least recently used) entry is removed from the cache.
* @param key the key with which the specified value is to be associated.
* @param value a value to be associated with the specified key.
*/
public synchronized void put (K key, V value) {
map.put (key, value); }
/**
* Clears the cache.
*/
public synchronized void clear() {
map.clear(); }
/**
* Returns the number of used entries in the cache.
* @return the number of entries currently in the cache.
*/
public synchronized int usedEntries() {
return map.size(); }
/**
* Returns a <code>Collection</code> that contains a copy of all cache entries.
* @return a <code>Collection</code> with a copy of the cache content.
*/
public synchronized Collection<Map.Entry<K,V>> getAll() {
return new ArrayList<Map.Entry<K,V>>(map.entrySet()); }
} // end class LRUCache
// Test routine for the LRUCache class.
public static void main (String[] args) {
LRUCache<String,String> c = new LRUCache<String, String>(3);
c.put ("1", "one"); // 1
c.put ("2", "two"); // 2 1
c.put ("3", "three"); // 3 2 1
c.put ("4", "four"); // 4 3 2
if (c.get("2") == null) throw new Error(); // 2 4 3
c.put ("5", "five"); // 5 2 4
c.put ("4", "second four"); // 4 5 2
// Verify cache content.
if (c.usedEntries() != 3) throw new Error();
if (!c.get("4").equals("second four")) throw new Error();
if (!c.get("5").equals("five")) throw new Error();
if (!c.get("2").equals("two")) throw new Error();
// List cache content.
for (Map.Entry<String, String> e : c.getAll())
System.out.println (e.getKey() + " : " + e.getValue()); }
双链表 + hashtable实现原理:
public class LRUCache {
private int cacheSize;
private Hashtable nodes;//缓存容器
private int currentSize;
private Entry first;//链表头
private Entry last;//链表尾
public LRUCache(int i) {
currentSize = 0;
cacheSize = i;
nodes = new Hashtable(i);//缓存容器
}
/**
* 获取缓存中对象,并把它放在最前面
*/
public Entry get(Object key) {
Entry node = nodes.get(key);
if (node != null) {
moveToHead(node);
return node;
} else {
return null;
}
}
/**
* 添加 entry到hashtable, 并把entry
*/
public void put(Object key, Object value) {
//先查看hashtable是否存在该entry, 如果存在,则只更新其value
Entry node = nodes.get(key);
if (node == null) {
//缓存容器是否已经超过大小.
if (currentSize >= cacheSize) {
nodes.remove(last.key);
removeLast();
} else {
currentSize++;
}
node = new Entry();
}
node.value = value;
//将最新使用的节点放到链表头,表示最新使用的.
moveToHead(node);
nodes.put(key, node);
}
/**
* 将entry删除, 注意:删除操作只有在cache满了才会被执行
*/
public void remove(Object key) {
Entry node = nodes.get(key);
//在链表中删除
if (node != null) {
if (node.prev != null) {
node.prev.next = node.next;
}
if (node.next != null) {
node.next.prev = node.prev;
}
if (last == node)
last = node.prev;
if (first == node)
first = node.next;
}
//在hashtable中删除
nodes.remove(key);
}
/**
* 删除链表尾部节点,即使用最后 使用的entry
*/
private void removeLast() {
//链表尾不为空,则将链表尾指向null. 删除连表尾(删除最少使用的缓存对象)
if (last != null) {
if (last.prev != null)
last.prev.next = null;
else
first = null;
last = last.prev;
}
}
/**
* 移动到链表头,表示这个节点是最新使用过的
*/
private void moveToHead(Entry node) {
if (node == first)
return;
if (node.prev != null)
node.prev.next = node.next;
if (node.next != null)
node.next.prev = node.prev;
if (last == node)
last = node.prev;
if (first != null) {
node.next = first;
first.prev = node;
}
first = node;
node.prev = null;
if (last == null)
last = first;
}
/*
* 清空缓存
*/
public void clear() {
first = null;
last = null;
currentSize = 0;
}
}
class Entry {
Entry prev;//前一节点
Entry next;//后一节点
Object value;//值
Object key;//键
}
/**
* A ring buffer (circular buffer, FIFO) for bytes.
*
* <p>All methods of this class are non-blocking and not synchronized (not thread-safe).
*/
public class ByteRingBuffer {
private byte[] rBuf; // ring buffer data
private int rBufSize; // ring buffer size
private int rBufPos; // position of first (oldest) data byte within the ring buffer
private int rBufUsed; // number of used data bytes within the ring buffer
/**
* Creates a ring buffer.
*/
public ByteRingBuffer (int size) {
if (size <= 0) {
throw new IllegalArgumentException(); }
rBufSize = size;
rBuf = new byte[rBufSize]; }
/**
* Resizes the ring buffer by preserving it's data.
*
* <p>If the new size is not enough to keep all used data in the ring buffer,
* the excess old data is discarded.
*/
public void resize (int newSize) {
if (newSize <= 0) {
throw new IllegalArgumentException(); }
if (newSize < rBufUsed) { // if new buffer is too small to contain all data
discard(rBufUsed - newSize); } // discard oldest data
byte[] newBuf = new byte[newSize];
int newBufUsed = read(newBuf, 0, newSize); // transfer data to new buffer
rBuf = newBuf;
rBufSize = newSize;
rBufPos = 0;
rBufUsed = newBufUsed; }
/**
* Returns the size of the ring buffer.
*/
public int getSize() {
return rBufSize; }
/**
* Returns the number of free bytes within the ring buffer.
*/
public int getFree() {
return rBufSize - rBufUsed; }
/**
* Returns the number of used bytes within the ring buffer.
*/
public int getUsed() {
return rBufUsed; }
/**
* Clears the ring buffer.
*/
public void clear() {
rBufPos = 0;
rBufUsed = 0; }
/**
* Discards the oldest <code>len</code> bytes within the ring buffer.
* This has the same effect as calling <code>read(new byte[len], 0, len)</code>.
*
* @param len
* The number of bytes to be discarded.
*/
public void discard (int len) {
if (len < 0) {
throw new IllegalArgumentException(); }
int trLen = Math.min(len, rBufUsed);
rBufPos = clip(rBufPos + trLen);
rBufUsed -= trLen; }
/**
* Writes data to the ring buffer.
*
* @return
* The number of bytes written.
* This is guaranteed to be <code>min(len, getFree())</code>.
*/
public int write (byte[] buf, int pos, int len) {
if (len < 0) {
throw new IllegalArgumentException(); }
if (rBufUsed == 0) {
rBufPos = 0; } // (speed optimization)
int p1 = rBufPos + rBufUsed;
if (p1 < rBufSize) { // free space in two pieces
int trLen1 = Math.min(len, rBufSize - p1);
append(buf, pos, trLen1);
int trLen2 = Math.min(len - trLen1, rBufPos);
append(buf, pos + trLen1, trLen2);
return trLen1 + trLen2; }
else { // free space in one piece
int trLen = Math.min(len, rBufSize - rBufUsed);
append(buf, pos, trLen);
return trLen; }}
/**
* Writes data to the ring buffer.
*
* <p>Convenience method for: <code>write(buf, 0, buf.length)</code>
*/
public int write (byte[] buf) {
return write(buf, 0, buf.length); }
private void append (byte[] buf, int pos, int len) {
if (len == 0) {
return; }
if (len < 0) {
throw new AssertionError(); }
int p = clip(rBufPos + rBufUsed);
System.arraycopy(buf, pos, rBuf, p, len);
rBufUsed += len; }
/**
* Reads data from the ring buffer.
*
* @return
* The number of bytes read.
* This is guaranteed to be <code>min(len, getUsed())</code>.
*/
public int read (byte[] buf, int pos, int len) {
if (len < 0) {
throw new IllegalArgumentException(); }
int trLen1 = Math.min(len, Math.min(rBufUsed, rBufSize - rBufPos));
remove(buf, pos, trLen1);
int trLen2 = Math.min(len - trLen1, rBufUsed);
remove(buf, pos + trLen1, trLen2);
return trLen1 + trLen2; }
/**
* Reads data from the ring buffer.
*
* <p>Convenience method for: <code>read(buf, 0, buf.length)</code>
*/
public int read (byte[] buf) {
return read(buf, 0, buf.length); }
private void remove (byte[] buf, int pos, int len) {
if (len == 0) {
return; }
if (len < 0) {
throw new AssertionError(); }
System.arraycopy(rBuf, rBufPos, buf, pos, len);
rBufPos = clip(rBufPos + len);
rBufUsed -= len; }
private int clip (int p) {
return (p < rBufSize) ? p : (p - rBufSize); }
}
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.ByteBuffer;
import java.security.SecureClassLoader;
import java.util.Enumeration;
import java.util.Vector;
/**
* A class loader that combines multiple class loaders into one.<br>
* The classes loaded by this class loader are associated with this class loader,
* i.e. Class.getClassLoader() points to this class loader.
* <p>
* Author Christian d'Heureuse, Inventec Informatik AG, Zurich, Switzerland, www.source-code.biz<br>
* License: LGPL, http://www.gnu.org/licenses/lgpl.html<br>
* Please contact the author if you need another license.
*/
public class JoinClassLoader extends ClassLoader {
private ClassLoader[] delegateClassLoaders;
public JoinClassLoader (ClassLoader parent, ClassLoader... delegateClassLoaders) {
super (parent);
this.delegateClassLoaders = delegateClassLoaders; }
protected Class<?> findClass (String name) throws ClassNotFoundException {
// It would be easier to call the loadClass() methods of the delegateClassLoaders
// here, but we have to load the class from the byte code ourselves, because we
// need it to be associated with our class loader.
String path = name.replace('.', '/') + ".class";
URL url = findResource(path);
if (url == null) {
throw new ClassNotFoundException (name); }
ByteBuffer byteCode;
try {
byteCode = loadResource(url); }
catch (IOException e) {
throw new ClassNotFoundException (name, e); }
return defineClass(name, byteCode, null); }
private ByteBuffer loadResource (URL url) throws IOException {
InputStream stream = null;
try {
stream = url.openStream();
int initialBufferCapacity = Math.min(0x40000, stream.available() + 1);
if (initialBufferCapacity <= 2)
initialBufferCapacity = 0x10000;
else
initialBufferCapacity = Math.max(initialBufferCapacity, 0x200);
ByteBuffer buf = ByteBuffer.allocate(initialBufferCapacity);
while (true) {
if (!buf.hasRemaining()) {
ByteBuffer newBuf = ByteBuffer.allocate(2*buf.capacity());
buf.flip();
newBuf.put (buf);
buf = newBuf; }
int len = stream.read(buf.array(), buf.position(), buf.remaining());
if (len <= 0) break;
buf.position (buf.position()+len); }
buf.flip();
return buf; }
finally {
if (stream != null) stream.close(); }}
protected URL findResource (String name) {
for (ClassLoader delegate : delegateClassLoaders) {
URL resource = delegate.getResource(name);
if (resource != null) return resource; }
return null; }
protected Enumeration<URL> findResources (String name) throws IOException {
Vector<URL> vector = new Vector<URL>();
for (ClassLoader delegate : delegateClassLoaders) {
Enumeration<URL> enumeration = delegate.getResources(name);
while (enumeration.hasMoreElements()) {
vector.add (enumeration.nextElement()); }}
return vector.elements(); }
} // end class JoinClassLoader
How to convert a number to words (in Java)
// This snippet may be used freely, as long as the authorship note remains in the source code.
private static final String[] lowNames = {
"zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten",
"eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen"};
private static final String[] tensNames = {
"twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety"};
private static final String[] bigNames = {
"thousand", "million", "billion"};
/**
* Converts an integer number into words (american english).
* @author Christian d'Heureuse, Inventec Informatik AG, Switzerland, www.source-code.biz
**/
public static String convertNumberToWords (int n) {
if (n < 0) {
return "minus " + convertNumberToWords(-n); }
if (n <= 999) {
return convert999(n); }
String s = null;
int t = 0;
while (n > 0) {
if (n % 1000 != 0) {
String s2 = convert999(n % 1000);
if (t > 0) {
s2 = s2 + " " + bigNames[t-1]; }
if (s == null) {
s = s2; }
else {
s = s2 + ", " + s; }}
n /= 1000;
t++; }
return s; }
// Range 0 to 999.
private static String convert999 (int n) {
String s1 = lowNames[n / 100] + " hundred";
String s2 = convert99(n % 100);
if (n <= 99) {
return s2; }
else if (n % 100 == 0) {
return s1; }
else {
return s1 + " " + s2; }}
// Range 0 to 99.
private static String convert99 (int n) {
if (n < 20) {
return lowNames[n]; }
String s = tensNames[n / 10 - 2];
if (n % 10 == 0) {
return s; }
return s + "-" + lowNames[n % 10]; }
/**
* Parses an URL query string and returns a map with the parameter values.
* The URL query string is the part in the URL after the first '?' character up
* to an optional '#' character. It has the format "name=value&name=value&...".
* The map has the same structure as the one returned by
* javax.servlet.ServletRequest.getParameterMap().
* A parameter name may occur multiple times within the query string.
* For each parameter name, the map contains a string array with the parameter values.
* @param s an URL query string.
* @return a map containing parameter names as keys and parameter values as map values.
* @author Christian d'Heureuse, Inventec Informatik AG, Switzerland, www.source-code.biz.
*/
public static Map<String, String[]> parseUrlQueryString (String s) {
if (s == null) return new HashMap<String, String[]>(0);
// In map1 we use strings and ArrayLists to collect the parameter values.
HashMap<String, Object> map1 = new HashMap<String, Object>();
int p = 0;
while (p < s.length()) {
int p0 = p;
while (p < s.length() && s.charAt(p) != '=' && s.charAt(p) != '&') p++;
String name = urlDecode(s.substring(p0, p));
if (p < s.length() && s.charAt(p) == '=') p++;
p0 = p;
while (p < s.length() && s.charAt(p) != '&') p++;
String value = urlDecode(s.substring(p0, p));
if (p < s.length() && s.charAt(p) == '&') p++;
Object x = map1.get(name);
if (x == null) {
// The first value of each name is added directly as a string to the map.
map1.put (name, value); }
else if (x instanceof String) {
// For multiple values, we use an ArrayList.
ArrayList<String> a = new ArrayList<String>();
a.add ((String)x);
a.add (value);
map1.put (name, a); }
else {
@SuppressWarnings("unchecked")
ArrayList<String> a = (ArrayList<String>)x;
a.add (value); }}
// Copy map1 to map2. Map2 uses string arrays to store the parameter values.
HashMap<String, String[]> map2 = new HashMap<String, String[]>(map1.size());
for (Map.Entry<String, Object> e : map1.entrySet()) {
String name = e.getKey();
Object x = e.getValue();
String[] v;
if (x instanceof String) {
v = new String[]{(String)x}; }
else {
@SuppressWarnings("unchecked")
ArrayList<String> a = (ArrayList<String>)x;
v = new String[a.size()];
v = a.toArray(v); }
map2.put (name, v); }
return map2; }
private static String urlDecode (String s) {
try {
return URLDecoder.decode(s, "UTF-8"); }
catch (UnsupportedEncodingException e) {
throw new RuntimeException("Error in urlDecode.", e); }}
// Illustrates how to decode a time value and add it to todays date.
public static void main (String args[]) throws Exception {
int t = decodeTime("12:34:56");
Date today = getTodaysDate();
Date d = new Date(today.getTime()+t);
System.out.println (d); }
// Decodes a time value in "hh:mm:ss" format and returns it as milliseconds since midnight.
public static synchronized int decodeTime (String s) throws Exception {
SimpleDateFormat f = new SimpleDateFormat("HH:mm:ss");
TimeZone utcTimeZone = TimeZone.getTimeZone("UTC");
f.setTimeZone (utcTimeZone);
f.setLenient (false);
ParsePosition p = new ParsePosition(0);
Date d = f.parse(s,p);
if (d == null || !isRestOfStringBlank(s,p.getIndex()))
throw new Exception("Invalid time value (hh:mm:ss): \"" + s + "\".");
return (int)d.getTime(); }
// Returns todays date without the time component.
public static Date getTodaysDate() {
return truncateDate(new Date()); }
// Truncates the time component from a date/time value.
// (The default time zone is used to determine the begin of the day).
public static Date truncateDate (Date d) {
GregorianCalendar gc1 = new GregorianCalendar();
gc1.clear();
gc1.setTime(d);
int year = gc1.get(Calendar.YEAR), month = gc1.get(Calendar.MONTH), day = gc1.get(Calendar.DAY_OF_MONTH);
GregorianCalendar gc2 = new GregorianCalendar(year,month,day);
return gc2.getTime(); }
// Returns true if string s is blank from position p to the end.
public static boolean isRestOfStringBlank (String s, int p) {
while (p < s.length() && Character.isWhitespace(s.charAt(p))) p++;
return p >= s.length(); }
/**
* Reallocates an array with a new size, and copies the contents
* of the old array to the new array.
* @param oldArray the old array, to be reallocated.
* @param newSize the new array size.
* @return A new array with the same contents.
*/
private static Object resizeArray (Object oldArray, int newSize) {
int oldSize = java.lang.reflect.Array.getLength(oldArray);
Class elementType = oldArray.getClass().getComponentType();
Object newArray = java.lang.reflect.Array.newInstance(
elementType, newSize);
int preserveLength = Math.min(oldSize, newSize);
if (preserveLength > 0)
System.arraycopy(oldArray, 0, newArray, 0, preserveLength);
return newArray; }
// Test routine for resizeArray().
public static void main (String[] args) {
int[] a = {1, 2, 3};
a = (int[])resizeArray(a, 5);
a[3] = 4;
a[4] = 5;
for (int i=0; i<a.length; i++)
System.out.println(a[i]); }
How to resize a two-dimensional array
Two-dimensional arrays in Java are arrays of arrays. To resize a two-dimensional array, the resizeArray function must be applied to the outer array and to all the nested arrays.
Example:
int a[][] = new int[2][3];
//...
a = (int[][])resizeArray(a, 20);
// new array is [20][3]
for (int i=0; i<a.length; i++) {
if (a[i] == null)
a[i] = new int[30];
else a[i] = (int[])resizeArray(a[i], 30); }
// new array is [20][30]