Create a general cache package using the dynamic proxy class The disadvantage of the second method above is that the cache package cannot be reused. Every time we want to add a cache to a class, we need to write a special cache package to the target interface. this is a very slow and error-prone process. Jdk1.3 began to support dynamic proxy classes: special classes can decide which interface to implement at runtime-the common mode is, that is, the interface to implement during runtime. through this, we may implement a general cache package, which we call memoizer and decide which interface to implement at runtime. in this way, cachingbinarydigitscalculator is no longer needed. it is called as follows:
Binarydigitscalculator calculator = New cachingbinarydigitscalculator ( New pibinarydigitscalculator () ); |
You can use memoizer to override the following::
Binarydigitscalculator calculator = (Binarydigitscalculator) memoizer. memoize ( New pibinarydigitscalculator () ); |
The memoizer code is as follows:: [Code] import java. Lang. Reflect. invocationhandler; Import java. Lang. Reflect. invocationtargetexception; Import java. Lang. Reflect. method; Import java. Lang. Reflect. proxy; Import java. util. arrays; Import java. util. collections; Import java. util. hashmap; Import java. util. List; Import java. util. Map; Public class memoizer implements invocationhandler { Public static object memoize (Object object ){ Return proxy. newproxyinstance ( Object. getclass (). getclassloader (), Object. getclass (). getinterfaces (), New memoizer (object) ); } Private object; Private map caches = new hashmap (); Private memoizer (Object object ){ This. Object = object; } Public object invoke (Object proxy, method, Object [] ARGs) throws throwable { If (method. getreturntype (). Equals (void. Type )){ // Don't cache void Methods Return invoke (method, argS ); } Else { Map cache = getcache (method ); List key = arrays. aslist (ARGs ); Object value = cache. Get (key ); If (value = NULL &&! Cache. containskey (key )){ Value = invoke (method, argS ); Cache. Put (Key, value ); } Return value; } } Private object invoke (method, object [] ARGs) Throws throwable { Try { Return method. Invoke (object, argS ); } Catch (invocationtargetexception e ){ Throw E. gettargetexception (); } } Private synchronized map getcache (method M ){ Map cache = (MAP) caches. Get (m ); If (Cache = NULL ){ Cache = collections. synchronizedmap ( New hashmap () ); Caches. Put (M, cache ); } Return cache; } } [/Code] When the static method memoize is called, a new proxy instance-that is, a java. lang. reflect. proxy instance. implements an interface set. this interface set is composed of objects. getclass (). getinterfaces. each proxy instance contains a java. lang. reflect. invocationhandler instance to process the method called by this proxy instance. in our example, memoizer is an invocationhandler instance. When a method is called in a proxy instance, such as calculatebinarydigit, The invoke method in the memoizer instance is called and the relevant information is transmitted to the invoke method, to determine which method the proxy instance calls, including parameter information. in our example, input the memoizer Java. lang. the method parameter is calculatebinarydigit, while the parameter information is the number of digits that PI needs to be precise-integer n. on this basis, memoizer can further perform cache operations. In this example (caches is a hashmap and cache is a map), the key is used mainly for the incoming method information: Method object and parameter object. to achieve simplicity and versatility, memoizer has a hashmap caches about cache. Each method is a key and the corresponding value is a cache. then, convert the parameter information into a list object as the cache key. it is convenient to use list, and the equals () method can be ensured. Therefore, this list is equal only when the parameter information is identical. Once a cache key is created, it will be searched for before calculation. If it is found, the value in the cache will be returned. otherwise, if the method with these parameters has not been called, the method will be called through invoke. in our example, the calculatebinarydigit method in the instance pibinarydigitscalculator will be called through invoke. and the calculation result will be stored in the cache. When to use memoizer As a general rule, memoizer can be used when any traditional cache is needed-for example, the preceding example. in particular, each method in the interface that needs to use the memory function must meet the following conditions: 1. the return value of this method should not be changed every time it is called. 2. This method has no secondary effect. 3. the parameter of this method is fixed and non-mutable. Obviously, if the return value of this method is different each time, the cache will be useless. it is also very important that, because the method with a secondary effect will not be repeated, this method cannot have a secondary effect (method automatically updates certain States ). except for the void method. Similarly, memorize's method with undetermined mutable parameters is very dangerous, because it is very dangerous to store these parameters in hashmap. according to the definition of map, map is unknown when the key in this map changes. therefore, after you execute this method once, the relevant information is added to the map, and then the parameter mutate. When you call the method for the second time, an error is returned. Performance The main purpose of using cache is to speed up your program. however, reflection is a well-known inefficiency (improved in jdk1.4, calling methods through reflection is 1/2 faster than jdk1.3 ). memoizer mainly relies on reflection to call methods. Therefore, it does not seem to be a good way. however, if the use of cache can increase the speed of the program much higher than the speed of reflection, it is worth considering using memoizer. In our test of pibinarydigitscalculator, the test environment is jdk1.4. When n is less than 10, the cache speed is equivalent. however, when N increases, the advantage of using cache is displayed. therefore, users who frequently use pibinarydigitscalculator can consider using cache. Unfortunately, the only way to test whether your program requires a cache is to compare the running efficiency of your program in two cases. however, it is easy to construct a cache package for a program and remove it. The following suggestions can be used as a reference step: 1. Select the class for memory operations 2. Run it 3. If the efficiency is satisfactory, go to 6 4. Add memoizer and use Cache 5. If the efficiency is not significantly improved, migrate the memoizer 6. Try again if necessary. Theoretically, you need to analyze the impact of adding the memory function to a class on the entire system. only you know whether it is worth adding. some methods, even with a large amount of computing, are rarely called in this system, so there is no need to add a memory function for it. to ensure this, I have developed a more distinctive memoizer and implemented an interface called cachestatistics. You can obtain the number of caches and invalid caches from it. you can use it as a criterion for judgment. Extended memoizer It is very easy to modify the memoizer class to support different cache policies. A common type is least-recently-used (LRU) cahce, which has a fixed number of entries. this cache ensures that the entry does not exceed its maximum number. If it exceeds the maximum number, the oldest cache data will be discarded. that is, new data can be obtained from the cache. A class can use LRU cache to prevent a program from staying in a State for a long time. you can just pass a parameter to the memoize method in cachefactory to select the cache type you need. in the following example, LRU cache has a maximum of 1000 entries:
Binarydigitscalculator calculator = (Binarydigitscalculator) memoizer. memoize ( New pibinarydigitscalculator (), New lrucachefactory (0, 1000) ); |
Even if it is so simple, memoizer should be a useful tool for Java programmers.
|