Browse DevX
Sign up for e-mail newsletters from DevX


Write Efficient Java Apps Using Native Data Structures with JNI : Page 2

Sometimes Java's data structures use too much memory to store the data you need to store. In such situations, you can use the JNI native code interface to access native data structures. Find out how to use the STL in C++ to implement a space-efficient hashtable that works like a regular Java hashtable.

The Native Code
The core data structure is a hashtable implemented in C++, in the files NativeHash.cc and NativeHash.h. In fact, this code uses STL to implement the actual data structure; the NativeHash class is really just a wrapper around this data structure.

It is beyond the scope of this article to go into STL in detail, but here's how you implement the hashtable:
#include <map> include <string>


class NativeHash {
  map<string, string> ssmap;


'ssmap' is declared to be a map from string to string. This C++ is easy to use. To store a value, you simply assign it like this:
ssmap["key"] = "value";
To retrieve a value, access it like this:
string value = ssmap["key"];
This way, it's easy to create a wrapper class around this data structure, which you need to do because the interface to your native code must mimic the interface to your Java code.

In particular, there are methods to get and set a value:
const char *get( const char *key );
void put( const char *key, const char *value ); 
There are also auxiliary methods to check for key existence and check the number of entries:
int size() const;
int contains( const char *s ) const; 
There are two methods used to implement iteration:
const char *firstKey();
const char *nextKey( const char *key ); 
There are methods to read from and write to a file:
void write( const char *filename );
static NativeHash *read( const char *filename ); 
Your class includes these methods because reading and writing are so much faster when they are done directly from native code.

Finally, there is a dispose() method:
static void dispose( NativeHash *e ); 
This is used by the finalize method of the JNativeHash class to deallocate the NativeHash object. It is also necessary that the user be able to call this directly, rather than having it called during garbage collection; see the "Memory Management" section for details.

The Java Wrapper
JNativeHash.java contains the code that the client sees. Many of the methods are Java versions of the native methods described in the previous section:
public String get( String key );
public void put( String key, String value );
public int size();
public boolean contains( String key );
public void write( String filename );
static public JNativeHash read( String filename ) throws IOException; 
These methods, in fact, come in pairs. For example, the get() method has a twin, called get_u():
public String get( String key ) {
  synchronized( lock ) {
    return get_u( key );

native public String get_u( String key ); 
The get() method simply calls the get_u() method, but it does it inside a synchronized block. The get_u() method is a native method, so its implementation is omitted. Rather than having a Java implementation in JNativeHash.java, it has a C++ implementation in JNativeHash.cc.

This doubling isn't strictly necessary—we could have simply made the get() method do the work of both:
synchronized native public String get( String key ); 
However, it is better to be explicit about the locking mechanism, because this can permit more fine-grained parallelism. Likewise, by using a static lock, we are locking all hashtables independently, which may be safer in the event that the native code is not thread-safe. For example, if two objects are called at the same time, there may be a race condition deep within a non-thread-safe memory allocator. A static lock is safest, but if the programmer knows that the underlying native libraries are thread-safe, then she can simply make the lock non-static:
/*static*/ public Object lock = new Object();

Thanks for your registration, follow us on our social networks to keep up-to-date