Čtení/Zápisu Bajtů do a Ze Souboru Pouze Pomocí Java.IO

0

Otázka

Jak můžeme napsat bajtové pole do souboru (a přečtěte si ji zpět z tohoto souboru) v Javě?

Ano, všichni víme, že již existuje spousta otázek, jako to, ale oni si velmi chaotický a subjektivní vzhledem k tomu, že tam jsou tak mnoho způsobů, jak provést tento úkol.

Takže pojďme snížit rozsah otázku:

Domény:

  • Android / Java

Co chceme:

  • Rychle (jak je to možné)
  • Bug-free (v pevně pečlivý způsob, jak)

Co dělat nebudeme:

  • Knihoven třetích stran
  • Knihovny, které vyžadují Android API později než 23 (Marshmallow)

(Takže to vylučuje Apache Commons, Google Guava, Java.nio, a ponechává nás dobrý ol' Java.io)

Co potřebujeme:

  • Bajt pole je vždy úplně stejný (obsah a velikost) poté, co prochází psát-pak-proces čtení
  • Napište metoda vyžaduje pouze dva argumenty: Soubor soubor a byte[] data
  • Čtení metoda vrátí byte[] a vyžaduje pouze jeden argument: File

V mém konkrétním případě, jsou tyto metody vlastní (ne knihovnu) a NENÍ odpovědný za následující, (ale pokud chcete vytvořit více univerzální řešení, které se vztahuje na širší publikum, jít na to):

  • Thread-bezpečnost (soubor nebude mít přístup více než jeden proces najednou)
  • Soubor je null
  • Soubor, který odkazuje na neexistující místo
  • Nedostatek oprávnění na umístění souboru
  • Bajt pole je příliš velké
  • Byte array je null
  • Jednání s "index," "délku", nebo "připojit" argumenty/schopnosti

Takže jsme tak nějak v hledání konečného bullet-proof kód, který lidé v budoucnosti lze předpokládat, že je bezpečné použít, protože vaše odpověď má spoustu up-hlasů, a nejsou tam žádné komentáře, které říkají, "To může selhat, pokud..."

To je to, co zatím mám:

Zápis Bytů Do Souboru:

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

Čtení Bajtů Ze Souboru:

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

Z toho, co jsem četl, možné Výjimky jsou:

FileNotFoundException : mám pravdu, že tohle by se nemělo stát, jak dlouho cesta k souboru dodáváno byl odvozen pomocí Android je vlastní interní nástroje, a/nebo pokud aplikace byla testována správně?

IOException : já opravdu nevím, co by to mohlo způsobit... ale já jsem za předpokladu, že neexistuje žádný způsob, jak kolem toho, když to dělá.

Takže s tím na mysli... mohou tyto metody být zlepšeny nebo nahrazeny, a pokud ano, s čím?

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

Nejlepší odpověď

6

Vypadá to, že se bude základní utility/knihovny metod, které musí běžet na Android API 23 nebo novější.

O knihovně metody, zjistil jsem, že nejlepší dělat žádné předpoklady o tom, jak aplikace bude používat tyto metody. V některých případech může aplikace chcete přijímat zkontrolovat IOExceptions (protože data ze souboru musí existovat pro aplikace do práce), v jiných případech může aplikace není ani jedno, jestli data nejsou k dispozici (protože data ze souboru je jen cache, která je k dispozici také z primární zdroj).

Když přijde na I/O operace, není nikdy zárukou, že operace bude úspěšná (např. uživatel upustí telefon v záchodě). Knihovna by měla odrážet to, a dá aplikace na výběr, jak zvládnout chyby.

Optimalizovat I/O výkon, vždy předpokládat, že "šťastnou cestu" a chytit chyby se zjistit, co se pokazilo. To je kontraproduktivní intuitivní normální programování, ale zásadní při jednání s úložiště I/O. například, jen kontrola, zda soubor existuje před čtením ze souboru, může se vaše aplikace dvakrát pomalejší - všechny tyto druhy I/O akce přidat až rychle zpomalit vaše aplikace. Jen předpokládám, že soubor existuje, a pokud se dostanete k chybě, teprve pak zkontrolujte, zda soubor existuje.

Tak vzhledem k tomu, tyto nápady, hlavní funkce by mohla vypadat takto:

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

Poznámky o provádění:

  • Metody, může také hodit runtime výjimky, jako NullPointerExceptions - tyto metody jsou nikdy bude "bez chyb".
  • Nemyslím si, že ukládání do vyrovnávací paměti je potřeboval/chtěl ve výše uvedených metod, neboť pouze jedna nativní volání je provedeno (viz také zde).
  • Aplikace má nyní také možnost číst pouze začátek souboru.

Aby to bylo jednodušší pro aplikace číst soubor, další metody mohou být přidány. Ale všimněte si, že je do knihovny, aby zjistil jakékoli chyby a hlásit je do aplikace, protože aplikace sama o sobě může již rozpoznat tyto chyby.

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

Toto provedení přidává další skrytý bod selhání: OutOfMemory v okamžiku, kdy nová data pole je vytvořeno.

Vyhovět žádostem další, doplňkové metody mohou být přidány do pomoci s různými scénáři. Například, řekněme, že aplikace opravdu nechce jednat s kontrolované výjimky:

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

Metoda fileExceptionToRuntime je minimální implementace, ale ukazuje to, tu myšlenku.

Knihovna by také mohla pomoci aplikace řešení potíží při chybě dojde. Například metoda canReadFile(File f) mohl zkontrolovat, zda soubor existuje a je čitelný a není příliš velký. Aplikace může volat tyto funkce po souboru-čtení selže a podívejte se na časté důvody, proč soubor nelze přečíst. Totéž může být provedeno pro zápis do souboru.

2021-11-28 22:59:55

Ocení užitečné a informativní odpověď. Dávám to dohromady v projektu, aby zjistil, jestli můžu pochopit to lépe. Co je důvodem pro změnu metody readBytes podpis z toho, co jsem měl? (tvoje trvá byte[] jako jeden z args a vrací int). Také je poslední blok kódu mají být součástí knihovny nebo aplikace?
Nerdy Bunz

také nebude na řádku "return (int) f.length();" crash od f.délka je větší než celé Číslo.MAX_VALUE?
Nerdy Bunz

@NerdyBunz O poslední otázku: ne, "downcasting" nedává chybu a v tomto případě, IOException je vyvolána, když fsize hodnota je příliš velký. Také, měl jsem re-použitý fsize tam (od f.length() výsledky v I/O operace).
vanOekel

O první otázku: má být součástí knihovny. Můj byte[] readFile(File f) je podobný byte[] readBytesFromFile(final File file). Můj byte[] readFileData(File f) metoda je příklad, jak si můžete přizpůsobit tyto funkce dále. Já jsem měl problém přijít na to, které metody odhalit (public) a udržet skryté (private) a myslím, že to je otázka jen vy můžete odpovědět: které metody, které chcete aplikaci používat, aniž by byl omezující aplikaci?
vanOekel
3

I když nemůžeš použití knihoven třetích stran, můžete stále číst jejich kódu a učit se z jejich zkušeností. V Google Guava například, můžete obvykle přečíst soubor, do bajtů, jako je tento:

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

Základní provedení je toByteArrayInternal. Před voláním této, byste měli zkontrolovat:

  • Not null soubor je předán (NullPointerException)
  • Soubor existuje (FileNotFoundException)

Za to, že je snížena na manipulaci s InputStream a to, kde IOExceptions pocházejí. Při čtení proudy spoustu věcí mimo kontrolu vaše aplikace může pokazit (vadné sektory a jiné hardwarové problémy, mal fungující ovladače, OS, přístupová práva) a projevují se IOException.

Jsem kopírování tu realizaci:

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

Jak můžete vidět, většina z logiky má co do činění s, čtení souboru se v kusy. To je k řešení takových situací, kde nevíš, velikost InputStream, před zahájením čtení. Ve vašem případě, budete potřebovat pouze číst soubory a vy byste měli být schopni znát délku předem, takže tato složitost by se dalo předejít.

Další kontrola je OutOfMemoryException. Ve standardní Java limit je příliš velký, nicméně v Android, to bude mnohem menší hodnotu. Měli byste zkontrolovat předtím, než se snaží číst soubor, který tam je k dispozici dostatek paměti.

2021-11-26 13:42:23

V jiných jazycích

Tato stránka je v jiných jazycích

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