Yalnızca Kullanarak Bir Dosyaya ve Dosyadan Bayt Okuma / Yazma Java.IO

0

Soru

Java'da bir dosyaya bir bayt dizisi nasıl yazabiliriz (ve bu dosyadan geri okuyabiliriz)?

Evet, hepimiz zaten bunun gibi birçok soru olduğunu biliyoruz, ancak bu görevi yerine getirmenin pek çok yolu olduğu için çok dağınık ve öznel hale geliyorlar.

Öyleyse sorunun kapsamını azaltalım:

Alan:

  • Android / Java

Ne istiyoruz:

  • (Mümkün olduğu kadar)hızlı
  • Hatasız (son derece titiz bir şekilde)

Yapmadığımız şey:

  • Üçüncü taraf kitaplıkları
  • 23'ten sonra Android API gerektiren kütüphaneler (Marshmallow)

(Yani, bu Apache Commons, Google Guava, Java'yı dışlar.nio, ve bizi eski güzellikle baş başa bırakıyor. Java.io)

İhtiyacımız olan şey:

  • Bayt dizisi, yazma ve okuma işleminden geçtikten sonra her zaman tam olarak aynıdır (içerik ve boyut)
  • Yazma yöntemi yalnızca iki bağımsız değişken gerektirir: File file ve byte[] data
  • Read yöntemi bir byte [] döndürür ve yalnızca bir bağımsız değişken gerektirir: File file

Benim özel durumumda, bu yöntemler özeldir (kütüphane değildir) ve aşağıdakilerden sorumlu değildir (ancak daha geniş bir kitleye uygulanan daha evrensel bir çözüm oluşturmak istiyorsanız, bunun için gidin):

  • İş parçacığı güvenliği (dosyaya aynı anda birden fazla işlem tarafından erişilemez)
  • Dosya boş
  • Varolmayan konuma işaret eden dosya
  • Dosya konumunda izin eksikliği
  • Bayt dizisi çok büyük
  • Bayt dizisi null
  • Herhangi bir" dizin"," uzunluk "veya" ekleme " argümanları / yetenekleri ile uğraşmak

Böyle... gelecekte insanların kullanımının güvenli olduğunu varsayabilecekleri kesin kurşun geçirmez kodu arıyoruz çünkü cevabınız çok fazla oy alıyor ve "Bu çökebilir" diyen hiçbir yorum yok..."

Şimdiye kadar sahip olduğum şey bu:

Dosyaya Bayt Yaz:

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

Dosyadan Bayt Okuma:

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

Okuduklarımdan, olası İstisnalar şunlardır:

FileNotFoundException: Sağlanan dosya yolu Android'in kendi dahili araçları kullanılarak türetildiği ve/veya uygulama düzgün bir şekilde test edildiği sürece bunun olmaması gerektiği konusunda doğru muyum?

IOException: Buna neyin sebep olabileceğini gerçekten bilmiyorum... ama öyle olursa bunun üstesinden gelmenin bir yolu olmadığını varsayıyorum.

Bunu akılda tutarak... bu yöntemler geliştirilebilir veya değiştirilebilir mi ve eğer öyleyse, ne ile?

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

En iyi cevabı

6

Bunlar, Android API 23 veya sonraki sürümlerinde çalışması gereken temel yardımcı program / kütüphane yöntemleri olacak gibi görünüyor.

Kütüphane yöntemleriyle ilgili olarak, uygulamaların bu yöntemleri nasıl kullanacağı konusunda hiçbir varsayımda bulunmamayı en iyi buluyorum. Bazı durumlarda başvurular kontrol edilmek isteyebilir IOExceptions (uygulamanın çalışması için bir dosyadan gelen verilerin bulunması gerektiğinden), diğer durumlarda verilerin kullanılamaması uygulamalar tarafından önemsenmeyebilir (çünkü bir dosyadan gelen veriler yalnızca birincil kaynaktan da kullanılabilen önbellektir).

G / Ç işlemleri söz konusu olduğunda, işlemlerin başarılı olacağına dair hiçbir zaman bir garanti yoktur (örneğin, kullanıcının telefonu tuvalete düşürmesi). Kitaplık bunu yansıtmalı ve uygulamaya hataların nasıl işleneceği konusunda bir seçenek sunmalıdır.

G/Ç performansını optimize etmek için her zaman "mutlu yolu" varsayın ve neyin yanlış gittiğini anlamak için hataları yakalayın. Bu, normal programlamaya karşı sezgiseldir, ancak depolama G/Ç ile uğraşırken esastır.Örneğin, bir dosyadan okumadan önce bir dosyanın var olup olmadığını kontrol etmek uygulamanızı iki kat daha yavaş hale getirebilir - tüm bu tür G/Ç eylemleri uygulamanızı yavaşlatmak için hızlı bir şekilde toplanır. Sadece dosyanın var olduğunu varsayalım ve bir hata alırsanız, ancak o zaman dosyanın var olup olmadığını kontrol edin.

Yani bu fikirler göz önüne alındığında, ana işlevler şöyle görünebilir:

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

Uygulama ile ilgili notlar:

  • Yöntemler ayrıca çalışma zamanı istisnalarını da atabilir NullPointerExceptions - bu yöntemler asla "hatasız" olmayacaktır.
  • Yalnızca bir yerel arama yapıldığı için yukarıdaki yöntemlerde arabelleğe almanın gerekli/gerekli olduğunu düşünmüyorum (ayrıca buraya bakınız).
  • Şimdi bu uygulama aynı zamanda bir dosya tek başına okumak için seçeneği vardır.

Bir uygulamanın bir dosyayı okumasını kolaylaştırmak için ek bir yöntem eklenebilir. Ancak, uygulamanın kendisi artık bu hataları algılayamadığı için hataları tespit etmenin ve bunları uygulamaya bildirmenin kitaplığa bağlı olduğunu unutmayın.

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

Bu uygulama başka bir gizli hata noktası ekler: Yeni veri dizisinin oluşturulduğu noktada OutOfMemory.

Uygulamaları daha fazla barındırmak için, farklı senaryolara yardımcı olacak ek yöntemler eklenebilir. Örneğin, uygulamanın gerçekten denetlenen istisnalarla uğraşmak istemediğini varsayalım:

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

Yöntem fileExceptionToRuntime bu minimal bir uygulamadır, ancak buradaki fikri gösterir.

Kitaplık, bir hata oluştuğunda uygulamanın sorun gidermesine de yardımcı olabilir. Örneğin, bir yöntem canReadFile(File f) bir dosyanın var olup olmadığını ve okunabilir olup olmadığını ve çok büyük olmadığını kontrol edebilir. Uygulama, dosya okuma başarısız olduktan sonra böyle bir işlevi çağırabilir ve dosyanın okunamamasının yaygın nedenlerini denetleyebilir. Aynı şey bir dosyaya yazmak için de yapılabilir.

2021-11-28 22:59:55

Yararlı ve bilgilendirici cevabı takdir edin. Bunu daha iyi anlayıp anlayamayacağımı görmek için bir projede bir araya getiriyorum. readBytes yöntem imzasını sahip olduğumdan değiştirmenin nedeni nedir? (sizinki arg'lerden biri olarak bir bayt[] alır ve int döndürür). Ayrıca son kod bloğunuzun kütüphanenin veya uygulamanın bir parçası olması amaçlanıyor mu?
Nerdy Bunz

ayrıca, "return (int) f.length();" satırı çökmez, çünkü f.length Tamsayıdan daha büyüktür.MAX_VALUE?
Nerdy Bunz

@NerdyBunz Son soru hakkında: hayır," downcasting " bir hata vermez ve bu durumda, bir İOException atıldığında fsize değer çok büyük. Ayrıca, yeniden kullanmalıydım. fsize orada (o zamandan beri f.length() bir G/Ç işlemi ile sonuçlanır).
vanOekel

İlk soru hakkında: hepsi kütüphanenin bir parçası olması amaçlanmıştır. Benim byte[] readFile(File f) sizin için benzer byte[] readBytesFromFile(final File file). Benim byte[] readFileData(File f) yöntem, bu işlevleri daha fazla nasıl özelleştirebileceğinize bir örnektir. Hangi yöntemleri açığa çıkaracağımı bulmakta zorlandım (public) ve gizli tut (private) ve bence bu sadece sizin cevaplayabileceğiniz bir soru: uygulamayı kısıtlamadan uygulamanın hangi yöntemleri kullanmasını istiyorsunuz?
vanOekel
3

Üçüncü taraf kitaplıklarını kullanamasanız da, yine de kodlarını okuyabilir ve deneyimlerinden öğrenebilirsiniz. Örneğin, Google Guava'da, genellikle bir dosyayı aşağıdaki gibi baytlara okursunuz:

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

Bunun temel uygulaması toByteArrayInternal. Bunu aramadan önce kontrol etmelisiniz:

  • Boş olmayan bir dosya geçirilir (NullPointerException)
  • Dosya var (FileNotFoundException)

Bundan sonra, bir giriş akışının işlenmesine ve ioexceptions'ın geldiği yere indirgenir. Akışları okurken, uygulamanızın denetimi dışındaki pek çok şey yanlış gidebilir (bozuk sektörler ve diğer donanım sorunları, hatalı çalışan sürücüler, işletim sistemi erişim hakları) ve kendilerini bir İOException ile gösterebilir.

Uygulamayı buraya kopyalıyorum:

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

Gördüğünüz gibi, mantığın çoğu dosyayı parçalar halinde okumakla ilgilidir. Bu, okumaya başlamadan önce giriş akışının boyutunu bilmediğiniz durumları ele almak içindir. Sizin durumunuzda, yalnızca dosyaları okumanız gerekir ve uzunluğu önceden bilmeniz gerekir, böylece bu karmaşıklıktan kaçınılabilir.

Diğer onay OutOfMemoryException ' dir. Standart Java'da sınır çok büyük, ancak Android'de çok daha küçük bir değer olacak. Dosyayı okumaya çalışmadan önce yeterli bellek olup olmadığını kontrol etmelisiniz.

2021-11-26 13:42:23

Diğer dillerde

Bu sayfa diğer dillerde

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