Lire/Écrire des Octets à Partir d'un Fichier à l'Aide Seulement de Java.IO

0

La question

Comment peut-on écrire un tableau d'octets dans un fichier (et le lire à partir de ce fichier) en Java?

Oui, nous le savons tous il y a déjà beaucoup de questions comme ça, mais ils deviennent très salissant et subjective en raison du fait qu'il y a de nombreuses façons d'accomplir cette tâche.

Donc, nous allons réduire la portée de la question:

Domaine:

  • Android / Java

Ce que nous voulons:

  • Rapide (autant que possible)
  • Sans Bug (dans un rigidement manière méticuleuse)

Ce que nous ne sommes pas en train de faire:

  • Les bibliothèques de tiers
  • Les bibliothèques qui nécessitent l'API Android au plus tard le 23 (Guimauve)

(Donc, que les règles d' Apache Commons, Google Goyave, Java.nio, et nous laisse avec un bon ol' Java.io)

Ce dont nous avons besoin:

  • Tableau d'octets est toujours exactement le même (contenu et format) après la traversée de l'écriture puis-processus de lecture
  • La méthode d'écriture ne nécessite que deux arguments: fichier Fichier, et byte[] data
  • Lire la méthode retourne un byte[] et ne nécessite qu'un seul argument: fichier Fichier

Dans mon cas particulier, ces méthodes sont privées (pas une bibliothèque) et ne sont PAS responsable de ce qui suit, (mais si vous voulez créer une solution universelle qui s'applique à un large public, y aller):

  • Fil de sécurité (fichier ne sera pas consulté par plus d'un processus à la fois)
  • Fichier null
  • Fichier pointant vers inexistante emplacement
  • Manque d'autorisations à l'emplacement du fichier
  • Tableau d'octets en trop grandes
  • Tableau d'octets nul
  • Traiter avec des "index", "longueur" ou "ajouter" arguments/capacités

Donc... nous sommes un peu à la recherche de ce dernier à l'épreuve des balles de code que les gens dans l'avenir peut supposer, c'est sûr à utiliser car votre réponse a beaucoup de votes, et il n'y a pas les commentaires qui disent, "Qui peut se bloquer si..."

C'est ce que j'ai à ce jour:

Écrire Des Octets Dans Un Fichier:

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);
        }
    }

Lire Des Octets À Partir D'Un Fichier:

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;
}

De ce que j'ai lu, les Exceptions possibles sont:

FileNotFoundException : ai-je raison que cela ne devrait pas se produire tant que le chemin d'accès au fichier fourni a été calculée en utilisant Android propres outils à l'interne et/ou si l'application a été testé correctement?

IOException : je ne sais pas vraiment ce qui a pu provoquer cela... mais je suppose qu'il n'y a pas moyen de contourner cela si il n'.

Donc, avec cela à l'esprit... ces méthodes être améliorés ou remplacés, et si oui, avec quoi?

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

La meilleure réponse

6

Il ressemble à ceux-ci vont être de base utilitaire/bibliothèque de méthodes qui doivent s'exécuter sur l'API Android 23 ou plus tard.

Concernant la bibliothèque de méthodes, je trouve qu'il est préférable de ne pas faire des hypothèses sur la façon dont les applications vont utiliser ces méthodes. Dans certains cas, les applications peuvent souhaitez recevoir vérifié IOExceptions (car les données d'un fichier doit exister pour que l'application fonctionne), dans d'autres cas, les applications ne peuvent pas se soucient même si les données ne sont pas disponibles (car les données d'un fichier n'est le cache qui est également disponible à partir d'une source primaire).

Quand il s'agit d'opérations d'e/S, il n'y a jamais une garantie que les opérations de réussir (par exemple, l'utilisateur abandon de téléphone dans les toilettes). La bibliothèque devrait en tenir compte et de donner à l'application d'un choix sur la façon de gérer les erreurs.

Pour optimiser les performances I/O supposons toujours que du bonheur de chemin" et de repérer les erreurs de comprendre ce qui s'est passé. C'est contre-intuitif à la normale de la programmation, mais essentiel de traiter avec storage I/O. Par exemple, juste vérifier si un fichier existe avant la lecture à partir d'un fichier pouvez faire votre demande deux fois plus lente de toutes ces sortes de I/O actions d'ajouter jusqu'à rapide à lent de l'application. Il suffit de supposer que le fichier existe et si vous obtenez un message d'erreur, seulement ensuite, vérifiez si le fichier existe.

Alors au vu de ces idées, les principales fonctions pourrait ressembler à:

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); 
    }
}

Notes à propos de la mise en œuvre:

  • Les méthodes peuvent également jeter à l'exécution des exceptions comme NullPointerExceptions - ces méthodes ne sont jamais va être "sans bug".
  • Je ne pense pas que la mémoire tampon est nécessaire/voulu dans les méthodes ci-dessus, un seul appel des indigènes est fait (voir aussi ici).
  • Désormais, l'application a également la possibilité de lire que le début d'un fichier.

Pour le rendre plus facile pour une application de lecture d'un fichier, une méthode supplémentaire peut être ajouté. Mais notez que c'est à la bibliothèque pour détecter les erreurs et de les signaler à l'application depuis l'application elle-même ne peut plus détecter ces erreurs.

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.");
    }
}

Cette application ajoute un autre point caché de l'échec: dépassement de mémoire à l'endroit où le nouveau tableau de données est créée.

Pour répondre aux demandes en outre, d'autres méthodes peuvent être ajoutés pour aider avec différents scénarios. Par exemple, disons que l'application n'a pas vraiment envie de traiter avec les exceptions suivantes:

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;
}

La méthode fileExceptionToRuntime est une implémentation minimale, mais il montre que l'idée ici.

La bibliothèque peut également aider à une application pour résoudre les problèmes lorsqu'une erreur se produit. Par exemple, une méthode canReadFile(File f) pourrait vérifier si un fichier existe et est lisible et n'est pas trop grand. L'application pourrait appeler cette fonction après un échec de lecture et de vérification pour les raisons courantes pour lesquelles un fichier ne peut être lu. La même chose peut être fait pour l'écrire dans un fichier.

2021-11-28 22:59:55

Apprécier l'utile et instructif de réponse. Je suis en train de mettre ensemble dans un projet pour voir si je peux le comprendre mieux. Quelle est la raison de la modification de la readBytes signature de la méthode à partir de ce que j'avais? (le vôtre prend un byte[] comme l'un des arguments et retourne un int). Aussi est votre dernier bloc de code destiné à être une partie de la bibliothèque ou de la demande?
Nerdy Bunz

également à ne pas la ligne "return (int) f.longueur();" crash depuis f.la longueur est plus grande que Entier.MAX_VALUE?
Nerdy Bunz

@NerdyBunz à Propos de la dernière question: non, "passer" ne donne pas une erreur et dans ce cas, une IOException est levée lorsque la fsize la valeur est trop grande. Aussi, j'aurais ré-utilisé fsize il y a (depuis f.length() résultats dans une opération d'e/S).
vanOekel

Sur la première question: tout cela est destiné à faire partie de la bibliothèque. Mon byte[] readFile(File f) est similaire à la votre byte[] readBytesFromFile(final File file). Mon byte[] readFileData(File f) la méthode est un exemple de la façon dont vous pouvez personnaliser ces fonctions. J'ai eu de la difficulté à décider des méthodes à exposer (public) et de les tenir cachés (private) et je pense que c'est une question que vous pouvez répondre: quelles méthodes avez-vous souhaitez que l'application à utiliser sans être restrictive de la demande?
vanOekel
3

Bien que vous ne pouvez pas utiliser des bibliothèques tierces, vous pouvez toujours lire leur code et apprendre de leur expérience. Dans Google Goyave par exemple, vous avez l'habitude de lire un fichier en octets comme ceci:

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

La base de la mise en œuvre de ce est toByteArrayInternal. Avant l'appel de cette, vous devez vérifier:

  • Un pas de fichier nul est passé (NullPointerException)
  • Le fichier existe (FileNotFoundException)

Après cela, il est réduit à de la manipulation d'un InputStream et ce où IOExceptions viennent. Lors de la lecture de flux beaucoup de choses sur le contrôle de votre application peut aller mal (mauvais secteurs et d'autres problèmes de matériel, mal-fonctionnement des pilotes, le système d'exploitation de droits d'accès) et se manifestent avec une IOException.

Je copie ici la mise en œuvre:

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");
    }
  }

Comme vous pouvez le voir la plupart de la logique a à voir avec la lecture du fichier en morceaux. C'est pour gérer les situations où vous ne connaissez pas la taille de l'InputStream, avant de commencer la lecture. Dans votre cas, vous avez seulement besoin de lire des fichiers et vous devriez être en mesure de connaître la longueur d'avance, de sorte que cette complexité pourrait être évité.

L'autre case est OutOfMemoryException. En Java standard de la limite est trop grand, cependant dans Android, il y aura beaucoup moins de valeur. Vous devriez vérifier, avant d'essayer de lire le fichier qu'il y a suffisamment de mémoire disponible.

2021-11-26 13:42:23

Dans d'autres langues

Cette page est dans d'autres langues

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