Lectura/Escritura de Bytes a y Desde un Archivo Utilizando Sólo de Java.IO

0

Pregunta

¿Cómo podemos escribir una matriz de bytes en un archivo (y la lectura de ese archivo) en Java?

Sí, todos sabemos que ya hay un montón de preguntas como esa, pero tienen muy complicado y subjetivo debido al hecho de que hay muchas maneras de lograr esta tarea.

Así que vamos a reducir el alcance de la pregunta:

Dominio:

  • Android / Java

Lo que queremos:

  • Rápido (como es posible)
  • Libre de bugs (en una rígida forma meticulosa)

Lo que no estamos haciendo:

  • Las bibliotecas de terceros
  • Las bibliotecas que requieren Android API más tarde de las 23 (Malvavisco)

(Por lo tanto, que las normas de Apache Commons, Google Guayaba, Java.nio, y nos deja con buen ol' Java.io)

Lo que necesitamos:

  • Matriz de bytes es siempre exactamente el mismo (contenido y tamaño) después de pasar a través de la escritura, luego del proceso de lectura
  • Escribir método sólo requiere dos argumentos: el Archivo, y byte[] datos
  • Método Read devuelve un byte[] y sólo se requiere de un argumento: Archivo archivo

En mi caso particular, estos métodos son privados (no de una biblioteca) y NO se hace responsable de los siguientes, (pero si usted desea crear una solución universal que se aplica a un público más amplio, ir a por ello):

  • Hilo de seguridad (archivo no pueda ser accedida por más de un proceso a la vez)
  • Archivo null
  • Archivo que apunta a inexistente ubicación
  • La falta de permisos en la ubicación del archivo
  • Matriz de bytes a ser demasiado grande
  • Matriz de bytes null
  • Tratar con cualquier "índice", "longitud" o "anexar" argumentos/capacidades

Así que... estamos en la búsqueda de la definitiva prueba de balas código que la gente en el futuro puede suponer que es seguro de usar, ya que su respuesta tiene la gran cantidad de votos y no hay comentarios que dicen, "Que puede fallar si..."

Esto es lo que tengo hasta ahora:

Escribir Bytes En El Archivo:

private void writeBytesToFile(final File file, final byte[] data) {
        try {
            FileOutputStream fos = new FileOutputStream(file);
            fos.write(data);
            fos.close();
        } catch (Exception e) {
            Log.i("XXX", "BUG: " + e);
        }
    }

Leer Bytes Desde El Archivo:

private byte[] readBytesFromFile(final File file) {
        RandomAccessFile raf;
        byte[] bytesToReturn = new byte[(int) file.length()];
        try {
            raf = new RandomAccessFile(file, "r");
            raf.readFully(bytesToReturn);
        } catch (Exception e) {
            Log.i("XXX", "BUG: " + e);
        }
        return bytesToReturn;
}

Por lo que he leído, las posibles Excepciones son:

FileNotFoundException : ¿Estoy en lo cierto que esto no debería ocurrir mientras la ruta de acceso del archivo que está siendo suministrada se derivan de la utilización de Android propias herramientas internas y/o si la aplicación ha sido probado adecuadamente?

IOException : realmente no se sabe lo que puede causar esta... pero estoy asumiendo que no hay ninguna manera alrededor de ella si lo hace.

Así que con eso en mente... estos métodos pueden ser mejorados o reemplazados, y si es así, ¿con qué?

android arrays file java
2021-11-23 02:58:43
2

Mejor respuesta

6

Parece que estos van a ser núcleo de la utilidad/la biblioteca de los métodos que se deben ejecutar en Android API de 23 o más tarde.

Sobre la biblioteca de métodos, creo que es mejor no hacer suposiciones sobre cómo las aplicaciones se utilizan estos métodos. En algunos casos las aplicaciones que desee recibir comprobado IOExceptions (debido a que los datos de un archivo debe existir para que la aplicación funcione), en otros casos, las aplicaciones pueden incluso no importa si no se dispone de datos (como los datos de un archivo es de sólo caché que también está disponible a partir de una fuente primaria).

Cuando se trata de operaciones de e/S, nunca hay una garantía de que las operaciones de éxito (por ejemplo, el usuario de dejar caer el teléfono en el baño). La biblioteca debe reflejar eso, y dar a la aplicación de una elección sobre cómo manejar los errores.

Para optimizar el rendimiento de e/S siempre ha de asumir la "happy path" y la captura de los errores de averiguar lo que salió mal. Esto es contra intuitivo a la programación normal, pero esencial en el trato con el almacenamiento de I/O. Por ejemplo, comprobando si existe un archivo antes de la lectura de un archivo puede hacer que su aplicación dos veces más lento - todos estos tipos de I/O acciones suman rápido a lento de la aplicación hacia abajo. Sólo suponga que el archivo existe y si obtiene un error, sólo, a continuación, comprobar si existe el archivo.

Así que, dado que las ideas, las principales funciones que podría parecerse a:

public static void writeFile(File f, byte[] data) throws FileNotFoundException, IOException {
    try (FileOutputStream out = new FileOutputStream(f)) {
        out.write(data);
    }
}

public static int readFile(File f, byte[] data) throws FileNotFoundException, IOException {
    try (FileInputStream in = new FileInputStream(f)) {
        return in.read(data); 
    }
}

Notas sobre la aplicación:

  • Los métodos también pueden lanzar en tiempo de ejecución-excepciones como la de NullPointerExceptions - estos métodos son nunca va a ser "libre de errores".
  • No creo que el búfer se necesitaba/quería que en los métodos anteriores, puesto que sólo un nativo de la llamada se hace (ver también aquí).
  • La aplicación ahora también tiene la opción de leer solo el principio de un archivo.

Para hacer más fácil para que una aplicación para leer un archivo, un método adicional puede ser añadido. Pero tenga en cuenta que es a la biblioteca para detectar los errores y reportarlos a la aplicación desde la propia aplicación no puede detectar esos errores.

public static byte[] readFile(File f) throws FileNotFoundException, IOException {
    int fsize = verifyFileSize(f);
    byte[] data = new byte[fsize];
    int read = readFile(f, data);
    verifyAllDataRead(f, data, read);
    return data;
}

private static int verifyFileSize(File f) throws IOException {
    long fsize = f.length();
    if (fsize > Integer.MAX_VALUE) {
        throw new IOException("File size (" + fsize + " bytes) for " + f.getName() + " too large.");
    }
    return (int) fsize;
}

public static void verifyAllDataRead(File f, byte[] data, int read) throws IOException {
    if (read != data.length) {
        throw new IOException("Expected to read " + data.length 
                + " bytes from file " + f.getName() + " but got only " + read + " bytes from file.");
    }
}

Esta aplicación se añade otro de los puntos ocultos de la falla: OutOfMemory en el punto donde la nueva matriz de datos es creado.

Para dar cabida a aplicaciones adicionales, métodos adicionales pueden ser añadidos para ayudar con diferentes escenarios. Por ejemplo, digamos que la aplicación realmente no quieren tratar con excepciones comprobadas:

public static void writeFileData(File f, byte[] data) {
    try {
        writeFile(f, data);
    } catch (Exception e) {
        fileExceptionToRuntime(e);
    }
}

public static byte[] readFileData(File f) {
    try {
        return readFile(f);
    } catch (Exception e) {
        fileExceptionToRuntime(e);
    }
    return null;
}

public static int readFileData(File f, byte[] data) {
    try {
        return readFile(f, data);
    } catch (Exception e) {
        fileExceptionToRuntime(e);
    }
    return -1;
}

private static void fileExceptionToRuntime(Exception e) {
    if (e instanceof RuntimeException) { // e.g. NullPointerException
        throw (RuntimeException)e;
    }
    RuntimeException re = new RuntimeException(e.toString());
    re.setStackTrace(e.getStackTrace());
    throw re;
}

El método fileExceptionToRuntime es una implementación mínima, pero muestra la idea aquí.

La biblioteca también podría ayudar a una aplicación a la solución de problemas cuando se produce un error. Por ejemplo, un método de canReadFile(File f) puede comprobar si un archivo existe y es legible y no es demasiado grande. La aplicación puede llamar a una función de este tipo después de que un archivo-leer falla y verificación de razones comunes por las que un archivo no se puede leer. El mismo puede ser hecho por escrito a un archivo.

2021-11-28 22:59:55

Agradezco la ayuda y respuesta informativa. Me voy a poner juntos en un proyecto para ver si puedo entender mejor. ¿Cuál es la razón para el cambio de la readBytes firma del método de lo que había? (la tuya un byte[] como uno de los argumentos y devuelve int). También es su último bloque de código con la intención de ser parte de la biblioteca o de la aplicación?
Nerdy Bunz

también no la línea de "return (int) f.length();" accidente desde f.la longitud es mayor que el Entero.MAX_VALUE?
Nerdy Bunz

@NerdyBunz Sobre la última pregunta: no, "downcasting" no da un error y en este caso, se produce una excepción IOException cuando el fsize el valor es demasiado grande. También, debo de haber re-usado fsize allí (desde f.length() resultados en una operación de e/S).
vanOekel

Sobre la primera pregunta: de todos es la intención de ser parte de la biblioteca. Mi byte[] readFile(File f) es similar a la de su byte[] readBytesFromFile(final File file). Mi byte[] readFileData(File f) el método es un ejemplo de cómo se puede personalizar estas funciones. Tuve problemas para averiguar cuáles son los métodos para exponer (public) y mantener oculto (privatey creo que es una pregunta que sólo usted puede contestar: ¿qué métodos desea que la aplicación para usar sin ser restrictivos a la aplicación?
vanOekel
3

Aunque usted no puede utilizar las bibliotecas de terceros, usted todavía puede leer su código y aprender de su experiencia. En Google Guayaba por ejemplo, se suele leer un archivo en bytes como este:

FileInputStream reader = new FileInputStream("test.txt");
byte[] result = ByteStreams.toByteArray(reader);

El núcleo de la aplicación de la presente es toByteArrayInternal. Antes de llamar a esto, usted debe comprobar:

  • Not null archivo se pasa (NullPointerException)
  • Existe el archivo (FileNotFoundException)

Después de eso, se reduce a la manipulación de un InputStream y este donde IOExceptions vienen. Cuando la lectura de secuencias de un montón de cosas fuera del control de su aplicación puede ir mal (sectores defectuosos y otros problemas de hardware, mal funcionamiento de los controladores, OS derechos de acceso) y se manifiestan con una IOException.

Estoy copiando aquí la aplicación:

private static final int BUFFER_SIZE = 8192;

/** Max array length on JVM. */
private static final int MAX_ARRAY_LEN = Integer.MAX_VALUE - 8;

private static byte[] toByteArrayInternal(InputStream in, Queue<byte[]> bufs, int totalLen)
      throws IOException {
    // Starting with an 8k buffer, double the size of each successive buffer. Buffers are retained
    // in a deque so that there's no copying between buffers while reading and so all of the bytes
    // in each new allocated buffer are available for reading from the stream.
    for (int bufSize = BUFFER_SIZE;
        totalLen < MAX_ARRAY_LEN;
        bufSize = IntMath.saturatedMultiply(bufSize, 2)) {
      byte[] buf = new byte[Math.min(bufSize, MAX_ARRAY_LEN - totalLen)];
      bufs.add(buf);
      int off = 0;
      while (off < buf.length) {
        // always OK to fill buf; its size plus the rest of bufs is never more than MAX_ARRAY_LEN
        int r = in.read(buf, off, buf.length - off);
        if (r == -1) {
          return combineBuffers(bufs, totalLen);
        }
        off += r;
        totalLen += r;
      }
    }

    // read MAX_ARRAY_LEN bytes without seeing end of stream
    if (in.read() == -1) {
      // oh, there's the end of the stream
      return combineBuffers(bufs, MAX_ARRAY_LEN);
    } else {
      throw new OutOfMemoryError("input is too large to fit in a byte array");
    }
  }

Como puedes ver la mayoría de la lógica tiene que ver con la lectura del archivo en trozos. Esto es para manejar las situaciones, donde no se conoce el tamaño de la InputStream, antes de comenzar la lectura. En su caso, sólo es necesario para leer archivos y usted debería ser capaz de saber la longitud de antemano, por lo que esta complejidad podría ser evitado.

La salida es OutOfMemoryException. En el estándar de Java el límite es demasiado grande, sin embargo en Android, que será de un valor mucho menor. Usted debe comprobar, antes de intentar leer el archivo que hay suficiente memoria disponible.

2021-11-26 13:42:23

En otros idiomas

Esta página está en otros idiomas

Русский
..................................................................................................................
Italiano
..................................................................................................................
Polski
..................................................................................................................
Română
..................................................................................................................
한국어
..................................................................................................................
हिन्दी
..................................................................................................................
Français
..................................................................................................................
Türk
..................................................................................................................
Česk
..................................................................................................................
Português
..................................................................................................................
ไทย
..................................................................................................................
中文
..................................................................................................................
Slovenský
..................................................................................................................