JAVA - Gravar Objectos em Ficheiro, e carregar os Objectos do Ficheiro para ArrayList

Rexobias

Power Member
Malta, precisava de ajuda urgente aqui com um problema que me tem destruido a paciência. O programa é em Java e estou a usar o netbeans.

Tenho umas textFields que correspondem a cada um dos campos de uma classe. O objecto é facilmente criado através de um método que é chamado no evento de um botão "Criar". Após criado o objecto, chamo o seguinte método para gravar o mesmo num ficheiro:

Código:
public void escrever(CartaoFuncionario func) throws FileNotFoundException, IOException {
        ObjectOutputStream file = new ObjectOutputStream((new FileOutputStream(ficheiroFunc, true)));
        ObjectOutputStream buffer = new ObjectOutputStream(file);
        try {
            buffer.writeObject(func);
 
        } catch (Exception e) {
 
            e.printStackTrace();
        } finally {
            buffer.close();
        }
    }

Ao abrir o ficheiro criado (através do bloco de notas), noto que a informação é armazenada no mesmo sem problemas, como é pretendido.

O meu próximo objectivo é ter um método que carregue o conteudo do ficheiro para um ArrayList do tipo dos objectos que foram guardados. Tenho o seguinte método, que infelizmente resulta em erros, ou seja, não consigo ler os objectos do ficheiro:

Código:
    public void ler() throws IOException{
 
        ObjectInputStream ficheiro = new ObjectInputStream(
        new FileInputStream(ficheiroFunc));
 
        try {
            cartoesFunc.add((CartaoFuncionario) ficheiro.readObject());
        }
 
        catch (ClassNotFoundException ex){
 
        }
        ficheiro.close();
    }

As varíáveis e afins estão todas bem declaradas, o problema está no código, muito provavelmente no método de leitura. Pensava que este pequeno objectivo fosse fácil de cumprir mas a verdade é que não tenho conseguido fazer o pretendido.

Help meus caros Javáticos :sad:
 
Suponho que estejas a chamar várias vezes o método de escrita. O problema pode estar no facto de estares a fazer append ao ficheiro quando o que devias estar a fazer é chamar várias vezes o writeObject da mesma ObjectOutputStream. E.g.
public void escrever(CartaoFuncionario func, ObjectOutputStream oos) {...}
 
Que erros é que dá?
Eu fiz assim e funcionou:
Código:
ArrayList<CartaoFuncionario> l = new ArrayList<CartaoFuncionario>();
CartaoFuncionario c1 = null;
        ObjectInputStream ooin = null;
        try {
            ooin = new ObjectInputStream(new FileInputStream("ficheiro.obj"));
            c1 = (CartaoFuncionario) ooin.readObject();
            ooin.close();
        } catch (IOException e) { System.out.println(e.getMessage());}
        catch (ClassNotFoundException e) { System.out.println(e.getMessage()); }
        l.add(c1);

Já agora, porque é que na escrita estás a abrir uma ObjectOutputStream sobre outra ObjectOutputStream? Pelo nome que deste à 2ª penso que querias um buffer, nesse caso seria BufferedOutputStream e nem precisavas de a declarar:

Código:
ObjectOutputStream file = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(ficheiroFunc, true)));
 
Outra abordagem que poderias seguir, e até poderia facilitar a tua vida nalguns casos, seria gravar directamente no ficheiro o arraylist de "CartaoFuncionario". Assim bastaria ler e escrever uma vez. Atenção que neste caso, não deves fazer append ao ficheiro, mas sim escrever por cima do seu conteudo.

Foi só uma sugestão:D
 
Obrigado pessoal. nasic essa ideia passou-me pela cabeça, e realmente seria muito mais fácil (GUI -> Classes -> ArrayList -> Ficheiro) mas queria tornar o sistema mais eficaz gravando só o que é novo (claro que vai trazer-me dificuldades quando quiser eliminar registos, mas quando chegar a esse problem logo vejo).

Bem, de qualquer maneira melhorei muito o código com a vossa ajuda, mas ainda tenho um só problema:

método de escrita:
Código:
    public void escrever(CartaoFuncionario func) throws FileNotFoundException, IOException {
        ObjectOutputStream ficheiro = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(ficheiroFunc, true)));
        
        try {
            ficheiro.writeObject(func);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                ficheiro.close();
            }
    }

método de leitura:
Código:
    public void ler() throws IOException{
        CartaoFuncionario cartao = null;
        ObjectInputStream entrada = null;
        try {
            ooin = new ObjectInputStream(new FileInputStream(ficheiroFunc));
            cartao = (CartaoFuncionario) entrada.readObject();
            entrada.close();
        } catch (IOException e) { System.out.println(e.getMessage());}
        catch (ClassNotFoundException e) { System.out.println(e.getMessage()); }
        cartoesFunc.add(c1);        
    }

O meu problema é o seguinte. Eu faço a escrita de vários objectos (vamos supor Antonio, Maria e José (por esta ordem). Faço a escrita para o ficheiro chamando o método de escrita após criar cada objecto (carregar no botao "Criar"). Após criar os 3 objectos, tenho um outro botao que ao carregar nele, chama o método de leitura que devia preencher o ArrayList indicado com todos os 3 objectos anteriormente criados que estão armazenados no ficheiro, e depois chamo um outro metodo que imprime o conteudo do ArrayList.

O problema é que só obtenho o António, o primeiro registo.

Provavelmente preciso de um ciclo While no processo de leitura ... algo do género, enquanto tem registos/objectos no ficheiro vai fazendo o Add dos mesmos no ArrayList.

Cumprimentos.
 
O problema é que só obtenho o António, o primeiro registo.

Provavelmente preciso de um ciclo While no processo de leitura ... algo do género, enquanto tem registos/objectos no ficheiro vai fazendo o Add dos mesmos no ArrayList.

Cumprimentos.
Exactamente, só obtens o primeiro registo pq só lês esse.
para leres todos faz algo do genero:
Código:
CartaoFuncionario obj = null;
 
while ((obj = inputStream.readObject()) != null) {
       if (obj instanceof CartaoFuncionario )
                array.add(obj);
}
Tive para te dizer isto no post anterior, mas assim aprendes mais:P

Já agora "CartaoFuncionario" é serializable certo?
Se isto for chinês é so procurar no google
 
Última edição:
Sim, é tudo serializable, lol :P

Obrigado ... vou ver se resulta.

EDIT: Bem, o código parece-me correcto, mas continua a ler só o primeiro :S Aqui fica o código (sei que estou lá perto):

Código:
    public void ler() throws IOException{
        CartaoFuncionario cartao = null;
        ObjectInputStream streamEntrada = null;
        try {
            streamEntrada = new ObjectInputStream(new FileInputStream(ficheiroFunc));
            while ((cartao = (CartaoFuncionario) streamEntrada.readObject()) != null) {
                cartoesFunc.add(cartao); 
            }            
            streamEntrada.close();
        } catch (IOException e) { System.out.println(e.getMessage());}
        catch (ClassNotFoundException e) { System.out.println(e.getMessage()); }
    }
 
Última edição:
Não será problema da escrita?
Tenta abrir o ficheiro e procurar o que inseriste.és capaz de encontrar texto no meio do "lixo".
Faz debug tb.
 
Actualmente faço assim a escrita:

Código:
    public void escrever(CartaoFuncionario func) throws FileNotFoundException, IOException {
        ObjectOutputStream ficheiro = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(ficheiroFunc, true)));
        
        try {
            ficheiro.writeObject(func);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                ficheiro.close();
            }
    }

Já abri o ficheiro e aparecem blocos de texto. No 1º parece indicar a forma como a classe é construida, nos restantes dá para ver a informaçao dos objectos adicionados - aquilo tem uns gatafunhos misturados.

:S
 
Actualmente faço assim a escrita:

Código:
    public void escrever(CartaoFuncionario func) throws FileNotFoundException, IOException {
        ObjectOutputStream ficheiro = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(ficheiroFunc, true)));
        
        try {
            ficheiro.writeObject(func);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                ficheiro.close();
            }
    }

Já abri o ficheiro e aparecem blocos de texto. No 1º parece indicar a forma como a classe é construida, nos restantes dá para ver a informaçao dos objectos adicionados - aquilo tem uns gatafunhos misturados.

:S
Mas assim só estás a escrever um objecto??? E esse append ao ficheiro vai dar maravilha...
Mostra o código que invoca a função escrever.
 
Indo por partes.

1-Tenho um formulário em ambiente gráfico com Textfields. Através dos getText() das TextFields obtenho os campos que formam a classe. O objecto é criado e guarda os valores enviados.

2-O formulário tem um botão criar que ao ser carregado, chama um evento que consiste em criar o objecto e preencher os campos da mesma com o conteudo das TextFields.

3-Assim que criamos esse objecto ele é enviado para o ficheiro, e guardado no mesmo - por isso uso o append, para ir guardando registos á medida que são criados.

4-Por fim, chamo o método de leitura que retira a informação do ficheiro e guarda o mesmo num ArrayList de Objectos (do tipo que foi guardado anteriormente). Depois tenho um método para testar a leitura do ficheiro que consiste em imprimir o conteudo do ArrayList.

Aqui fica a forma como chamo o método escrever:

Código:
listaFunc.escrever();

listaFunc é um objecto de uma classe que tem como campo o tal ArrayList de objectos e os métodos de escrita e leitura no ficheiro.
 
Uma pequena recomendação:
Se é só os dados que queres guardar, devias optar por serializar isso tudo para XML (e vice-versa). Futuramente se mudares o objecto, não vais ter problemas de leitura porque os objectos depois são diferentes e não vais consegues fazer um cast.
 
A resposta para a tua dúvida é a seguinte: Não podes fazer append! Tens de arranjar outra solução. Talvez a solução do nasic de usar um ArrayList.
In short, what gets written to the file on an append is not the same
as when you do a reset. readObject is not smart enough to ignore
extra stream init stuff in the middle of a stream.

So the short answer is, you can't append. You must write to a new
file, or copy and write to a new file.
http://www.velocityreviews.com/forums/t147205-problem-appending-objects-to-file.html
StreamCorruptedException StreamCorruptedException
The object stream is scrambled. Perhaps you are trying to read human-readable data, e.g. something that was not prepared with writeObject. Perhaps you have classes missing or the classes used in the stream are obsolete. Perhaps you tried to use append mode to tack more onto the end of your stream file after it was closed and later reopened in append mode. That does not work.
http://mindprod.com/jgloss/runerrormessages.html#STREAMCORRUPTEDEXCEPTION
You may not append to the end of a serialized stream once it has been closed and reopened in append mode. The writes will appear to work, but when you go to read the file later you will get a java.io.StreamCorruptedException.
http://mindprod.com/jgloss/gotchas.html#SERIALIZATION
 
Bem, depois de tudo mudei de ideias e vou alterar o esquema do programa, pois não posso perder mais tempo, e estou totalmente dependente deste processo de escrita e leitura para poder terminar o mesmo. Cada vez que crio um registo, adiciono o mesmo ao ArrayList, e guardo o ArrayList num ficheiro. Esquematizando o que estou a pensar:

Abrir Programa:
-Carrega conteudo do Ficheiro para o ArrayList cartoesFunc;

Adicionar novo Cartao:
-Utilizador introduz informação nas TextFields;
-O conteudo é enviado para os campos da classe;
-Adiciona objecto ao ArrayList;
-Grava conteudo do ArrayList no ficheiro;

Esta é a minha ideia, mas ainda estou com problemas, muito provavelmente no processo de escrita e de leitura. Ao escrever vários registos o ficheiro parece manter sempre o seu tamanho. Depois de editar o mesmo noto que apenas o ultimo registo se encontra no mesmo. Ao ler e imprimir o conteudo do ficheiro para um ArrayList, e ao imprimir o tal ArrayList obtenho só o ultimo registo.

Aqui ficam os métodos de escrita e de leitura (cartoesFunc é o meu ArrayList, e CartaoFuncionario a classe dos objectos guardados no ArrayList) :

Código:
    public void escrever() throws Exception{   
        ObjectOutputStream saida = new ObjectOutputStream(new FileOutputStream("CartoesFunc.dat"));   
        saida.writeObject(cartoesFunc);   
        saida.close();
    }

Código:
    public void ler() throws Exception{   
        ObjectInputStream entrada = new ObjectInputStream(new FileInputStream("CartoesFunc.dat"));    
        cartoesFunc = (ArrayList<CartaoFuncionario>)entrada.readObject();
        entrada.close();        
    }
 
Última edição:
Podes forçar o flush da stream de output fazendo:
Código:
saida.flush();
Mas mesmo assim, não creio que vá resolver o problema. Esse código está a gerar warnings devido ao uso de operações inseguras, ou seja, aquele casting do Object para ArrayList<CartaoFuncionario> é inseguro porque isso não é um tipo genérico, é um tipo parametrizado. O melhor será criares uma classe ListaFuncionarios que tem como variável de instância um ArrayList<CartaoFuncionario> e codificas nessa classe os métodos para adicionar, remover, escrever em ficheiro, etc e depois, na classe onde escreves e lês, crias uma instância de ListaFuncionarios à qual adicionas os funcionários, bastando-te depois gravar essa instância na escrita, e ler essa instância na leitura (fazendo o casting para ListaFuncionarios).
 
Reparei agora num promenor que pode fazer falta.
Não tas a fechar o FileOutputStream...
@Baderous:
Podes fazer append sim...eu já utilizei esse método e funcionou perfeitamente.
@Rexobias:
Fui buscar código que escrevi há uns tempos para recordar, e tenho isto feito da seguinte forma:
para escrever:
public void criaUtilizador () throws IOException {
Scanner leitor = new Scanner (System.in);
FileOutputStream fr = new FileOutputStream ("sc.passwords",true);
ObjectOutputStream dos = new ObjectOutputStream (fr);
Utilizador ut = new Utilizador (nome,pass,perms); //não ligues as variaveis, este codigo nao eh o original
dos.writeObject(ut);
dos.flush();
dos.close();
fr.close();

}
Para ler:
public void carregaFicheiro () throws IOException, ClassNotFoundException, EOFException {
System.out.println("UTILIZADORES ACTUAIS:");
while (fr.available() != 0) { //fr é uma FileInputStream global
ObjectInputStream ois = new ObjectInputStream (fr);
Utilizador ut = new Utilizador(ois.readObject()); //tenho um construtor que recebe um object (não uso casts)
}
fr.close();
}
Outro problema que podes ter aí,é a forma como lês até ao fim do ficheiro. Ontem se calhar não te dei o melhor conselho. Desculpa lá.
Como te disseram usa smp, que necessário, o flush antes de fechares as streams.
Como te disse ontem, faz debug nisso e depuração do teu codigo, atraves duns sops dão, smp muito jeito.
É fundamental perceberes se o problema está na escrita ou na leitura do ficheiro.
 
Última edição:
O fluxo do teu código deve ser algo tipo isto:
Código:
package bolos;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

public class SerializeTest {

    public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
        new SerializeTest();

    }

    @SuppressWarnings("unchecked")
    public SerializeTest() throws FileNotFoundException, IOException, ClassNotFoundException {
        
        List<Bolos> lst = new ArrayList<Bolos>();
        ObjectOutputStream oos;
        
        lst.add(new Bolos(1));
        oos = new ObjectOutputStream(new FileOutputStream("bolos"));
        oos.writeObject(lst);
        oos.close();
        lst.add(new Bolos(2));
        oos = new ObjectOutputStream(new FileOutputStream("bolos"));
        oos.writeObject(lst);
        oos.close();
        lst.add(new Bolos(3));
        oos = new ObjectOutputStream(new FileOutputStream("bolos"));
        oos.writeObject(lst);
        oos.close();
        lst.add(new Bolos(4));
        oos = new ObjectOutputStream(new FileOutputStream("bolos"));
        oos.writeObject(lst);
        oos.close();
        lst.add(new Bolos(5));
        oos = new ObjectOutputStream(new FileOutputStream("bolos"));
        oos.writeObject(lst);
        oos.close();
        
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("bolos"));
        lst = (List<Bolos>) ois.readObject();
        for(Bolos bolos: lst) {
            System.out.println(bolos.bolos);
        }
        ois.close();
    }
    
    private static class Bolos implements Serializable {

        private static final long serialVersionUID = -7355538899582009108L;
        int bolos;
        public Bolos(int bolos) {
            this.bolos = bolos;
        }
    }
}
Aqui funciona na boa, vê lá se detectas o porquê de não funcionar no teu.
 
Reparei agora num promenor que pode fazer falta.
Não tas a fechar o FileOutputStream...
Não é necessário porque ele está a usar streams aninhadas:
[SIZE=-2][SIZE=-2][SIZE=-2][SIZE=-2]Similarly, when closing chained streams, you only need to close the outermost stream class because the close() call is automatically trickled through all the chained classes[/SIZE][/SIZE][/SIZE][/SIZE]
http://java.sun.com/developer/technicalArticles/Streams/ProgIOStreams/
@Baderous:
Podes fazer append sim...eu já utilizei esse método e funcionou perfeitamente.
Se não fechares a stream depois de cada escrita, funciona. Mas se fechares, é lançada uma StreamCorruptedException pelas razões que já indiquei.

Quanto ao código do napalm, funciona mas não aparece o warning que eu referi porque ele está a usar o @SuppressWarnings("unchecked"). http://groups.google.com/group/comp.../thread/3c05ddbbe4eaaba4#doc_585daaa682e4e563
 
Última edição:
Back
Topo