Estruturas de Dados - Arquivos
Objetivos da seção
- Aprender a acessar depósitos de dados persistentes chamados
"arquivos"
- Aprender a manipular arquivos de várias formas, incluindo
para a serialização de objetos
Noção de Stream
- Usaremos um conceito muito genérico para acessar arquivos: o Stream
(fluxo)
- Um stream é uma sequência ordenada de dados com uma fonte e um
destino

- O que é importante sobre o stream é que o acesso aos dados é sequencial
- Não é como acessar objetos na memória
- Neste caso, acessamos qualquer objeto diretamente na memória usando a referência
- Se houver 1000 pedaços de informação num stream, acessarei cada pedaço, mas
sequencialmente, um de cada vez: "Me dê o próximo ... me dê o próximo ..."
- A noção de stream é importante pois permite acessar vários tipos de "depósitos
de informação"
- Arquivos
- Conexões de rede
- Dispositivos (teclado, tela, impressora, fita de backup, câmera de vídeo, ...)
- Um stream pode ser caracterizado em várias dimensões
- Direção
- Streams de entrada
- Em Java, possuem a palavra "Input" na classe (ex. InputStream)
- O destino da informação é seu programa
- Isto é, seu programa lê informação do stream
- Streams de saída
- Em Java, possuem a palavra "Output" na classe (ex. OutputStream)
- A fonte da informação é seu programa
- Isto é, seu programa grava informação no stream
- Streams bidirecionais
- Em Java, possuem a palavra "RandomAccess" na classe (ex. RandomAccessFile)
- Seu programa lê e grava informação do/no stream
- Tipo de informação lida
- Streams de bytes (caracterizados pela palavra Stream)
- Leitura/gravação de bytes crus (nenhuma palavra adicional)
- Leitura/gravação de tipos nativos (palavra Data)
- Leitura/gravação de objetos inteiros (palavra Object)
- Streams de caracteres Unicode (caracterizados pela palavra Reader ou Writer)
- Fontes e destinos (de onde vem/para onde vai a
informação)
- Arquivo (palavra File)
- Para ter persistência de informação depois que seu
programa termina
- Array de bytes (palavra ByteArray)
- Array de caracteres (palavra CharArray)
- String (palavra String)
- Outro processo (palavra Piped)
- Processamento intermediário realizado na informação
- Bufferização (palavra Buffered)
- Na leitura, como o disco é lento, é melhor trazer muita informação de uma vez dentro
de um lugar da memória (um array) que chamamos buffer
- O acesso é feito diretamente à informação no buffer
- Quando o buffer esvazia, é enchido novamente com uma única operação de leitura do
disco
- Idém para a saída
- Compressão (palavra ZIP)
- Para ler arquivo zip, por exemplo
- Para guardar o número da linha enquanto lê informação (palavra LineNumber)
- Para quebrar a informação em pedaços ou "tokens" (palavra Tokenizer)
- É por causa dessa grande variedade de opções que o sistema de entrada/saída do Java,
baseado em streams, é meio complexo (mas não complicado)
Montagem de Sequências de Streams
- Quando queremos usar um stream, perguntamos primeiro:
- Em que direção o stream deve funcionar?
- Que tipo de informação será tratada?
- Qual é a fonte (na leitura) ou destino (na gravação) da informação?
- Qual processamento(s) intermediário(s) deve(m) ser feito(s) na informação?
- A beleza do sistema de E/S Java (e sua fonte de complexidade) é que podemos montar um
stream juntando vários pedaços, como num jogo de Lego
- Com algumas exceções, podemos tratar as 4 dimensões de forma ortogonal,
isto é tomando decisões individuais para cada dimensão e juntando a solução para cada
dimensão para fazer o stream final
- A montagem do stream é feita juntando objetos de várias classes através de um esquema
de "wrapping"
- Exemplo: Ler caracteres de um arquivo com bufferização
- Direção: Ler
- Tipo: Caracteres
- Fonte: Arquivo
- Processamento: Bufferização
BufferedReader in = new BufferedReader(
new FileReader("dados.dat")
);
// manipula o stream "in" como veremos adiante
- O stream que montamos é o seguinte:

- A tabela abaixo dá um resumo da classes mais importantes do Java para tratamento de
streams
Classe |
Direção |
Tipo de Informação |
Fonte/Destino |
Processamento |
Lê |
Grava |
Bytes crus |
Tipos nativos |
Objetos |
Carac-
teres |
Arquivo |
Array de bytes |
Outro processo |
Array de carac-
teres |
String |
Bufferi-
zação |
Compressão |
Número
de
Linha |
Tokeniza |
FileOutputStream |
|
X |
X |
X |
X |
|
X |
|
|
|
|
|
|
|
|
ByteArrayOutputStream |
|
X |
X |
X |
X |
|
|
X |
|
|
|
|
|
|
|
PipedOutputStream |
|
X |
X |
X |
X |
|
|
|
X |
|
|
|
|
|
|
BufferedOutputStream |
|
X |
X |
X |
X |
|
|
|
|
|
|
X |
|
|
|
ObjectOutputStream |
|
X |
|
|
X |
|
|
|
|
|
|
|
|
|
|
DataOutputStream |
|
X |
|
X |
|
|
|
|
|
|
|
|
|
|
|
ZIPOutputStream |
|
X |
X |
X |
X |
|
|
|
|
|
|
|
X |
|
|
GZIPOutputStream |
|
X |
X |
X |
X |
|
|
|
|
|
|
|
X |
|
|
FileInputStream |
X |
|
X |
X |
X |
|
X |
|
|
|
|
|
|
|
|
ByteArrayInputStream |
X |
|
X |
X |
X |
|
|
X |
|
|
|
|
|
|
|
PipedInputStream |
X |
|
X |
X |
X |
|
|
|
X |
|
|
|
|
|
|
BufferedInputStream |
X |
|
X |
X |
X |
|
|
|
|
|
|
X |
|
|
|
ObjectInputStream |
X |
|
|
|
X |
|
|
|
|
|
|
|
|
|
|
DataInputStream |
X |
|
|
X |
|
|
|
|
|
|
|
|
|
|
|
ZIPInputStream |
X |
|
X |
X |
X |
|
|
|
|
|
|
|
X |
|
|
GZIPInputStream |
X |
|
X |
X |
X |
|
|
|
|
|
|
|
X |
|
|
RandomAccessFile |
X |
X |
X |
|
|
|
X |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
FileWriter |
|
X |
|
|
|
X |
X |
|
|
|
|
|
|
|
|
PipedWriter |
|
X |
|
|
|
X |
|
|
X |
|
|
|
|
|
|
CharArrayWriter |
|
X |
|
|
|
X |
|
|
|
X |
|
|
|
|
|
StringWriter |
|
X |
|
|
|
X |
|
|
|
|
X |
|
|
|
|
BufferedWriter |
|
X |
|
|
|
X |
|
|
|
|
|
X |
|
|
|
PrintWriter
(imprime formatado) |
|
X |
|
|
|
X |
|
|
|
|
|
|
|
|
|
FileReader |
X |
|
|
|
|
X |
X |
|
|
|
|
|
|
|
|
PipedReader |
X |
|
|
|
|
X |
|
|
X |
|
|
|
|
|
|
CharArrayReader |
X |
|
|
|
|
X |
|
|
|
X |
|
|
|
|
|
StringReader |
X |
|
|
|
|
X |
|
|
|
|
X |
|
|
|
|
BufferedReader |
X |
|
|
|
|
X |
|
|
|
|
|
X |
|
|
|
LineNumberReader |
X |
|
|
|
|
X |
|
|
|
|
|
|
|
X |
|
StreamTokenizer |
X |
|
|
|
|
X |
|
|
|
|
|
|
|
|
X |
- Exemplo:
- Queremos ler caracteres de um arquivo com bufferização (exemplo já visto)
BufferedReader in = new BufferedReader(
new FileReader("dados.dat")
);
// manipula o stream "in" como veremos adiante
- Exemplo:
- Queremos gravar dados em arquivo zipado
BufferedOutputStream out = new BufferedOutputStream(
new ZipOutputStream(
new FileOutputStream("dados.zip")
)
);
// manipula o stream "out" como veremos adiante
- Exemplo:
- Queremos gravar objetos num arquivo
ObjectOutputStream out = new ObjectOutputStream(
new FileOutputStream("agencia.txt"))
);
// manipula o stream "out" como veremos adiante
Acesso a Arquivo: Um primeiro Exemplo Simples
- Problema a resolver:
- Escrever um programa que receba como parâmetros o nome de um ou mais arquivos e que
imprima na saída o número de caracteres presentes em cada um.
import java.io.*;
public class CharCount {
public static void main(String[] args) {
if(args.length < 1) {
System.err.println("Sintaxe: CharCount arq [arq ...]");
System.exit(1);
}
for(int i = 0; i < args.length; i++) {
try {
System.out.println(args[i] + ": " + contaCaracteres(args[i]));
} catch( IOException e ) {
System.err.println(args[i] + ": Erro: " + e.getMessage());
}
}
}
static int contaCaracteres(String nomeArquivo) throws IOException {
BufferedReader in = new BufferedReader(
new FileReader(nomeArquivo)
);
int numCarac = 0;
try {
while(in.read() >= 0) {
numCarac++;
}
} finally {
in.close(); // isso é feito mesma que haja exceção
}
return numCarac;
}
}
- Teste o programa com arquivos que não existem também
Alguns Detalhes sobre o Conceito de Arquivo
- Arquivos são usados devido à sua persistência
- Isto é, o conteúdo não desaparece quando o programa termina
- Isso não é o caso para objetos manipulados pelo programa
- Outro lugar para guardar informação persistente é um Banco de Dados
- É uma "camada" em cima de arquivos para dar um acesso mais rápido quando há
uma quantidade muito grande de informação
- O acesso sequencial proporcionado pelo arquivo seria muito lento
- O Banco de Dados fornece outros serviços, além de rapidez, tais como:
- Robustez
- Agrupamento de operações (transações)
- Controle de concorrência (quando vários usuários acessam os dados ao mesmo tempo)
- etc.
- Ao usar "new FileReader(nomeArquivo)", o sistema operacional "abre"
o arquivo
- A "abertura" do arquivo tem vários propósitos:
- Verificar se o arquivo existe
- Verificar se você tem permissão de usar o arquivo da maneira que está pedindo (ler ou
gravar, por exemplo)
- Preparar o sistema operacional para receber pedidos de acesso ao arquivo feitos pelo
programa com a maior rapidez possível
- Para atender a isso, o sistema operacional guarda informação do arquivo na memória
para ter acesso mais rápido
- No final, é necessário avisar o sistema operacional que o arquivo não será mais
usado, por enquanto
- Isso é feito com a operação de fechamento de arquivo (close)
- No programa CharCount, se não fechassemos o arquivo, o programa não funcionaria com
milhares de argumentos
- Porque o sistema só pode abrir um certo número limitado de arquivos, devido a
limitações de recursos internos do sistema operacional
- Arquivos são frequentemente organizados por registro
- Exemplo: se um arquivo contiver o cadastro de alunos, um registro conteria o cadastro de
um único aluno
- O registro consiste de campos (nome, matrícula, data de
mascimento, ...)
- O registro pode ser de tamanho fixo (tamanho fixo para cada campo)
- O registro pode ser de tamanho variável (usando um separador especial entre campos)
- Às vezes, o arquivo pode ser organizado de outras formas e não conter apenas uma
sequência de registros do mesmo tipo
- Exemplos: arquivo HTML, arquivo XML, etc., etc.
Alguns Métodos Disponíveis para Manipular Streams
- As classes que definem streams em Java possuem vários métodos de acesso
- Já usamos o método read() do BufferedReader, acima
- Resumimos aqui alguns dos métodos mais importantes de algumas classes
- Veja a documentação da API Java para ver a lista completa de métodos
- InputStream
- Todos os InputStreams podem:
- Ler um byte: read()
- Ler vários bytes: read(byte[] b)
- Observe que a leitura de um byte retorna int
- Motivo: queremos retornar qualquer valor de byte e ainda ter o valor -1 para indicar o
fim do stream
- O ObjectInputStream ainda pode:
- Ler um Objeto qualquer: readObject()
- O DataInputStream ainda pode:
- Ler dados de tipos básicos: readBoolean(), readDouble(), readInt(), ...
- OutputStream
- Todos os OutputStreams podem:
- Gravar um byte: write(int b)
- Gravar vários bytes: write(byte[] b)
- O ObjectOutputStream ainda pode:
- Gravar um Objeto qualquer: writeObject(Object obj)
- O DataInputStream ainda pode:
- Gravar dados de tipos básicos: writeBoolean(), writeDouble(), writeInt(), ...
- FileReader
- Todos os FileReaders podem:
- Ler um caractere: read()
- Retorna -1 no fim de arquivo
- O BufferedReader ainda pode:
- Ler uma linha inteira: readLine()
- O LineNumberReader ainda pode:
- Retornar o número da linha: getLineNumber()
- FileWriter
- Todos os FileWriters podem:
- Gravar um caractere: write(int c)
- O BufferedWriter ainda pode:
- Gravar um caractere de nova-linha (separador de linhas): newLine()
- O PrintWriter ainda pode:
- Gravar dados de tipos básicos de forma formatada: print(int), print(double), ...
Serialização de Objetos
- Um objeto possui atributos que definem seu estado
- Objetos não sobrevivem à morte do programa
- O que fazer se quisermos gravar uma cópia dos objetos e recuperar a cópia depois?
- Uma forma é de gravar os objetos em arquivo, se forma serial
- A serialização significa gravar, sequencialmente e
byte-por-byte, os atributos de um objeto
- Utilidade: colocar objetos num lugar que só pode ser acessado sequencialmente
- Exemplo: mandar/ler objetos numa conexão de rede (acessado como um stream que chamamos
um socket)
- Exemplo: guardar/ler objetos em arquivos
- Exemplo: vejamos a classe Agencia (Agencia.java)
package p1.aplic.banco;
import java.util.*;
import java.io.*;
import p1.aplic.geral.*;
/**
* Classe de agência bancária simples.
* Nesta versão, há uma única agência. A agência tem uma conta "caixa" para
* depósitos e saques. A agência pode ser "persistente". Isto significa que
* tudo que ocorreu de movimentação de contas pode ser gravado em disco
* para uso posterior ao fechar a agência.
*
* @author Jacques Philippe Sauvé, jacques@dsc.ufpb.br
* @version 1.1
* <br>
* Copyright (C) 1999 Universidade Federal da Paraíba.
*/
public class Agencia {
protected static boolean aberto = false;
protected static Conta caixa = null;
protected static Movimento movimento = null;
protected static HashMap titulares = null;
protected static HashMap contas = null;
// várias coisas foram retiradas
// ...
/**
* Fechamento do caixa e gravação dos dados em arquivo.
* Aborta o programa com mensagem se houver problemas.
*/
/* deveria usar exceções se der pau aqui, mas vamos poupar os alunos principiantes (usamos isso em programas iniciais) */
public static void fecharCaixa() {
// usamos serializacao de objetos para manter os tipos dos objetos
// se nao fizesse isso, teria problemas em distinguir tipos de contas, de pessoas, ...
ObjectOutputStream out = null;
try {
try {
out = new ObjectOutputStream(new FileOutputStream(getNomeArquivo()));
} catch( FileNotFoundException e ) {
System.err.println("Nao pode criar " + getNomeArquivo());
System.exit(1);
}
List tudo = new ArrayList(); // junta tudo num unico objeto
// para nao perder as ligacoes entre objetos
tudo.add(titulares);
tudo.add(contas);
tudo.add(movimento);
out.writeObject(tudo);
out.close();
} catch(IOException e) {
System.err.println(e);
System.exit(1);
}
}
/**
* Abertura do caixa (da agencia) e leitura dos dados persistentes gravados em arquivo.
* Aborta o programa com mensagem se houver problemas.
*/
public static void abrirCaixa() {
if(aberto) {
return;
}
titulares = new HashMap();
contas = new HashMap();
movimento = new Movimento();
aberto = true; // elimina recursao ao criar a conta caixa
ObjectInputStream in = null;
try {
try {
in = new ObjectInputStream(new FileInputStream(getNomeArquivo()));
} catch( FileNotFoundException e ) {
// nao achar o arquivo significa que estamos começando do zero
caixa = new ContaCaixa();
addConta(caixa);
return;
}
List tudo = new ArrayList();
tudo = (List)in.readObject();
titulares = (Map)tudo.get(0);
contas = (HashMap)tudo.get(1);
movimento = (Movimento)tudo.get(2);
caixa = localizarConta(0);
in.close();
} catch(Exception e) {
System.err.println(e);
System.exit(1);
}
}
protected static String getNomeArquivo() {
return "agencia.dat";
}
}
- O exemplo mostra como usar um ObjectInputStream para gravar objetos num arquivo
- Objetos que são assim gravados devem implementar a interface Serializable
- Há mais coisas a dizer sobre o assunto de serialização mas não o faremos aqui
- É importante observar que, ao gravar um objeto (o ArrayList "tudo"), o objeto
e quaisquer outros objetos para os quais ele aponta são serializados e gravados no stream
- Se houver conexões entre objetos, cada objeto será gravado um única vez
- Exemplo: titulares e contas podem acabar apontando para a mesma pessoa "João"
- O objeto correspondendo a João será gravado uma única vez
- Este é o motivo de juntar tudo que queremos e gravar um único objetão
Exemplo: Reader e Writer
- Vamos supor que queremos gravar a informação da agência em registros textuais, um por
linha
- Por exemplo, suponhamos a seguinte movimentação:
- Depósito de 1234.56 na conta 1
- Transferência de 98.76 da conta 1 para a conta 2
- O arquivo seguinte poderia representar a situação:
TITULAR#Ana#12345678901
TITULAR#Joao#30914060506
CONTA#2#12345678901#25/08/2001 18:40#98.76
INICIO_MOVIMENTO
TRANSACAO#1#2#98.76#transferencia para conta 2
FIM_MOVIMENTO
CONTA#1#30914060506#25/08/2001 18:40#1135.8
INICIO_MOVIMENTO
TRANSACAO#25/08/2001 18:40#0#1#1234.56#deposito
TRANSACAO#25/08/2001 18:40#1#2#98.76#transferencia para conta 2
FIM_MOVIMENTO
CONTA#0#0#25/08/2001 18:40#-1234.56
INICIO_MOVIMENTO
TRANSACAO#25/08/2001 18:40#0#1#1234.56#deposito
FIM_MOVIMENTO
INICIO_MOVIMENTO
TRANSACAO#25/08/2001 18:40#0#1#1234.56#deposito
TRANSACAO#25/08/2001 18:40#1#2#98.76#transferencia para conta 2
FIM_MOVIMENTO
- Observe que há vários registros, um por linha
- O separador de campos é o caractere "#"
- O tipo do registro é informado com o primeiro campo
- As últimas três linhas se referem à movimentação da agência como um todo
- A informação no arquivo é suficiente para recompor os objetos do programa quando este
inicia?
- Não!
- Não sabemos se a Conta é da classe Conta ou ContaSimples ou ContaCaixa e não
poderíamos reconstituir a informação
- Uma outra alternativa seria introduzir um campo dando a classe do objeto apropriado
- Porém, deve estar claro que a gravação de objetos com serialização é muito mais
simples
TITULAR#Ana#12345678901
TITULAR#Joao#30914060506
CONTA#p1.aplic.banco.ContaSimples#2#12345678901#25/08/2001 18:40#98.76
INICIO_MOVIMENTO
TRANSACAO#25/08/2001 18:40#1#2#98.76#transferencia para conta 2
FIM_MOVIMENTO
CONTA#p1.aplic.banco.ContaSimples#1#30914060506#25/08/2001 18:40#1135.8
INICIO_MOVIMENTO
TRANSACAO#25/08/2001 18:40#0#1#1234.56#deposito
TRANSACAO#25/08/2001 18:40#1#2#98.76#transferencia para conta 2
FIM_MOVIMENTO
CONTA#p1.aplic.banco.ContaCaixa#0#0#25/08/2001 18:40#-1234.56
INICIO_MOVIMENTO
TRANSACAO#25/08/2001 18:40#0#1#1234.56#deposito
FIM_MOVIMENTO
INICIO_MOVIMENTO
TRANSACAO#25/08/2001 18:40#0#1#1234.56#deposito
TRANSACAO#25/08/2001 18:40#1#2#98.76#transferencia para conta 2
FIM_MOVIMENTO
- Vamos tentar fazer uma nova classe Agencia2 que é igual a Agencia mas que grava os
dados neste formato
- O mais óbvio seria fazer Agencia2 herdar de Agencia e fazer override dos seguintes
métodos
- abrirCaixa
- fecharCaixa
- getNomeArquivo (para abrir agencia.txt, por exemplo)
- Mas isso não funciona!
- O problema é que o resto do código chama métodos estáticos Agencia.xxxx()
- Esses métodos não podem sofrer override!
- Não há polimorfismo possível porque usamos métodos estáticos!
- Isso mostra como métodos estáticos são ruins
- Você verão soluções na disciplina Métodos Avançados de Programação
- A solução chama-se um Singleton
- Mesmo assim, vamos mostrar como seria pelo menos o esboço da solução
- Vamos mostrar a leitura e gravação da informação de titulares
- Cuidado! O código abaixo não funciona (falta de polimorfismo)
public class Agencia2 extends Agencia {
/**
* Separador de campos no arquivo de texto
*/
private final static String SEPARADOR = "#";
/**
* Labels de registros do arquivo de persistencia
*/
private final static String LABEL_TITULAR = "TITULAR";
private final static String LABEL_CONTA = "CONTA";
private final static String LABEL_INICIO_MOVIMENTO = "INICIO_MOVIMENTO";
private final static String LABEL_FIM_MOVIMENTO = "FIM_MOVIMENTO";
private final static String LABEL_TRANSACAO = "TRANSACAO";
/**
* Fechamento do caixa e gravação dos dados em arquivo.
* Aborta o programa com mensagem se houver problemas.
*/
public static void fecharCaixa() {
PrintWriter out = null;
try {
out = new PrintWriter(
new BufferedWriter(
new FileWriter(getNomeArquivo())
)
);
gravaTitulares(out);
// ... grava o resto aqui
out.close();
} catch(FileNotFoundException e) {
fatal("Nao pode criar arquivo " + getNomeArquivo() + ": " + e.getMessage());
} catch(IOException e) {
fatal(e.toString());
}
}
private static void gravaTitulares(PrintWriter out) {
Iterator it;
it = titulares.values().iterator();
while(it.hasNext()) {
Pessoa pessoa = (Pessoa)it.next();
out.println(LABEL_TITULAR + SEPARADOR +
pessoa.getClass().getName() + SEPARADOR +
pessoa.getNome() + SEPARADOR +
pessoa.getCPF());
}
}
/**
* Abertura do caixa (da agencia) e leitura dos dados persistentes gravados em arquivo.
* Aborta o programa com mensagem se houver problemas.
*/
public static void abrirCaixa() {
if(aberto) {
return;
}
titulares = new HashMap();
contas = new HashMap();
movimento = new Movimento();
aberto = true; // elimina recursao ao criar a conta caixa
BufferedReader in = null;
try {
in = new BufferedReader(
new FileReader(getNomeArquivo())
);
leInformação(in);
caixa = localizarConta(0);
in.close();
} catch(FileNotFoundException e) {
// nao achar o arquivo significa que estamos começando do zero
caixa = new ContaCaixa();
addConta(caixa);
return;
} catch(Exception e) {
fatal(e.toString());
}
}
private static void leInformação(BufferedReader in) throws Exception {
String linha;
while((linha = in.readLine()) != null) {
StringTokenizer st = new StringTokenizer(linha, SEPARADOR);
if(!st.hasMoreTokens()) {
throw new Exception("Erro de sintaxe no arquivo");
}
String label = st.nextToken();
if(label.equals(LABEL_TITULAR)) {
addTitular(leTitular(st));
} else if(label.equals(LABEL_CONTA)) {
// ...
} else if(label.equals(LABEL_INICIO_MOVIMENTO)) {
// ...
} else {
throw new Exception("Erro de sintaxe no arquivo");
}
}
}
private static Pessoa leTitular(StringTokenizer st) throws Exception {
String nome = null;
String cpf = null;
try {
nome = st.nextToken();
cpf = st.nextToken();
} catch(NoSuchElementException e) {
throw new Exception("Erro de sintaxe no arquivo");
}
return new Pessoa(nome, cpf);
}
protected static String getNomeArquivo() {
return "agencia.txt";
}
/**
* Erro fatal: imprime erro e cai fora
*/
private static void fatal(String mensagem) {
System.err.println(mensagem);
System.exit(1);
}
}
- A (quase) solução acima também tem muito acoplamento com as classes do pacote
p1.aplic.banco
- Uma alternativa: deixa cada classe (Pessoa, Conta, ...) saber se colocar em arquivo
- Desvantagem: tem que mexer em muitas classes
- A melhor solução:
- Para pouca informação: serialização
- Para muita informação: usar um banco de dados
Conceito de Entrada/Saída Padrão e Redirecionamento
Palavras Finais
- O tratamento acima não esgota o assunto de arquivos
- O aluno deve completar seu entendimento de arquivos através da leitura de livros sobre
Java
ed-1 programa próxima