Tahukah anda, mungkin terdapat Fail yang wujud dan tidak wujud pada masa yang sama? Adakah anda sedar, bahawa anda boleh memadamkan fail dan masih menggunakannya? Temui kes tepi fail ini & lain-lain dalam pembangunan perisian.
Dalam artikel saya sebelum ini tentang kes tepi dalam pembangunan perisian, saya menulis tentang perangkap teks dan saya memberi anda beberapa cadangan, bagaimana untuk mengelakkannya. Dalam catatan blog ini, saya ingin menumpukan pada fail dan operasi I/O fail.
API java.io.File menyediakan, antara lain, 3 kaedah ini:
#wujud()
#isDirectory()
#isFile()
Orang mungkin berfikir bahawa, jika ia ditunjuk oleh laluan tertentu yang wujud, objek adalah sama ada fail atau direktori — seperti dalam soalan ini pada Stack Overflow. Walau bagaimanapun, ini tidak selalunya benar.
Ia tidak disebut secara eksplisit dalam File#isFile() javadocs, tetapi fail **ada benar-benar bermaksud **fail biasa. Oleh itu, fail Unix khas seperti peranti, soket dan paip mungkin wujud tetapi ia bukan fail dalam takrifan itu.
Lihat coretan berikut:
import java.io.File val file = File("/dev/null") println("exists: ${file.exists()}") println("isFile: ${file.isFile()}") println("isDirectory: ${file.isDirectory()}")
Seperti yang anda boleh lihat pada demo langsung, Fail yang bukan fail mahupun direktori mungkin wujud.
Pautan simbolik juga merupakan fail khas tetapi ia dikendalikan secara telus hampir di mana-mana dalam API java.io (lama). Satu-satunya pengecualian ialah keluarga kaedah #getCanonicalPath()/#getCanonicalFile(). Ketelusan di sini bermakna semua operasi dimajukan kepada sasaran, sama seperti ia dilakukan secara langsung padanya. Ketelusan sedemikian biasanya berguna, mis. anda hanya boleh membaca daripada, atau menulis ke, beberapa fail. Anda tidak mengambil berat tentang resolusi laluan pautan pilihan. Walau bagaimanapun, ia juga boleh membawa kepada beberapa kes pelik. Contohnya, mungkin terdapat Fail yang wujud dan tidak wujud pada masa yang sama.
Mari kita pertimbangkan pautan simbolik yang tergantung. Sasarannya tidak wujud, jadi semua kaedah dari bahagian sebelumnya akan mengembalikan palsu. Walau bagaimanapun, laluan fail sumber masih diduduki, mis. anda tidak boleh membuat fail baharu pada laluan itu. Berikut ialah kod yang menunjukkan kes ini:
import java.nio.file.Files import java.nio.file.Path import java.nio.file.Paths val path = Paths.get("foo") Files.createSymbolicLink(path, path) println("exists : ${path.toFile().exists()}") println("isFile : ${path.toFile().isFile()}") println("isDirectory : ${path.toFile().isDirectory()}") println("createNewFile: ${path.toFile().createNewFile()}")
Dan tunjuk cara langsung.
Dalam java.io API, untuk mencipta direktori yang mungkin tidak wujud dan memastikan ia wujud selepas itu, anda boleh menggunakan File#mkdir() (atau File#mkdirs() jika anda ingin mencipta direktori induk yang tidak wujud sebagai baik) dan kemudian File#isDirectory(). Adalah penting untuk menggunakan kaedah ini dalam susunan yang disebutkan. Mari lihat apa yang mungkin berlaku jika pesanan diterbalikkan. Dua (atau lebih) benang yang melakukan operasi yang sama diperlukan untuk menunjukkan kes ini. Di sini, kami akan menggunakan benang biru dan merah.
(merah) ialahDirektori()? — tidak, perlu mencipta
(biru) ialahDirektori()? — tidak, perlu mencipta
(merah) mkdir()? — kejayaan
(biru) mkdir()? — gagal
Seperti yang anda lihat benang biru gagal mencipta direktori. Walau bagaimanapun, ia sebenarnya dicipta, jadi hasilnya harus positif. Jika isDirectory() telah memanggil pada penghujungnya, hasilnya akan sentiasa betul.
Bilangan fail dibuka pada masa yang sama oleh proses UNIX tertentu adalah terhad kepada nilai RLIMIT_NOFILE. Pada Android, ini biasanya 1024 tetapi berkesan (tidak termasuk deskriptor fail yang digunakan oleh rangka kerja) anda boleh menggunakan lebih sedikit (semasa ujian dengan Aktiviti kosong pada Android 8.0.0, terdapat kira-kira 970 deskriptor fail tersedia untuk digunakan). Apa yang berlaku jika anda cuba membuka lebih banyak lagi? Nah, fail itu tidak akan dibuka. Bergantung pada konteks, anda mungkin menghadapi pengecualian dengan sebab yang jelas (Terlalu banyak fail terbuka), sedikit mesej yang membingungkan (cth. Fail ini tidak boleh dibuka sebagai deskriptor fail; ia mungkin dimampatkan) atau hanya palsu sebagai nilai pulangan apabila anda biasanya menjangkakan benar. Lihat kod yang menunjukkan isu ini:
package pl.droidsonroids.edgetest import android.content.res.AssetFileDescriptor import android.support.test.InstrumentationRegistry import org.junit.Assert import org.junit.Test class TooManyOpenFilesTest { //asset named "test" required @Test fun tooManyOpenFilesDemo() { val context = InstrumentationRegistry.getContext() val assets = context.assets val descriptors = mutableListOf<AssetFileDescriptor>() try { for (i in 0..1024) { descriptors.add(assets.openFd("test")) } } catch (e: Exception) { e.printStackTrace() //java.io.FileNotFoundException: This file can not be opened as a file descriptor; it is probably compressed } try { context.openFileOutput("test", 0) } catch (e: Exception) { e.printStackTrace() //java.io.FileNotFoundException: /data/user/0/pl.droidsonroids.edgetest/files/test (Too many open files) } val sharedPreferences = context.getSharedPreferences("test", 0) Assert.assertTrue(sharedPreferences.edit().putBoolean("test", true).commit()) } }
Perhatikan bahawa, jika anda menggunakan #apply(), nilai itu tidak akan disimpan secara berterusan — jadi anda tidak akan mendapat sebarang pengecualian. Walau bagaimanapun, ia boleh diakses sehingga proses apl yang memegang bahawa contoh SharedPreferences dimatikan. Itu kerana pilihan yang dikongsi turut disimpan dalam ingatan.
Orang mungkin berfikir bahawa zombi, hantu dan makhluk lain yang serupa wujud dalam fantasi dan fiksyen seram sahaja. Tetapi… ia adalah nyata dalam sains komputer! Istilah biasa sedemikian merujuk kepada proses zombi. Malah, fail mayat hidup juga boleh dibuat dengan mudah.
In Unix-like operating systems, file deletion is usually implemented by unlinking. The unlinked file name is removed from the file system (assuming that it is the last hardlink) but any already open file descriptors remain valid and usable. You can still read from and write to such a file. Here is the snippet:
import java.io.BufferedReader import java.io.File import java.io.FileReader val file = File("test") file.writeText("this is file content") BufferedReader(FileReader(file)).use { println("deleted?: ${file.delete()}") println("content?: ${it.readLine()}") }
And a live demo.
First of all, remember that we can’t forget about the proper method calling order when creating non-existent directories. Furthermore, keep in mind that a number of files open at the same time is limited and not only files explicitly opened by you are counted. And the last, but not least, a trick with file deletion before the last usage can give you a little bit more flexibility.
Originally published at www.thedroidsonroids.com on September 27, 2017.
Atas ialah kandungan terperinci Kes Tepi yang Perlu Diingati. Fail Bahagian. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!