Caching is one of the first techniques used to improve the
performance of web browsers and web servers. The browser
cache makes network lookup operations unnecessary because
a recent copy of the file is kept in the local cache, and the
web server cache reduces the cost of loading the file
from disk for each request. This section explains how you
can use caching in a similar way to improve performance in many
client/server applications written in the
JavaTM programming language.
The java.util.Collections API available in the Java® 2
Software Development Kit (SDK) software makes implementing a cache simple.
This API provides the HashMap class, which works well for
caching one object, and the LinkedList class, which works well
in combination with the HashMap class for caching many objects.
Caching One Object
A HashMap object stores data in key and value pairs.
When you put a data value in the HashMap , you assign it a
key and later use that key to retrieve the data.
A HashMap object is very similar to a Hashtable
and can be used to keep a temporary copy of previously generated results.
Objects kept in the HashMap
cache could, for example, be a list of completed auction results.
In this case, the results of a JDBC query might be requested hundreds of
times a second by persons wanting to know who was the highest bidder, but
the completed results lists only actually changes once a minute as each
auction completes. You can write your
program to retrieve unchanged objects from the results cache instead of
querying the database every time and gain a significant performance
improvement.
This code example
runs a database query once a minute, and returns
cached copies for requests that come between the queries.
import java.util.*;
import java.io.*;
class DBCacheRecord {
Object data;
long time;
public DBCacheRecord(Object results, long when) {
time=when;
data=results;
}
public Object getResults() {
return data;
}
public long getLastModified() {
return time;
}
}
public class DBCache {
Map cache;
public DBCache() {
cache = new HashMap();
}
public Object getDBData(String dbcommand) {
if(!cache.containsKey(dbcommand)) {
synchronized(cache) {
cache.put(dbcommand, readDBData(dbcommand));
}
} else {
if((new Date().getTime() ) -
((DBCacheRecord)cache.get(
dbcommand)).getLastModified()>=1000) {
synchronized(cache) {
cache.put(dbcommand, readDBData(dbcommand));
}
}
}
return ((DBCacheRecord)cache.get(
dbcommand)).getResults();
}
public Object readDBData(String dbcommand) {
/*Insert your JDBC code here For Example:
ResultSet results=stmt.executeQuery(dbcommand);
*/
String results="example results";
return(new DBCacheRecord(results,new
Date().getTime()));
}
public static void main(String args[]) {
DBCache d1=new DBCache();
for(int i=1;i<=20;i++) {
d1.getDBData(
"select count(*) from results where
TO_DATE(results.completed) <=SYSDATE");
}
}
}
Caching Many Objects
Sometimes you will want to cache more than one object. For example,
you might want to keep the most recently accessed files on a web server
in a cache. If you use a HashMap object for a purpose
like this, it will continue to grow and use a lot of memory.
If your machine has large amounts of memory and only a small number of objects
to cache then a growing HashMap may not be a problem.
However, if you are intending to cache alot of objects then you may find
that keeping only the most recent
objects in the cache provides the best use of the machines memory. You can combine a HashMap object with a
LinkedList to create what is called a Most Recently Used
(MRU) cache.
Note: There are other techniques used to constrain cache size besides MRU. MRU is one of the simpler algorithms.
With an MRU cache, you can place a constraint on which objects remain
in cache, and thereby, control the size of the cache. There are three main
operations that the MRU cache has to perform:
- If the cache is not full, new objects not already in the cache
are inserted at the head of the list.
- If the cache is not full and the object to be inserted already exists in
the cache, it is moved to the head of the list.
- If the cache is full and a new object is to be inserted, the last object
in the cache is removed and the new object is inserted at the head of
the list.
This diagram shows how the LinkedList and
HashMap work together to implement the operations
described above. A discussion of the diagram follows.
MRU Cache with LinkedList and HashMap
The LinkedList provides the queue mechanism, and the entries
in the LinkedList contain the key to the data in the
HashMap . To add a new entry to the front of the list, the
addFirst method is called.
- If the list is already full, the
removeLast method
is called and the data entry is also removed
from the HashMap .
-
If an entry was already in the list, it is removed with
a call to the
remove method and inserted at the front of the list with
a call to the addFirst method.
The Collections API does not implement locking, so if you remove entries
from or add entries to LinkedList or HashMap
objects, you need to
lock access to these objects. You can also use a Vector or
ArrayList to get the same results as shown in the code
below with the LinkedList .
This code example
uses an MRU cache to keep a cache of files loaded from disk.
When a file is requested, the program checks to see if the file
is in the cache.
If the file is not in the cache, the program reads
the file from disk and places the cache copy at the
beginning of the list.
If the file is in cache, the program compares the
modification times of the file and cache entry.
- If the cache entry time is older, the
program reads the file from disk, removes the cache copy, and places
a new copy in the cache at the front of the
LinkedList .
- If the file time is older, the program gets the file from
the cache and moves the cache copy to the front of the list.
import java.util.*;
import java.io.*;
class myFile {
long lastmodified;
String contents;
public myFile(long last, String data) {
lastmodified=last;
contents=data;
}
public long getLastModified() {
return lastmodified;
}
public String getContents() {
return contents;
}
}
public class MRUCache {
Map cache;
LinkedList mrulist;
int cachesize;
public MRUCache(int max) {
cache = new HashMap();
mrulist= new LinkedList();
cachesize=max;
}
public String getFile(String fname) {
if(!cache.containsKey(fname)) {
synchronized(cache) {
if(mrulist.size() >=cachesize) {
cache.remove(mrulist.getLast());
mrulist.removeLast();
}
cache.put(fname, readFile(fname));
mrulist.addFirst(fname);
}
} else {
if((new File(fname).lastModified())>
((myFile)cache.get(fname)).getLastModified()) {
synchronized(cache) {
cache.put(fname, readFile(fname));
}
}
synchronized(cache) {
mrulist.remove(fname);
mrulist.addFirst(fname);
}
}
return ((myFile)cache.get(fname)).getContents();
}
public myFile readFile(String name) {
File f = new File(name);
StringBuffer filecontents= new StringBuffer();
try {
BufferedReader br=new BufferedReader(
new FileReader(f));
String line;
while((line =br.readLine()) != null) {
filecontents.append(line);
}
} catch (FileNotFoundException fnfe){
return (null);
} catch ( IOException ioe) {
return (null);
}
return (new myFile(f.lastModified(),
filecontents.toString()));
}
public void printList() {
for(int i=0;i<mrulist.size();i++) {
System.out.println("item "+i+"="+mrulist.get(i));
}
}
public static void main(String args[]) {
// Number of entries in MRU cache is set to 10
MRUCache h1=new MRUCache(10);
for(int i=1;i<=20;i++) {
// files are stored in a subdirectory called data
h1.getFile("data"+File.separatorChar+i);
}
h1.printList();
}
}
[TOP]
|