Laboratorio 09 — Entrada Salida (I O) en Java - jorgealbertopineda78-del/java GitHub Wiki

Laboratorio 09 — Entrada/Salida (I/O) en Java

Unidad: 9 — Entrada/Salida
Duración estimada: 90 minutos
Nivel: Intermedio-Avanzado


Conexión con laboratorios anteriores

En el Lab08 la bitácora del gremio vivía en un ArrayList en memoria:

private ArrayList<String> bitacora = new ArrayList<>();

Cuando el programa termina, esa información desaparece. En un sistema real necesitamos que los datos persistan: que el roster del gremio, el inventario y el historial de batallas se guarden en archivos y se puedan recargar la próxima vez que inicie el juego.


Objetivo

  • Leer y escribir archivos de texto con BufferedReader y BufferedWriter
  • Verificar existencia de archivos y directorios con File
  • Manejar excepciones de I/O con IOException
  • Persistir y recuperar datos del sistema RPG en archivos .txt

Contexto del problema

El Sistema de Gestión de Gremio ahora necesita guardar su estado en disco:

  1. El roster se guarda en roster.txt al cerrar el programa
  2. El inventario se guarda en inventario.txt
  3. La bitácora de batallas se acumula en bitacora.txt (modo append)
  4. Al iniciar, el sistema recarga roster e inventario desde los archivos

Diagrama UML

┌─────────────────────────────────────────────────────────┐
│                  PersistenciaGremio                     │
│  - CARPETA: String = "datos_gremio"                     │
│                                                         │
│  + guardarRoster(ArrayList<Personaje>)                  │
│    throws IOException                                   │
│  + cargarRoster(): ArrayList<String>                    │
│    throws IOException                                   │
│                                                         │
│  + guardarInventario(HashMap<String, Integer>)          │
│    throws IOException                                   │
│  + cargarInventario(): HashMap<String, Integer>         │
│    throws IOException                                   │
│                                                         │
│  + agregarEntradaBitacora(String entrada)               │
│    throws IOException                                   │
│  + mostrarBitacora()                                    │
│    throws IOException                                   │
│                                                         │
│  - crearCarpetaSiNoExiste()                             │
└─────────────────────────────────────────────────────────┘

Archivos generados:
  datos_gremio/
  ├── roster.txt       ← un personaje por línea
  ├── inventario.txt   ← formato: item,cantidad
  └── bitacora.txt     ← entradas acumuladas (append)

Instrucciones

Parte 1 — Clase PersistenciaGremio y escritura (trabajo en clase)

Crea PersistenciaGremio.java:

import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

public class PersistenciaGremio {

    private static final String CARPETA = "datos_gremio";

    public PersistenciaGremio() {
        crearCarpetaSiNoExiste();
    }

    // Crea la carpeta si todavía no existe
    private void crearCarpetaSiNoExiste() {
        File carpeta = new File(CARPETA);
        if (!carpeta.exists()) {
            carpeta.mkdir();
            System.out.println("[IO] Carpeta '" + CARPETA + "' creada.");
        }
    }

    // ──────────────────────────────────────────────────────
    // GUARDAR ROSTER
    // Formato de línea:  Sylva,10,300
    //                    nombre,nivel,puntosVida
    // ──────────────────────────────────────────────────────
    public void guardarRoster(ArrayList<Personaje> roster) throws IOException {

        File archivo = new File(CARPETA + "/roster.txt");

        BufferedWriter writer = new BufferedWriter(new FileWriter(archivo));

        for (Personaje p : roster) {
            writer.write(p.getNombre() + "," +
                         p.getNivel()  + "," +
                         p.getPuntosVida());
            writer.newLine();   // salto de línea independiente del SO
        }

        writer.close();
        System.out.println("[IO] Roster guardado: " +
                           roster.size() + " personajes.");
    }

    // ──────────────────────────────────────────────────────
    // CARGAR ROSTER — retorna lista de líneas sin parsear
    // (el Main decide qué hacer con cada línea)
    // ──────────────────────────────────────────────────────
    public ArrayList<String> cargarRoster() throws IOException {

        File archivo = new File(CARPETA + "/roster.txt");
        ArrayList<String> lineas = new ArrayList<>();

        if (!archivo.exists()) {
            System.out.println("[IO] roster.txt no encontrado.");
            return lineas;
        }

        BufferedReader reader = new BufferedReader(new FileReader(archivo));
        String linea;

        while ((linea = reader.readLine()) != null) {
            if (!linea.trim().isEmpty()) {
                lineas.add(linea);
            }
        }

        reader.close();
        System.out.println("[IO] Roster cargado: " +
                           lineas.size() + " registros.");
        return lineas;
    }
}

💡 new FileWriter(archivo) por defecto sobrescribe el archivo.
Para agregar al final (modo append) se usa new FileWriter(archivo, true).
Lo veremos en la bitácora (Parte 2).


Parte 2 — Bitácora en modo append (trabajo en casa)

Agrega estos métodos a PersistenciaGremio:

// ──────────────────────────────────────────────────────
// AGREGAR ENTRADA A BITÁCORA (modo append — no borra lo anterior)
// ──────────────────────────────────────────────────────
public void agregarEntradaBitacora(String entrada) throws IOException {

    File archivo = new File(CARPETA + "/bitacora.txt");

    // true = modo append → agrega al final del archivo
    BufferedWriter writer = new BufferedWriter(
                                new FileWriter(archivo, true));
    writer.write(entrada);
    writer.newLine();
    writer.close();
}

// ──────────────────────────────────────────────────────
// LEER Y MOSTRAR BITÁCORA COMPLETA
// ──────────────────────────────────────────────────────
public void mostrarBitacora() throws IOException {

    File archivo = new File(CARPETA + "/bitacora.txt");

    if (!archivo.exists()) {
        System.out.println("[IO] La bitácora está vacía.");
        return;
    }

    System.out.println("\n=== Bitácora de Batallas ===");
    BufferedReader reader = new BufferedReader(new FileReader(archivo));
    String linea;
    int numero = 1;

    while ((linea = reader.readLine()) != null) {
        System.out.println(numero++ + ". " + linea);
    }

    reader.close();
}

💡 new FileWriter(archivo, true) — el segundo parámetro true
activa el modo append. Cada llamada agrega al final sin borrar
lo que ya había. Perfecto para logs y bitácoras.


Parte 3 — Inventario en archivo (trabajo en casa)

Agrega a PersistenciaGremio los métodos del inventario:

guardarInventario(HashMap<String, Integer> inventario)

  • Abre datos_gremio/inventario.txt para escritura (sobrescribe)
  • Por cada entrada del mapa escribe una línea con formato: item,cantidad
  • Ejemplo: Poción de vida,8
  • Cierra el writer e imprime cuántos items se guardaron

cargarInventario(): HashMap<String, Integer>

  • Si el archivo no existe, retorna un HashMap vacío
  • Lee línea por línea y usa split(",") para separar nombre y cantidad
  • Integer.parseInt(partes[1]) convierte el texto a número
  • Retorna el HashMap reconstruido

Pista para cargarInventario():

String[] partes = linea.split(",");
String item     = partes[0];
int cantidad    = Integer.parseInt(partes[1]);
inventario.put(item, cantidad);

Parte 4 — Clase principal Main (trabajo casa)

Demuestra el sistema en 5 bloques. Rodea todo con try-catch:

try {
    // todos los bloques aquí
} catch (IOException e) {
    System.out.println("Error de archivo: " + e.getMessage());
}

Bloque 1 — Crear personajes y guardar roster:

PersistenciaGremio persistencia = new PersistenciaGremio();

ArrayList<Personaje> roster = new ArrayList<>();
roster.add(new Druida("Sylva", 10, 300, 100));
roster.add(new Arquero("Legolas", 6, 150, "Arco", 20, 95));
roster.add(new Guerrero("Thorin", 9, 400, "Hacha", 80));

persistencia.guardarRoster(roster);

Bloque 2 — Cargar roster desde archivo:

ArrayList<String> lineasRoster = persistencia.cargarRoster();
System.out.println("\n=== Roster cargado desde archivo ===");
for (String linea : lineasRoster) {
    String[] partes = linea.split(",");
    System.out.println("Nombre: " + partes[0] +
                       " | Nivel: " + partes[1] +
                       " | Vida: " + partes[2]);
}

Bloque 3 — Guardar y cargar inventario:

HashMap<String, Integer> inventario = new HashMap<>();
inventario.put("Poción de vida", 8);
inventario.put("Flecha élfica", 30);
inventario.put("Pergamino de fuego", 3);

persistencia.guardarInventario(inventario);

HashMap<String, Integer> inventarioCargado =
    persistencia.cargarInventario();

System.out.println("\n=== Inventario cargado desde archivo ===");
for (Map.Entry<String, Integer> e : inventarioCargado.entrySet()) {
    System.out.println(e.getKey() + " → " + e.getValue());
}

Bloque 4 — Escribir entradas en la bitácora:

persistencia.agregarEntradaBitacora("Sylva atacó a Malachar (daño: 240)");
persistencia.agregarEntradaBitacora("Legolas sin flechas — no pudo atacar");
persistencia.agregarEntradaBitacora("Thorin venció a Dragón de Hielo");

persistencia.mostrarBitacora();

Bloque 5 — Verificar los archivos creados:

File carpeta = new File("datos_gremio");
System.out.println("\n=== Archivos en datos_gremio/ ===");
for (File f : carpeta.listFiles()) {
    System.out.println(f.getName() +
                       " (" + f.length() + " bytes)");
}

Ejemplo de salida esperada

[IO] Carpeta 'datos_gremio' creada.
[IO] Roster guardado: 3 personajes.

[IO] Roster cargado: 3 registros.

=== Roster cargado desde archivo ===
Nombre: Sylva   | Nivel: 10 | Vida: 300
Nombre: Legolas | Nivel: 6  | Vida: 150
Nombre: Thorin  | Nivel: 9  | Vida: 400

[IO] Inventario guardado: 3 items.
[IO] Inventario cargado: 3 items.

=== Inventario cargado desde archivo ===
Poción de vida      → 8
Flecha élfica       → 30
Pergamino de fuego  → 3

=== Bitácora de Batallas ===
1. Sylva atacó a Malachar (daño: 240)
2. Legolas sin flechas — no pudo atacar
3. Thorin venció a Dragón de Hielo

=== Archivos en datos_gremio/ ===
roster.txt     (44 bytes)
inventario.txt (58 bytes)
bitacora.txt   (97 bytes)

Criterios de evaluación

Criterio Puntos
crearCarpetaSiNoExiste() usa File correctamente 10
guardarRoster() escribe con BufferedWriter en formato correcto 15
cargarRoster() lee con BufferedReader y maneja archivo inexistente 15
guardarInventario() y cargarInventario() con formato clave,valor 20
agregarEntradaBitacora() usa modo append (FileWriter(f, true)) 15
mostrarBitacora() lee e imprime numerado 10
Main demuestra los 5 bloques con try-catch (IOException) 15
Total 100

Preguntas de reflexión

Responde en respuestas.docx:

  1. ¿Cuál es la diferencia entre FileWriter(archivo) y FileWriter(archivo, true)? ¿Qué pasaría con la bitácora si usaras el primero?
  2. ¿Por qué es importante verificar si un archivo existe antes de leerlo? ¿Qué excepción lanzaría Java si intentas leer un archivo que no existe sin verificar?
  3. ¿Qué ventaja tiene BufferedReader sobre leer el archivo directamente con FileReader? ¿Qué hace el "buffer" en este contexto?
  4. ¿Definir Enums en java, para que sirven investigar y listar en documento las clases similares a BufferedReader y BufferedWriter y FileReader y FileWriter?

Entregable

Sube al repositorio en la carpeta lab09_io:

lab09_io/
├── Personaje.java
├── Guerrero.java
├── Arquero.java
├── Druida.java
├── Nigromante.java
├── (excepciones del Lab07)
├── PersistenciaGremio.java
├── Main.java
└── datos_gremio/        ← se genera al ejecutar el programa
    ├── roster.txt
    ├── inventario.txt
    └── bitacora.txt

Laboratorio preparado para el curso de Programación Orientada a Objetos — Ago-Dic 2025

⚠️ **GitHub.com Fallback** ⚠️