Java Technology Home Page
A-Z Index

Java Developer Connection(SM)
Online Training

Downloads, APIs, Documentation
Java Developer Connection
Tutorials, Tech Articles, Training
Online Support
Community Discussion
News & Events from Everywhere
Products from Everywhere
How Java Technology is Used Worldwide
 
Training Index

Writing Advanced Applications
Chapter 8 Continued: Caching Client/Server Applications

[<<BACK] [CONTENTS] [NEXT>>]

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]


[ This page was updated: 13-Oct-99 ]

Products & APIs | Developer Connection | Docs & Training | Online Support
Community Discussion | Industry News | Solutions Marketplace | Case Studies
Glossary - Applets - Tutorial - Employment - Business & Licensing - Java Store - Java in the Real World
FAQ | Feedback | Map | A-Z Index
For more information on Java technology
and other software from Sun Microsystems, call:
(800) 786-7638
Outside the U.S. and Canada, dial your country's AT&T Direct Access Number first.
Sun Microsystems, Inc.
Copyright © 1995-99 Sun Microsystems, Inc.
All Rights Reserved. Legal Terms. Privacy Policy.