I am writing a program in Java which displays a range of afterschool clubs (E.G. Football, Hockey - entered by user). The clubs are added into the following ArrayList:
private ArrayList<Club> clubs = new ArrayList<Club>();
By the followng Method:
public void addClub(String clubName) {
Club club = findClub(clubName);
if (club == null)
clubs.add(new Club(clubName));
}
'Club' is a class with a constructor - name:
public class Club {
private String name;
public Club(String name) {
this.name = name;
}
//There are more methods in my program but don't affect my query..
}
My program is working - it lets me add a new Club Object into my arraylist, i can view the arraylist, and i can delete any that i want etc.
However, I now want to save that arrayList (clubs) to a file, and then i want to be able to load the file up later and the same arraylist is there again.
I have two methods for this (see below), and have been trying to get it working but havent had anyluck, any help or advice would be appreciated.
Save Method (fileName is chosen by user)
public void save(String fileName) throws FileNotFoundException {
String tmp = clubs.toString();
PrintWriter pw = new PrintWriter(new FileOutputStream(fileName));
pw.write(tmp);
pw.close();
}
Load method (Current code wont run - File is a string but needs to be Club?
public void load(String fileName) throws FileNotFoundException {
FileInputStream fileIn = new FileInputStream(fileName);
Scanner scan = new Scanner(fileIn);
String loadedClubs = scan.next();
clubs.add(loadedClubs);
}
I am also using a GUI to run the application, and at the moment, i can click my Save button which then allows me to type a name and location and save it. The file appears and can be opened up in Notepad but displays as something like Club#c5d8jdj (for each Club in my list)
You should use Java's built in serialization mechanism.
To use it, you need to do the following:
Declare the Club class as implementing Serializable:
public class Club implements Serializable {
...
}
This tells the JVM that the class can be serialized to a stream. You don't have to implement any method, since this is a marker interface.
To write your list to a file do the following:
FileOutputStream fos = new FileOutputStream("t.tmp");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(clubs);
oos.close();
To read the list from a file, do the following:
FileInputStream fis = new FileInputStream("t.tmp");
ObjectInputStream ois = new ObjectInputStream(fis);
List<Club> clubs = (List<Club>) ois.readObject();
ois.close();
As an exercise, I would suggest doing the following:
public void save(String fileName) throws FileNotFoundException {
PrintWriter pw = new PrintWriter(new FileOutputStream(fileName));
for (Club club : clubs)
pw.println(club.getName());
pw.close();
}
This will write the name of each club on a new line in your file.
Soccer
Chess
Football
Volleyball
...
I'll leave the loading to you. Hint: You wrote one line at a time, you can then read one line at a time.
Every class in Java extends the Object class. As such you can override its methods. In this case, you should be interested by the toString() method. In your Club class, you can override it to print some message about the class in any format you'd like.
public String toString() {
return "Club:" + name;
}
You could then change the above code to:
public void save(String fileName) throws FileNotFoundException {
PrintWriter pw = new PrintWriter(new FileOutputStream(fileName));
for (Club club : clubs)
pw.println(club); // call toString() on club, like club.toString()
pw.close();
}
In Java 8 you can use Files.write() method with two arguments: Path and List<String>, something like this:
List<String> clubNames = clubs.stream()
.map(Club::getName)
.collect(Collectors.toList())
try {
Files.write(Paths.get(fileName), clubNames);
} catch (IOException e) {
log.error("Unable to write out names", e);
}
This might work for you
public void save(String fileName) throws FileNotFoundException {
FileOutputStream fout= new FileOutputStream (fileName);
ObjectOutputStream oos = new ObjectOutputStream(fout);
oos.writeObject(clubs);
fout.close();
}
To read back you can have
public void read(String fileName) throws FileNotFoundException {
FileInputStream fin= new FileInputStream (fileName);
ObjectInputStream ois = new ObjectInputStream(fin);
clubs= (ArrayList<Clubs>)ois.readObject();
fin.close();
}
ObjectOutputStream.writeObject(clubs)
ObjectInputStream.readObject();
Also, you 'add' logic is logically equivalent to using a Set instead of a List. Lists can have duplicates and Sets cannot. You should consider using a set. After all, can you really have 2 chess clubs in the same school?
To save and load an arraylist of
public static ArrayList data = new ArrayList ();
I used (to write)...
static void saveDatabase() {
try {
FileOutputStream fos = new FileOutputStream("mydb.fil");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(data);
oos.close();
databaseIsSaved = true;
}
catch (IOException e) {
e.printStackTrace();
}
} // End of saveDatabase
And used (to read) ...
static void loadDatabase() {
try {
FileInputStream fis = new FileInputStream("mydb.fil");
ObjectInputStream ois = new ObjectInputStream(fis);
data = (ArrayList<User>)ois.readObject();
ois.close();
}
catch (IOException e) {
System.out.println("***catch ERROR***");
e.printStackTrace();
}
catch (ClassNotFoundException e) {
System.out.println("***catch ERROR***");
e.printStackTrace();
}
} // End of loadDatabase
Related
I have a method that writes data from a list to a file, a method that reads data from a file into a list and a method that writes data from a list in a file to the specified number of times. I'm trying to extract data from a file after I use the first method writeFile () everything works fine. I read the data from the file into the list by readFile () method. After that I use my method which writes to the file the number of times I need, everything is fine, it writes multyWrite (). But after that I can not read the data from the file in the readFile () method since I get `
Exception stack trace:
Exception in thread "main" java.io.StreamCorruptedException: invalid type code: AC
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1599)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)
at ProductService.readFile(ProductService.java:47)
at Main.main(Main.java:21)
I know that I should use objectOutputStream.reset (), but where would it be better to use it?
private String fileName;
private ProductInterface<FlyingMachine> productService = new ProductInterfaceImpl();
private ObjectOutputStream objectOutputStream;
private FileOutputStream fileOutputStream;
public ProductService(String fileName) throws IOException {
this.fileName = fileName;
fileOutputStream = new FileOutputStream(fileName);
this.objectOutputStream = new ObjectOutputStream(fileOutputStream);
}
public void writeFile() throws IOException {
try {
for (FlyingMachine f : productService.getProductContainer()) {
objectOutputStream.writeObject(f);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (objectOutputStream != null) {
objectOutputStream.flush();
objectOutputStream.close();
fileOutputStream.close();
}
}
}`
public void readFile() throws IOException {
ObjectInputStream objectInputStream = null;
FileInputStream fileInputStream = null;
try {
fileInputStream = new FileInputStream(fileName);
objectInputStream = new ObjectInputStream(fileInputStream);
while (fileInputStream.available() > 0) {
FlyingMachine flyingMachine = (FlyingMachine) objectInputStream.readObject();
productService.getProductContainer().add(flyingMachine);
}
} catch (ClassNotFoundException | EOFException e) {
e.printStackTrace();
} finally {
if (objectInputStream != null) {
objectInputStream.close();
fileInputStream.close();
}
}
}
public void multyWrite(int number) throws IOException {
for (int i = 0; i < number; i++) {
try {
fileOutputStream = new FileOutputStream(fileName, true);
objectOutputStream = new ObjectOutputStream(fileOutputStream);
for (FlyingMachine f : productService.getProductContainer()) {
objectOutputStream.writeObject(f);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (objectOutputStream != null) {
objectOutputStream.flush();
objectOutputStream.close();
}
}
}
}
You create a new ObjectOutputStream in the constructor. In writeFile you use that OOS instance and close it. But in multyWrite you don't use it and instead create new instances.
Now when you call multyWrite without having called writeFile first, that first OOS will still be open, but the OOS you create in multyWrite doesn't know that - thus causing your file to have two OOS headers after another.
And then when you try to read such a file, the ObjectInputStream will find the first header (all is fine) and then unexpectedly find the second header, while it expected a type code. That header starts with 0xAC, hence throwing the exception message "invalid type code: AC".
To fix this, either have multyWrite use the OOS constructed in your constructor, the same way writeFile does, or make sure that that OOS is closed before you create a new one.
It's generally not a good idea to open a stream (of any kind) in a constructor and then rely on external code calling a specific method to close it. Better create streams when you need them and close them directly.
I'm basically making a journal app where each individual journal entry needs to persist, and I would like to keep all entries in a single file.
I've seen tons of tutorials on serializing a single object and so I came up with this solution, (which doesn't work) but even if I manage to fix it, it feels like a sloppy solution.
(Here I'm trying to serialize an arraylist, and each time I save an entry, i de-serialize the list and add the new entry to the list before serializing again)
To clarify, my question is: s this a good way to save objects to the same file, on multiple occasions?
Or does anyone have some tips about something else I should try, links to videos or documentation regarding this is also appreciated.
public class Serializer
{
//Calls readFile and adds the returned entries to an ArrayList
//Add the target object to the list and write to the file
public static void writeToFile(Object target)
{
ArrayList entries = new ArrayList();
entries = readFile();
entries.add(target);
String filename = "entries.bin";
FileOutputStream fileOut = null;
ObjectOutputStream objOut = null;
try
{
fileOut = new FileOutputStream(filename);
objOut = new ObjectOutputStream(fileOut);
objOut.writeObject(entries);
objOut.close();
}
catch(IOException ex)
{
ex.printStackTrace();
}
}
//Reads the file and returns all entries in a list
public static ArrayList readFile ()
{
ArrayList persistedEntries = new ArrayList<>();
String filename = "entries.bin";
FileInputStream fileIn = null;
ObjectInputStream objIn= null;
try
{
fileIn = new FileInputStream(filename);
objIn = new ObjectInputStream(fileIn);
persistedEntries = (ArrayList) objIn.readObject();
objIn.close();
}
catch(IOException ex)
{
ex.printStackTrace();
}
catch(ClassNotFoundException ex)
{
ex.printStackTrace();
}
return persistedEntries;
}
}
Is this a good way to save objects to the same file, on multiple occasions?
I would argue no. This is because your method writeToFile or more accurately appendToFile can introduce strange behaviour in edge cases (such as entries.bin having an unexpected object). I would argue for this:
Use writeToFile(ArrayList<Object> target) to overwrite the file with the specified array. Then add a method appendToFile(Object target) that handles the process of reading from entries.bin from the disk, appending target then writing the array to the disk. This has the advantage of separating any logic related to 'merging' the new object target with the file on disk, and the actual logic of writing to the entries.bin file.
If just a learning exercise I would go with the above. Potential resource
Adding a reformatted version:
public class Serializer
{
private String filename;
// pass in "entries.bin"
public Serializer(String filename) {
this.filename = filename;
}
public void append(Object target) {
// readfile will return at least empty arraylist
ArrayList entries = readFile();
entries.add(target);
serialize(entries);
}
public void serialize(ArrayList entries)
{
FileOutputStream fileOut = null;
ObjectOutputStream objOut = null;
try
{
fileOut = new FileOutputStream(filename);
objOut = new ObjectOutputStream(fileOut);
objOut.writeObject(entries);
objOut.close();
}
catch(IOException ex)
{
ex.printStackTrace();
}
}
//Reads the file and returns all entries in a list
public ArrayList deserialize ()
{
ArrayList persistedEntries = new ArrayList<>();
FileInputStream fileIn = null;
ObjectInputStream objIn = null;
try
{
fileIn = new FileInputStream(filename);
objIn = new ObjectInputStream(fileIn);
Object result = objIn.readObject();
if (!(result instanceof ArrayList)) {
// read object is not an arraylist
}
persistedEntries = (ArrayList) objIn.readObject();
objIn.close();
}
catch(IOException ex)
{
ex.printStackTrace();
}
catch(ClassNotFoundException ex)
{
ex.printStackTrace();
}
return persistedEntries;
}
}
I'm experimenting with Serialization and wrote the following class:
public static void main(String[] args) throws ClassNotFoundException{
File file = new File("D:/serializable.txt");
try(FileOutputStream fos = new FileOutputStream(file);
ObjectOutputStream ous = new ObjectOutputStream(fos);
FileInputStream fis = new FileInputStream(file);
ObjectInputStream ois = new ObjectInputStream(fis)){
// SerialTest st = new SerialTest();
// ous.writeObject(st);
SerialTest st = (SerialTest) ois.readObject();
System.out.println(st);
} catch (IOException e) {
e.printStackTrace();
}
}
Serialized class:
public static class SerialTest implements Serializable{
private int count;
private Object object;
public int count(){
return count;
}
public Object object(){
return object;
}
private void readObject(ObjectOutputStream ous) throws IOException{
ous.writeObject(object);
ous.writeInt(count);
}
private void writeObject(ObjectInputStream ois) throws IOException, ClassNotFoundException{
ois.readInt();
ois.readObject();
}
}
After serializing the object as in the commeted code, I tried to desirialize it as specified here. I got
java.io.EOFException
at java.io.ObjectInputStream$BlockDataInputStream.peekByte(ObjectInputStream.java:2598)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1318)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:370)
Moroever, the content of the file containing the serialized object is chaged.
But when I remove the resource-declarations
FileOutputStream fos = new FileOutputStream(file);
ObjectOutputStream ous = new ObjectOutputStream(fos);
from the try-with-resources clasuse it works completely fine. Why? Why the resource declarations affects the deserialization?
Well without the write calls, you're currently creating an empty file to start with, because that's what the FileOutputStream constructor you're calling does. If the file already exists, it is truncated to be 0 bytes long. So when you then try to read an object from it, there's nothing to read.
Even with the writing part uncommented, there's still the possibility of buffering issues, where the data hasn't actually been written to the file yet.
I would strongly urge you to write the file and close the output stream, and then separately open it for input. Having the same file open for read and write at the same time seems like a recipe for confusing results to me.
So the code would look something like this:
// Exception handling omitted as this is just test code
public static void main(String[] args) throws Exception {
File file = new File("D:/serializable.txt");
try (FileOutputStream fos = new FileOutputStream(file);
ObjectOutputStream ous = new ObjectOutputStream(fos)) {
SerialTest st = new SerialTest();
ous.writeObject(st);
}
try (FileInputStream fis = new FileInputStream(file);
ObjectInputStream ois = new ObjectInputStream(fis)) {
SerialTest st = (SerialTest) ois.readObject();
System.out.println(st);
}
}
Leaving aside the fact that you obviously have the content of readObject() and writeObject() back to front:
private void readObject(ObjectOutputStream ous) throws IOException{
ous.writeObject(object);
ous.writeInt(count);
}
Here you are writing first the object then the integer.
private void writeObject(ObjectInputStream ois) throws IOException, ClassNotFoundException{
ois.readInt();
ois.readObject();
}
Here you are reading first the integer then the object.
Ain't gonna work.
BUT ... You don't need either of these methods. Remove them. Or, if you want to serialize data of the parent class, fix them to (a) call defaultReadObject() and defaultWriteObject() respectively, and get rid of what you already have in there, which will already happen by default, or at least when you read the object and the integer in the same order as you wrote them, store them into the respective instance members.
NB serialized data isn't text and shouldn't be stored in files named .txt.
I have admin account which should be able to add many users to a .dat file. Then I want to retrieve all the objects from the .dat file into a list for further programming.
public class User implements Serializable { //get and set methods }
This is hwo I am writing each object to the .dat file
public void addNewUser() throws Exception {
User newUser=new User();
newUser.name="test";
newUser.position="admin";
FileOutputStream outStream = new FileOutputStream("Users.dat", true);
ObjectOutputStream objectOutputFile = new ObjectOutputStream(outStream);
// Write the object to the file.
objectOutputFile.writeObject(newUser);
// Close the file.
objectOutputFile.close();
}
How can retrieve all the objects from the .dat file into ArrayList??
public class displayUsers { **//what to do??** }
You can either write the list object and read it as list. But since you're writing user objects individually, you can do something like this -
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("Users.dat"));
Object object = null;
while ((object = ois.readObject()) != null) {
if (object instanceof User) {
User user = (User) object;
list.add(user);
}
}
Of course, you would need to take care of exceptions (like EOFException).
Generally it is bad practice to concatenate individual ObjectOutputStreams in a file without adding any lengths or delimiters. So better write all objects in one pass (and use ObjectOutputStream.reset in case your process is long-running and you fear memory leaks (otherwise ObjectOutputStream will keep a reference to every object it serialized before) or add them to a List and write it.
If you have to write it in multiple passes, I'd suggest to write the individual objects to a ByteArrayOutputStream first, and then use DataOutputStream to write the array prefixed by its length. That way, you can use DataInputStream to get out the individual byte arrays and use ByteArrayInputStream to deserialize them.
In case this does not work, you can try this solution (depending on the lookahead used by ObjectInputStream, this might not work for more complex objects with custom serialization formats, though, so use at your own risk):
public static void displayUsers() throws Exception {
FileInputStream fiis = new FileInputStream("Users.dat");
InputStream fis = new FilterInputStream(fiis) {
#Override
public void close() throws IOException {
// ignore
}
};
try {
while (true) {
ObjectInputStream in = new ObjectInputStream(fis);
User user = (User) in.readObject();
in.close();
System.out.println(user.name + "/" + user.position);
}
} catch (EOFException ex) {
// done
}
fiis.close();
}
List<User> listOfUser = new ArrayList<User>();
ObjectInputStream input = null;
try {
while (true) {
input = new ObjectInputStream(new FileInputStream("Users.dat"));
listOfUser.add(input.readObject());
}
}
catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
finally {
input.close();
}
I have this serializable class, wich i use to store on a binary file an ArrayList of Strings.
public class SaveState implements Serializable{
public static ArrayList <String> favoriteBusStopNumbers = new ArrayList<String>();
public static SaveState instance=new SaveState();
}
I'm using this method to store the instance with the arrayList of strings once this array is full with the data i must store:
public static void saveData(){
ObjectOutput out;
try {
//primero comprobamos si existe el directorio, y si no, lo creamos.
File folder = new File(Environment.getExternalStorageDirectory() + DIRECTORY_NAME);
if(!folder.exists())
folder.mkdirs();
File outFile = new File(Environment.getExternalStorageDirectory(), DIRECTORY_NAME+"appSaveState.data");
out = new ObjectOutputStream(new FileOutputStream(outFile));
out.writeObject(SaveState.instance);
out.close();
} catch (Exception e) {e.printStackTrace();}
}
And finally, i use this method on the init of my app to load the file and fill my SaveState.instance variable with the previously stored data:
public static void loadData(){
ObjectInput in;
try {
File inFile = new File(Environment.getExternalStorageDirectory(), DIRECTORY_NAME+"appSaveState.data");
in = new ObjectInputStream(new FileInputStream(inFile));
SaveState.instance=(SaveState) in.readObject();
in.close();
} catch (Exception e) {e.printStackTrace();}
}
When i save the data, the file is being created correctly with the object fill of data, i know it because the file has more than 0KB of disc space. But something is going wrong here because when i start my app and load the data, my SaveState.instance variable gets an empty ArrayList of strings....... then ¿what is wrong in the code?
Thanks
Your arraylist is static. Static variables are associated with the class and not with the object. So they dont get serialized.
If you do want to serialize static variables, you have to override readObject and writeObject.
Here is some additional info -- http://java.sun.com/developer/technicalArticles/Programming/serialization/
Best solution here is to reconsider your data structure and make the arraylist non-static if its saving the state of an object.
EDIT: Alternatively, you could serialize just the arraylist ( as #Ted suggests below )
The problem is that variables marked as static will not be serialized except you implement
private void readObject(ObjectInputStream ois){}
and
private void writeObject(ObjectOutputStream oos){}
ObjectOutputStream - JavaDoc:
The default serialization mechanism for an object writes the class of
the object, the class signature, and the values of all non-transient
and non-static fields.
Since the only data kept in SaveState is a static array, you'll have more success just serializing and deserializing the array:
public static void saveData(){
ObjectOutput out;
try {
//primero comprobamos si existe el directorio, y si no, lo creamos.
File folder = new File(Environment.getExternalStorageDirectory() + DIRECTORY_NAME);
if(!folder.exists())
folder.mkdirs();
File outFile = new File(Environment.getExternalStorageDirectory(), DIRECTORY_NAME+"appSaveState.data");
out = new ObjectOutputStream(new FileOutputStream(outFile));
out.writeObject(SaveState.favoriteBusStopNumbers);
out.close();
} catch (Exception e) {e.printStackTrace();}
}
#SuppressWarnings("unchecked")
public static void loadData(){
ObjectInput in;
try {
File inFile = new File(Environment.getExternalStorageDirectory(), DIRECTORY_NAME+"appSaveState.data");
in = new ObjectInputStream(new FileInputStream(inFile));
SaveState.favoriteBusStopNumbers=(ArrayList<String>) in.readObject();
in.close();
} catch (Exception e) {e.printStackTrace();}
}
(You need to suppress the warning about casting to a generic type.)
You don't really need the SaveState.instance field at all.