Java: Escribiendo varios objetos en un archivo

Zerokkk

Buenas! No os voy a rallar mucho, así que seré directo:

Quiero escribir objetos en un archivo, de forma que pueda escribirlos en tiempos separados (es decir, no todos a la vez, sino que pueda hacer otras cosas entre uno y otro), y luego puedan ser leídos todos de golpe en un método de inicialización para meter los datos de cada uno en la tabla.

Este es el código de escritura que estoy usando:

  try{
      ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("NoteList.txt"));     
      
oos.writeUnshared(nota); oos.close(); } catch(IOException excp){ excp.getMessage(); }

Lo que actualmente me hace con cada escritura es sobreescribir el objeto anterior. Por si lo preguntáis, sí, tengo la clase Nota con el serializer implementado.

¿Cómo hago para que me los meta uno a uno?

DaRk-eXe

EN C++ hay un puntero que se posiciona donde queramos antes de escribir, supong que en Java habrá otro, lo posicionas al final del fichero y escribes.

Supongo que por defecto se te pondrá al principio del fichero, lo pones al final y así no sobreescribe.

elkaoD

#1 si el segundo parámetro del constructor de FileOutputStream = true, el contenido se añade al final.

http://docs.oracle.com/javase/7/docs/api/java/io/FileOutputStream.html

Parámetro append.

De todas formas, ¿no puedes dejar el stream abierto y reutilizarlo? Si todo se hace en una misma sesión, aunque no sea a la vez, no es mala idea lo de dejar el archivo abierto y reusarlo.

1 2 respuestas
B

#3 fatal error xD luego viene cuando por lo que sea peta la aplicación y se joden los archivos y demás...

No le deis malas costumbres al chaval que cuando haga algo serio si hace eso puede hacer un destrozo.

Yo de ti haría algo mas simple y que a la larga es mas practica, las notas guárdalas en un xml y usa una de las apis xml para añadir nodos y tal.

Si no puedes usar xml pos no te toca otra que antes de escribir en el archivo posicionarte después del ultimo texto antes de escribir, ya que si abres por escritura normal un file se abre por el inicio.

2 respuestas
elkaoD

#4 ¿a qué te refieres?

Dejar un stream abierto mientras lo estás usando no jode ningún archivo. Es EXACTAMENTE igual. Mientras que te asegures de hacer flush(), ¿qué problema hay?

http://stackoverflow.com/questions/515975/closing-streams-in-java (la respuesta con más votos, el resto son crap).

No veo razón alguna para cerrar un stream si lo vas a volver a usar en seguida (otra cosa es si vas a escribir cada hora, pero no es lo que entendí de #1).

2 respuestas
Zerokkk

#5 Pero eso tiene un problema, aparte de lo que dice #4 que se ha comentado en el ciclo, sucede que cuando cierre y abra el programa, el stream será distinto y por tanto empezarán a aparecer problemas. La idea es que, con streams distintos, funcione bien.

Voy a probar lo primero que dijiste en #3, ahora cuento.

edit: Bien, sí que funciona.

Ahora viene otro tema: la lectura. He comprobado el archivito y me está creando correctamente los objetos, sin importar el stream ni nada, por lo que al no haber solapamientos, la inserción de datos está completada. Pero la lectura no tengo ni pajolera de cómo sacarla para que me lea absolutamente todo...

¿Me es mejor guardar un único arraylist, o incluso treemap? ¿O hay alguna buena forma de poder sacar todos los objetos?

Código de lectura actual, que sólo me lee el primer elemento del archivo, y luego lo añade a la tabla:

  
public void initialize(){ try{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("NoteList.txt")); try { for(int i=0;i<5;i++){ Object obj = ois.readObject(); if(obj instanceof Note){ Note not = (Note) obj; tabla.agregarNota(not); table.setModel(tabla); } } } catch (ClassNotFoundException ex) { Logger.getLogger(NotesFrame.class.getName()).log(Level.SEVERE, null, ex); }ois.close(); } catch(IOException excp){ excp.getMessage(); } }
2 respuestas
elkaoD

#6 que no os engañen :P Lo de cerrar SIEMPRE los archivos de dice como una máxima porque es la mejor forma de evitar errores, pero como toda máxima, tiene excepciones.

La nueva pregunta no la entiendo. ¿Qué falla ahí? Debería leerte los 5 objetos.

1 1 respuesta
Zerokkk

#7 Mira el edit completo. La idea es, que en ese método (que inicializa el frame donde tengo la tabla, de modo que la tabla aparezca ya con todos los datos), saque del archivito de antes los objetos (las notas) y las meta en la tabla.

El problema es que no tengo ni pajolera idea de cómo hacer que me saque más de un objeto. Actualmente me saca sólo el primero.

edit: El for es de prueba, eh? Lo hice para probar, nada más. Y no, no ha funcionado xD.

edit 2: La profesora en clase me dijo que un while(//instancia del stream.readObject()!=null){//codigo} debería funcionar, pero si hago eso, la tabla no me muestra absolutamente nada. Vamos, que no pasa por el while.

1 respuesta
elkaoD

#8 tu profesora tiene razón (sorprendentemente), como dice debería funcionar. También me extraña que #6 no te funcione con el for... tal como está debería mostrarte los cinco objetos.

¿No te estará soltando una excepción y la estarás ignorando? (véase que haces excp.getMessage(); pero no lo imprimes).

1 respuesta
Zerokkk

#9 Vale, con el ejemplo del for me ha soltado: "invalid type code: AC". ¿Qué significa?

Lo de no imprimir la excepción me pasa por ir rápido, menudo detalle más tonto xD.

1 respuesta
elkaoD

#10 buscando en Google con "invalid type code: AC" -socket (quito socket porque salían muchos con sockets, que no es lo que buscas).

http://stackoverflow.com/questions/3182240/java-io-streamcorruptedexception-invalid-type-code-ac

"You can't append to an ObjectOutputStream. That will definitly corrupt the stream and you get StreamCorruptedException."

"You have to write all cheque objects in 'one session'. Or use the AppendableObjectOutputStream instead of the standard ObjectOutputStream."

Mira en http://stackoverflow.com/questions/1194656/appending-to-an-objectoutputstream/1195078#1195078, básicamente no escriben cabeceras y usan reset().

Tienes tres opciones:

1. Dejas el stream abierto como comentaba en #3.
2. Cada vez que escribas haces el write entero.
3. Usas el AppendableObjectOutputStream que se han currado.

Depende del caso de uso una será mejor que otra:

1. Lo de dejar el stream abierto evita que el archivo te lo modifiquen externamente (que para mí tiene sentido, sigo pensando que lo de #3 no es descabellado). El archivo siempre reflejará lo que ves en tu aplicación. Esto lo hacen la mayoría de aplicaciones (¿no te ha pasado nunca lo de "no se puede borrar el archivo, está siendo usado"?) y con razón.
2. Escribirlo entero viene bien si aunque modifiquen el archivo externamente tú quieres que el estado de tu aplicación sea el que se guarde. Va a machacar TODO lo que haya y dejar solo lo que hubiera en tu aplicación a la hora de escribir.
3. Mantener el append viene bien si quieres que se pueda modificar el archivo externamente y además no perder cambios. Sin embargo, tu aplicación NO va a ver cosas que ocurran externamente hasta que hagas una recarga.

Yo personalmente dejaría el Stream abierto como comento en #3. Tu aplicación es dueña del archivo y fin. Asegúrate de hacer flush() para guardar siempre tras los writes (y close() al salir) y todo debería ir guay.

La 2 tampoco es mala solución (aunque suerte guardando 10000 notas todas de golpe).

Para mí lo de hacer append es la más guarra de todas. Demasiados casos problemáticos.

1 respuesta
Zerokkk

#11 El output? No sería más bien el input? Es decir, escribo perfectamente, el problema es al leer. ¿Hago entonces mi propia clase del OOS reescribiendo el método que comentan en el link?

1 respuesta
elkaoD

#12 he editado #11, míralo.

Y sí, es el Output. Tu problema es que no puedes usar un OutputStream usando append porque cada OutputStream escribe su cabecera (y no tiene sentido dos cabeceras en el mismo archivo).

Insisito en que el problema es que (cito) "You can't append to an ObjectOutputStream.", "You have to write all [...] objects in 'one session'."

1 respuesta
Zerokkk

#13 No está solucionando nada :/

    try{
        
File arch = new File("NoteList.txt"); if(arch.exists()){ AppendableObjectOutputStream oos = new AppendableObjectOutputStream(new FileOutputStream(arch, true)); oos.writeUnshared(nota); oos.close(); } else { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(arch, true)); oos.writeUnshared(nota); oos.close(); }

Esto es lo que tengo ahora para escribir. Los true como parámetros no cambian nada, ya he probado sin ellos (o cambiando) y nada. El método de lectura no sé si estará mal, pero ahora en lugar de leer el primer fichero, me lee el último:

while(ois.readObject()!=null){
                Object obj = ois.readObject();
                 
if(obj instanceof Note){ Note not = (Note) obj; tabla.agregarNota(not); table.setModel(tabla); }

La verdad que el sistema de ficheros en Java está hecho un caos.

1 respuesta
B

#5 si lo deja abierto durante corto tiempo no pasa nada, yo pensaba que te referías a dejarlo abierto durante toda la ejecución del programa, para solo guardar datos de vez en cuando.

1 respuesta
elkaoD

#14 si en el .txt está todo y sin embargo ahora te lee solo el último... es que en realidad te está leyendo todo (haz un println de cada Note en cada iteración del bucle y te aseguras).

Me juego un dedo a que tu problema ahora está en tabla.agregarNota o algo así.

"La verdad que el sistema de ficheros en Java está hecho un caos."

Nope, está muy, muy bien hecho. Lo que pasa es que es complicado manejar archivos (con o sin Java) y no cagarla. Java simplemente te evita que la cagues (y te frustra porque no te deja cagarla xD) pero créeme que es por tu bien.

#15 lee #11 y verás por qué tampoco es descabellado dejarlo abierto durante toda la ejecución. Insisto: ¿cuál es el problema? Datos no va a perder si hace flush() y el handle del archivo da igual que esté abierto o no.

A mí me parece conceptualmente lógico que si un programa es "dueño" del archivo durante toda la ejecución este no se cierre. El 99% de programas lo hacen y con razón. El 1% restante simplemente machaca al guardar (opción 2 de #11). Hacer append es buscarse dolores de cabeza.

Como digo en #7, toda máxima tiene excepciones.

1 respuesta
Zerokkk

#16 No, no. Acabo de comprobarlo y, efectivamente, me lee lo mismo que se muestra en la tabla. El problema reside en el guardado o en la lectura de datos, no sé bien donde.

Ahora mismo el programa, si añado dos notas, sólo se guarda bien la primera. Pero ojo, ahora ésta SÍ que se guarda bien, ésta se queda siempre y cuando vuelvo a abrir el programa ahí sigue. En todas las siguientes ejecuciones que haga pasará igual, e iré viendo al abrir el programa sólo las primeras notas añadidas en los momentos anteriores, siempre y cuando haya habido en el mismo uso, otro añadido. Es decir, que si sólo añado una nota no se guarda, ésta sólo se guarda si después, añado otra más (la cual se pierde).

Menudo lol, por qué sucede esto? Voy a seguir viendo el código un rato, pero si se os ocurre algo, será de gran ayuda xD.

PD: Voy a probar a meterlos con el mimso ObjectOutputStream.
edit: ¿Y cómo se supone que he de instanciar el OOS para usarlo en toda la clase (de tipo Frame) sin problemas? ¿Con definirlo (sin instanciarlo) en la clase me llega? No lo tengo muy claro, no con estas instancias que requieren de try y catch.

Zerokkk

¡Arreglado! Dejo la solución para gente que tenga el mismo problema en el futuro. Gracias a elKaoD por los consejos, sin ellos no habría conseguido hacerlo xD:

Escritura:

 try{
        
// Definimos el archivo File arch = new File("NoteList.txt"); /* IMPORTANTE: Esto es tal que, si el archivo existe, utilizamos una clase propia (que os facilito luego) que es básicamente una sobreescritura de uno de los métodos de ObjectOutputStream, que deja escribir al final del fichero. Si no, utilizamos la clase básica, para que pueda poner cabecera (lo cual es necesario). */ if(arch.exists()){
AppendableObjectOutputStream oos = new AppendableObjectOutputStream(new FileOutputStream(arch, true)); // Recordad utilizar writeUnshared! oos.writeUnshared(nota); oos.close(); } else { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(arch, true)); oos.writeUnshared(nota); oos.close(); }

Clase ObjectOutputStream propia:

 public class AppendableObjectOutputStream extends ObjectOutputStream {

  public AppendableObjectOutputStream(OutputStream out) throws IOException {
    super(out);
  }

  @Override
  protected void writeStreamHeader() throws IOException {
    // do not write a header, but reset:
    // this line added after another question
    // showed a problem with the original
    reset();
  }

}

Método de lectura:

    
try{ // Instanciamos nuestro ObjectInputstream con el archivo en el que hemos escrito. ObjectInputStream ois = new ObjectInputStream(new FileInputStream("NoteList.txt")); try { /* AVISO: Esto es un poco chapuzas, pero no encontré nada mejor. Poned como límite un número alto que sepáis que no vaya a ser alcanzado, y listo. He probado con el while(ois.readObject!=null) y da problemas tales como sólo leer uno de cada 2 objetos, así que por ahora creo que este es el más acertado. Como nota, también podéis hacer un archivo de texto donde tengáis un simple número que indique cuantos objetos hay escritos, y exportarlo aquí, en caso de que queráis romperos un poco más la cabeza y queráis un programa más escalable xD. */ for(int i=0;i<500;i++){ Object obj = ois.readObject();
if(obj instanceof Note){ Note not = (Note) obj; /* Llegados a este punto, ya tenemos el objeto listo para ser usado. Yo, por ejemplo, lo añado a una tabla.*/ tabla.agregarNota(not); table.setModel(tabla); allNotes.put(i, not); } } } catch (ClassNotFoundException ex) { Logger.getLogger(NotesFrame.class.getName()).log(Level.SEVERE, null, ex); }ois.close(); } catch(IOException excp){ System.out.println(excp.getMessage()); }
1
kraneok

Aquí tienes un breve ejemplo de fichero secuencial.

public void escribeArchivo()
    {
        String nombre;
        boolean seguir = true;
        
while(seguir) { entrada = new Scanner(System.in); System.out.println("Escribe los datos del alumno : Nombre, Direccion, Edad, Curso, Nota"); nombre = entrada.nextLine(); if(!nombre.equals("")) { String direccion = entrada.next(); int edad = entrada.nextInt(); String curso = entrada.next(); float nota = entrada.nextFloat(); archivo.format("%d \t\t %s \t\t %s \t\t %d \t\t %s \t\t %.2f\n", indice, nombre, direccion, edad, curso, nota); indice++; } else { seguir = false; } } archivo.close(); }

Usuarios habituales