2

I'm working on a Java File I/O Interface, I need my files to be in binary format, not in strings. I've found ObjectOutputStream and ObjectInputStream useful for my needs, but I need the interface to be able to write at the end of my file, as of I need it to record future data in the same file over and over.

I tried using the FileOutputStream(String file, boolean append) constructor but since my class implements Serializable there seems to be an issue. When it attempts to read a second record it throws a StreamCorruptedException. I know it must be because there's this header that kind of describes the object's fields.

My question is, how can I store objects succesfully? I like using objects, because it makes it easier to structure and handle data. This is my code so far:

try
    {
        FileOutputStream outFile;
        ObjectOutputStream outStream;
        FileInputStream inFile;
        ObjectInputStream inStream;

        outFile = new FileOutputStream("people.mcf", true);
        outStream = new ObjectOutputStream(outFile);

        //Writing the objects
        System.out.println("Writing file...");
        for(int i = 0; i < 3; i++)
        {
            outStream.writeObject(people[i]);
        }
        System.out.println("Finished writing file...");
        outFile.close();
        outStream.close();
        //Reading the files
        System.out.println("Attempting to read file...");
        inFile = new FileInputStream("people.mcf");
        inStream = new ObjectInputStream(inFile);
        Person buffer;
        while(true)
        {                
            buffer = (Person)inStream.readObject();
            System.out.println(buffer.getData());
        }

    }
    catch(EOFException e)
    {
        System.out.println("Reached end of file...");
    }
    catch(IOException e)
    {
        System.err.println(e.toString());
    }

This is the Person class:

static class Person implements Serializable
{
    private final String name;
    private final int age;
    private final String address;

    public Person(String name, int age, String address)
    {
        this.name = name;
        this.age = age;
        this.address = address;
    }               

    public String getData()
    {
        return name + " " + age + " " + address;
    }       
}

This is the output I get:

Writing file...
Finished writing file...
Attempting to read file...
Andres 26 Palo Gordo
Pilar 22 Palo Gordo
Kelvin 27 Palo Gordo
java.io.StreamCorruptedException: invalid type code: AC
BUILD SUCCESSFUL (total time: 0 seconds)

EDIT: I'm not asking why I'm getting the StreamCorruptedException, I'm aware that ObjectOutputStream is causing this, what I'm asking for is another way to store Object data in a structured manner.

  • `The objects must be read back from the corresponding ObjectInputstream with the same types and in the same order as they were written.` And you are reading them in while true loop. Can you try reading only 3 objects? Maybe you are getting StreamCorrupted exception instead of EOFException. – Mikhail Boyarsky Apr 29 '17 at 17:46
  • You can't append objects to an Object Stream. It has a header and a footer. btw if you want to write an array to an object stream, just write the array as it's an object too. – Peter Lawrey Apr 29 '17 at 18:15
  • @MikhailBoyarsky I know for a fact that there are more than 3 registers, because with a clean file it reads just fine. – Andres Rincon Apr 29 '17 at 18:21
  • @PeterLawrey is there a way to store data in an organized way so I can read it later and maybe store it in an object? – Andres Rincon Apr 29 '17 at 18:23
  • @AndresRincon yes, too many to mention here. The simplest solution is to do what I suggested. – Peter Lawrey Apr 29 '17 at 19:24

1 Answers1

0

If your record is of FIXED LENGTH or at least of MAXIMUM length, the following code do the job.

Using RandomAccessFile you'll be able to seek the file pointer to write new, or update an already existing record. See Indexed File in Wikipedia.

To be more portable, you may serialize the record using ByteBuffer, already used by FileChannel which is an interface to RandomAccessFile, see RandomAccessFile.getChannel().

Code sample:

Person class revisited:

import java.nio.ByteBuffer;
import java.util.UUID;

public class Person implements ISerializable<UUID> {

   private final UUID   _id;
   private final String _name;
   private final int    _age;
   private final String _address;

   public Person( ByteBuffer source ) {
      _id      = SerializeUtil.unserializeUUID( source );
      _name    = SerializeUtil.unserializeString( source );
      _age     = source.get();
      _address = SerializeUtil.unserializeString( source );
   }

   public Person( String name, int age, String address) {
      _id      = UUID.randomUUID();
      _name    = name;
      _age     = age;
      _address = address;
   }

   @Override
   public UUID getKey() {
      return _id;
   }

   @Override
   public void serialize( ByteBuffer target ) {
      target.clear();
      SerializeUtil.serializeUUID  ( _id     , target );
      SerializeUtil.serializeString( _name   , target );
      target.put((byte)_age );
      SerializeUtil.serializeString( _address, target );
   }
}

IndexFile class:

import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Map;
import java.util.TreeMap;
import java.util.function.Consumer;
import java.util.function.Function;

public class IndexedFile<K extends Comparable<K>, R extends ISerializable<K>>
   implements
      Closeable
{
   private final Map<K, Long>            _index = new TreeMap<>();
   private final RandomAccessFile        _file;
   private final FileChannel             _channel;
   private final ByteBuffer              _record;
   private final Function<ByteBuffer, R> _factory;

   public IndexedFile( File tracksFile, int recordSize, Function<ByteBuffer, R> factory ) throws IOException {
      _file    = new RandomAccessFile( tracksFile, "rw" );
      _channel = _file.getChannel();
      _record  = ByteBuffer.allocate( recordSize );
      _factory = factory;
      _channel.truncate( 0 );
   }

   @Override
   public void close() throws IOException {
      _file.close();
      _channel.close();
   }

   public int size() {
      return _index.size();
   }

   public int recordSize() {
      return _record.capacity();
   }

   public void put( R record ) throws IOException {
      final K    key    = record.getKey();
      final Long offset = _index.get( key );
      if( offset != null ) {
         _channel.position( offset );
      }
      else {
         final long pos = _channel.size();
         _channel.position( pos );
         _index.put( key, pos );
      }
      record.serialize( _record );
      _record.position( _record.limit());
      _record.flip();
      _channel.write( _record );
   }

   public R read( int recNo ) throws IOException {
      if( recNo >= _index.size()) {
         return null;
      }
      final long offset = recNo*_record.capacity();
      _channel.position( offset );
      _record.clear();
      _channel.read( _record );
      _record.flip();
      return _factory.apply( _record );
   }

   public R get( K key ) throws IOException {
      final Long offset = _index.get( key );
      if( offset == null ) {
         return null;
      }
      _channel.position( offset );
      _record.clear();
      _channel.read( _record );
      _record.flip();
      return _factory.apply( _record );
   }
}

For completeness, utility class:

import java.nio.ByteBuffer;
import java.util.UUID;

public final class SerializeUtil {

   public static void serializeString( String s, ByteBuffer target ) {
      final byte[] bytes = s.getBytes();
      target.putInt( bytes.length );
      target.put( bytes );
   }

   public static String unserializeString( ByteBuffer target ) {
      final int length = target.getInt();
      final byte[] bytes = new byte[length];
      target.get( bytes );
      return new String( bytes );
   }

   public static void serializeUUID( UUID id, ByteBuffer target ) {
      target.putLong( id.getMostSignificantBits());
      target.putLong( id.getLeastSignificantBits());
   }

   public static UUID unserializeUUID( ByteBuffer source ) {
      final long msb = source.getLong();
      final long lsb = source.getLong();
      return new UUID( msb, lsb );
   }
}

And the interface ISerializable:

import java.nio.ByteBuffer;

public interface ISerializable<K extends Comparable<K>> {

   public K getKey();

   public void serialize( ByteBuffer target );
}
Aubin
  • 14,617
  • 9
  • 61
  • 84