Home arrow Java arrow Page 3 - Storing and Retrieving Data
JAVA

Storing and Retrieving Data


Storing bytes of data locally on a device equipped for the Mobile Internet Device Profile (MIDP) is easy. MIDP allows you to store arrays of bytes. But what if the data you need to store isn't in the form of bytes? And how can you make the data small enough so that storing it on a device with a relatively small amount of memory is not a problem? That's where this article comes in. It is excerpted from the book J2ME Games with MIDP2, written by Carol Hamer (Apress, 2004; ISBN: 1590593820).

Author Info:
By: Apress Publishing
Rating: 4 stars4 stars4 stars4 stars4 stars / 24
May 12, 2005
TABLE OF CONTENTS:
  1. · Storing and Retrieving Data
  2. · Serializing More Complex Data Using Streams
  3. · Using Data Types and Byte Arithmetic
  4. · Applying Data Storage to a Game
  5. · Converting an array of bytes into a dungeon
  6. · Creating the Complete Example Game
  7. · DungeonManager.java
  8. · Doors and keys

print this article
SEARCH DEVARTICLES

Storing and Retrieving Data - Using Data Types and Byte Arithmetic
(Page 3 of 8 )

Using InputStream and OutputStream to encode and decode your data for storage purposes would be sufficient if your target device had an infinite amount of memory, which is far from the case for many small devices. And the DataOutputStream uses a full byte to record a single Boolean when of course you could squeeze eight Booleans into the same space, and as mentioned previously, DataOutputStream uses 4 bytes to store an int when you often know in advance that the value will be small enough to fit into 1 or 2 bytes. Of course, you have a little bit of design strategy to consider when deciding whether to store your data in the standard way or whether to compact it. Compacting your data not only complicates your program, but it makes your data less portable. If you’re using custom compression algorithms, it’s easier to render your data completely unsalvageable with a small error than it is when your data is serialized in a standard format. Therefore, if you’re storing only a little data, then it’s generally better to serialize it using the standard methods. In the case of this chapter’s example program, however, data compression makes a nontrivial difference. In the game, a player is exploring a maze-like dungeon that’s created by a 16x16 square grid. I’d like to allow for the possibility of having a large series of different boards for this game, and each board is stored as a chunk of data that tells which squares of the grid should be empty and which should be filled. Therefore, you create the background of this game with a 16x16 two-dimensional array of ones and zeros—or, in other words, with 256 Booleans. If I plan to store multiple boards, I’d prefer not to store this as an array of 256 bytes (or worse, as 256 ints that would equal 1024 bytes!) when I could store it compactly as 32 bytes.

It’s not hard to compact data if you know a little bit about byte arithmetic. And Java 2 Micro Edition (J2ME) programmers naturally need to use byte arithmetic more than the average Java programmer. Recall that you use the bitwise “or” operator for placing strings and images (see the “Using the Graphics and Canvas Classes” section in Chapter 2) and for layout directives (see the “Using the Form and Item classes” section in Chapter 2), and you use a bitwise “and” operator to get all the information about the current key states (see the “Using the GameCanvas Class” section in Chapter 3). To stock eight Booleans into a byte, all you need to do is add the ones and zeros to the byte one by one and use the shift operator to shift the result up one bit between each new addition. To get the data out again, you just perform a bitwise “and” between the byte and a series of appropriate flag bytes. For example, 128 corresponds to the top bit of a byte, so to find out if the top bit of a given byte is set, all you have to do is perform a bitwise “and” with your chosen byte and a flag byte whose value is equal to 128. The value returned by the operation will be nonzero if and only if the top bit of your byte was set. You can get all the other flag bytes easily from the initial flag by shifting down the flag byte.

The class in Listing 5-3 is the complete version of my integer compression utility class (called DataConverter). It contains methods to pack eight Booleans into a byte as described previously, as well as methods to convert integers in various size ranges to bytes. I’ve even included methods that will convert an int to an array of 4 bytes giving exactly the same values as you’d get using DataInputStreamandDataInputStream. I include these only so you can see the precise Java’s algorithm for internally representing integers, not because you should choose to use these utilities over DataInputStream and DataOutputStream.

The only tricky part in any of the algorithms in Listing 5-3 is dealing with when a byte is considered to be signed and when it’s considered to be unsigned. Signed bytes range in value from -128 to 127, and unsigned bytes range in value from 0 to 255. Any byte can be regarded as signed or unsigned; it’s just a question of whether you consider the top bit to indicate a negative sign or 128. If you convert a single byte to an int, Java will consider the byte to be signed when returning the value. If you wanted its value as an unsigned byte, then you can fix it by adding 256 if the value is negative (that is, add 128 to get the value into the positive range and then add another 128 for the value of the top bit that was set). An integer obviously needs only one sign, so when Java represents an integer internally as 4 bytes, only the high byte (which is the first of the 4 bytes) is regarded as signed. Dealing with the interplay between signed and unsigned bytes is a little bit confusing, but I hope that the code example in Listing 5-3 will help clarify how it works.

Listing 5-3 shows the code for DataConverter.java.

Listing 5-3. DataConverter.java

package net.frog_parrot.util;
import java.io.*;
/**
  * This class is aset of simple utility functions that
  * can be used to convert standard data types to bytes
 
* and back again. It is used especially for data storage,
  * but also for sending and receiving data.
  *
 
* @author Carol Hamer
 
*/
public class DataConverter {
//------------------------------------------------------- // utilities to encode small, compactly stored small ints.
/**
  * Encodes acoordinate pair into a byte.
  * @param coordPair apair of integers to be compacted into
 
* a single byte for storage.
 
* WARNING: each of the two values MUST BE
 
* between 0 and 15 (inclusive). This method does not
  *
verify the length of the array (which must be 2!)
 
* and it doesn't verify that the ints are of the right size.
  */
public static byte encodeCoords(int[] coordPair) {
  // get the byte value of the first coordinate:
  byte retVal = (new Integer(coordPair[0])).byteValue();
  // move the first coordinate's value up to the top
  // half of the storage byte:
  retVal = (new Integer(retVal << 4)).byteValue();
  // store the second coordinate in the lower half
  // of the byte:
  retVal += (new Integer(coordPair[1])).byteValue();
  return(retVal);
}
/**
 
* Encodes eight ints into a byte.
 
* This could be easily modified to encode eight Booleans.
 
* @param eight an array of at least eight ints.
 
* WARNING: all values must be 0 or 1! This method does
 
* not verify that the values are in the correct range
 
* and it doesn't verify that the array is long enough.
  * @param offset the index in the array eight to start
 
* reading data from. (should usually be 0)
  */
public static byte encode8(int[] eight, int offset) {
  // get the byte value of the first int:
  byte retVal = (new Integer(eight[offset])).byteValue();
  // progressively move the data up one bit in the
  // storage byte and then record the next int in
  // the lowest spot in the storage byte:
  for(int i = offset + 1; i < 8 + offset; i++) {
   
retVal = (new Integer(retVal << 1)).byteValue();
    retVal += (new Integer(eight[i])).byteValue();
    }
    return(retVal);
  }
//------------------------------------------------------- // utilities to decode small, compactly stored small ints.
/**
 
* Turns abyte into a pair of coordinates.
  */
public static int[] decodeCoords(byte coordByte) {
    int[] retArray = new int[2];
    // you perform a bitwise and with the value 15
    // in order to just get the bits of the lower
    // half of the byte:
    retArray[1] = coordByte & 15;
    // To get the bits of the upper half of the
    // byte, you perform a shift to move them down:  
    retArray[0] = coordByte >> 4;
    // bytes in Java are generally assumed to be
    // signed, but in this coding algorithm you
    // would like to treat them as unsigned:
    if(retArray[0] < 0) {
     
retArray[0] += 16;
    }
    return(retArray);
  }
 
/**
  
* Turns abyte into eight ints.
  */
public static int[] decode8(byte data) {
  int[] retArray = new int[8];
  // The flag allows us to look at each bit individually
  // to determine if it is 1 or 0. The number 128
  // corresponds to the highest bit of a byte, so you
  // start with that one.
  int flag = 128;
  // You use a loop that checks
  // the data bit by bit by performing a bitwise
  // and (&) between the data byte and a flag:
  for(int i = 0; i < 8; i++) {
   
if((flag & data) != 0) {
      retArray[i] = 1;
    } else {
      retArray[i] = 0;
    }
    // move the flag down one bit so you can
    // check the next bit of data on the next pass
    // through the loop:
    flag = flag >> 1;
  }
  return(retArray);
}
//-------------------------------------------------------
// standard integer interpretation
/**
 
* Uses an input stream to convert an array of bytes to an int.
  */
public static int parseInt(byte[] data) throws IOException {
  DataInputStream stream
   
= new DataInputStream(new ByteArrayInputStream(data));
  int retVal = stream.readInt();
  stream.close();
  return(retVal);
}
/**
 
* Uses an output stream to convert an int to four bytes.
  */
public static byte[] intToFourBytes(int i) throws IOException {
  ByteArrayOutputStream baos = new ByteArrayOutputStream(4);
  DataOutputStream dos = new DataOutputStream(baos); 
  dos.writeInt(i);
  baos.close();
  dos.close();
  byte[] retArray = baos.toByteArray();
  return(retArray);
}
//-------------------------------------------------------
// integer interpretation illustrated
/**
 
* Java appears to treat abyte as being signed when
 
* returning it as an int--this function converts from
 
* the signed value to the corresponding unsigned value.
 
* This method is used by nostreamParseInt.
  */
public static int unsign(int signed) {
  int retVal = signed;
  if(retVal < 0) {
    retVal += 256;
  }
  return(retVal);
}
/**
 
* Takes an array of bytes and returns an int.
  * This version will return the same value as the
 
* method parseInt previously. This version is included
 
* in order to illustrate how Java encodes int values
 
* in terms of bytes.
 
* @param data an array of 1, 2, or 4 bytes.
  */
public static int nostreamParseInt(byte[] data) {
  // byte 0 is the high byte, which is assumed
  // to be signed. As you add the lower bytes
  // one by one, you unsign them because because
  // a single byte alone is interpreted as signed,
  // but in an int only the top byte should be signed.
  // (note that the high byte is the first one in the array)
  int retVal = data[0];
  for(int i = 1; i < data.length; i++) {
   
retVal = retVal << 8;
   
retVal += unsign(data[i]);
  }
  return(retVal);
}
/**
  * Takes an arbitrary int and returns
  * an array of 4 bytes.
 
* This version will return the same byte array
 
* as the method intToFourBytes previous. This version
 
* is included in order to illustrate how Java encodes
 
* int values in terms of bytes.
  */
public static byte[] nostreamIntToFourBytes(int i) {
  byte[] fourBytes = new byte[4];
  // when you take the byte value of an int, it
  // only gives you the lowest byte. So you
 
// get all 4 bytes by taking the lowest
  // byte four times and moving the whole int
  // down by one byte between each one.
  // (note that the high byte is the first one in the array)
  fourBytes[3] = (new Integer(i)).byteValue();
  i = i >> 8;
  fourBytes[2] = (new Integer(i)).byteValue();
  i = i >> 8;
  fourBytes[1] = (new Integer(i)).byteValue();
  i = i >> 8;
  fourBytes[0] = (new Integer(i)).byteValue();
  return(fourBytes);
}
/**
 
* Takes an int between -32768 and 32767 and returns
 
* an array of 2 bytes. This does not verify that
 
* the argument is of the right size. If the absolute
 
* value of i is too high, it will not be encoded
 
* correctly.
  */
public static byte[] nostreamIntToTwoBytes(int i) {
  byte[] twoBytes = new byte[2];
  // when you take the byte value of an int, it
  // only gives you the lowest byte. So you
  // get the lower two bytes by taking the lowest
  // byte twice and moving the whole int
  // down by one byte between each one.
  twoBytes[1] = (new Integer(i)).byteValue();
  i = i >> 8;
  twoBytes[0] = (new Integer(i)).byteValue();
  return(twoBytes);
 
}
}


blog comments powered by Disqus
JAVA ARTICLES

- Java Too Insecure, Says Microsoft Researcher
- Google Beats Oracle in Java Ruling
- Deploying Multiple Java Applets as One
- Deploying Java Applets
- Understanding Deployment Frameworks
- Database Programming in Java Using JDBC
- Extension Interfaces and SAX
- Entities, Handlers and SAX
- Advanced SAX
- Conversions and Java Print Streams
- Formatters and Java Print Streams
- Java Print Streams
- Wildcards, Arrays, and Generics in Java
- Wildcards and Generic Methods in Java
- Finishing the Project: Java Web Development ...

Watch our Tech Videos 
Dev Articles Forums 
 RSS  Articles
 RSS  Forums
 RSS  All Feeds
Write For Us 
Weekly Newsletter
 
Developer Updates  
Free Website Content 
Contact Us 
Site Map 
Privacy Policy 
Support 

Developer Shed Affiliates

 




© 2003-2017 by Developer Shed. All rights reserved. DS Cluster - Follow our Sitemap
Popular Web Development Topics
All Web Development Tutorials