Ръководство за Java FileChannel

1. Общ преглед

В този бърз урок ще разгледаме класа FileChannel, предоставен в библиотеката Java NIO . Ще обсъдим как да четем и пишем данни с помощта на FileChannel и ByteBuffer .

Също така ще проучим предимствата на използването на FileChannel и някои от другите му функции за манипулиране на файлове.

2. Предимства на FileChannel

Предимствата на FileChannel включват:

  • Четене и писане на определена позиция във файл
  • Зареждане на част от файл директно в паметта, което може да бъде по-ефективно
  • Можем да прехвърляме файлови данни от един канал в друг с по-бърза скорост
  • Можем да заключим секция от файл, за да ограничим достъпа от други нишки
  • За да избегнем загуба на данни, можем да принудим незабавно записване на актуализации във файл в хранилището

3. Четене с FileChannel

FileChannel се представя по-бързо от стандартния вход / изход, когато четем голям файл.

Трябва да отбележим, че въпреки че са част от Java NIO , FileChannel операциите блокират и нямат неблокиращ режим.

3.1. Четене на файл с помощта на FileChannel

Нека разберем как да четем файл с помощта на FileChannel за файл, който съдържа:

Hello world

Този тест чете файла и проверява дали е прочетен добре:

@Test public void givenFile_whenReadWithFileChannelUsingRandomAccessFile_thenCorrect() throws IOException { try (RandomAccessFile reader = new RandomAccessFile("src/test/resources/test_read.in", "r"); FileChannel channel = reader.getChannel(); ByteArrayOutputStream out = new ByteArrayOutputStream()) { int bufferSize = 1024; if (bufferSize > channel.size()) { bufferSize = (int) channel.size(); } ByteBuffer buff = ByteBuffer.allocate(bufferSize); while (channel.read(buff) > 0) { out.write(buff.array(), 0, buff.position()); buff.clear(); } String fileContent = new String(out.toByteArray(), StandardCharsets.UTF_8); assertEquals("Hello world", fileContent); } }

Тук четем байтове от файла, използвайки FileChannel , RandomAccessFile и ByteBuffer.

Трябва също да отбележим, че множество едновременни нишки могат безопасно да използват FileChannels . Въпреки това, само по една нишка в даден момент е разрешена операция, която включва актуализиране на позицията на канала или промяна на размера на файла. Това блокира други нишки, опитващи се за подобна операция, докато предишната операция не завърши.

Операциите, които предоставят явни позиции на канала, могат да се изпълняват едновременно, без да бъдат блокирани.

3.2. Отваряне на FileChannel

За да прочетем файл с помощта на FileChannel , трябва да го отворим.

Нека да видим как да отворите FileChannel с помощта на RandomAccessFile :

RandomAccessFile reader = new RandomAccessFile(file, "r"); FileChannel channel = reader.getChannel();

Режим „r“ показва, че каналът е само „отворен за четене“. Трябва да отбележим, че затварянето на RandomAccessFile също ще затвори свързания канал.

След това ще видим отваряне на FileChannel за четене на файл с помощта на FileInputStream :

FileInputStream fin= new FileInputStream(file); FileChannel channel = fin.getChannel();

По същия начин затварянето на FileInputStream също затваря канала, свързан с него.

3.3. Четене на данни от FileChannel

За да прочетем данните, можем да използваме един от методите за четене .

Нека да видим как да четем последователност от байтове. Ще използваме ByteBuffer, за да съхраняваме данните:

ByteBuffer buff = ByteBuffer.allocate(1024); int noOfBytesRead = channel.read(buff); String fileContent = new String(buff.array(), StandardCharsets.UTF_8); assertEquals("Hello world", fileContent);

След това ще видим как да прочетем последователност от байтове, започвайки от позиция на файла:

ByteBuffer buff = ByteBuffer.allocate(1024); int noOfBytesRead = channel.read(buff, 5); String fileContent = new String(buff.array(), StandardCharsets.UTF_8); assertEquals("world", fileContent);

Трябва да отбележим необходимостта от Charset за декодиране на байтов масив в String .

Ние уточни CHARSET с които байта първоначално са били кодирани. Без него , ние може да се свърши с неясен текст. По-специално многобайтовите кодирания като UTF-8 и UTF-16 може да не са в състояние да декодират произволна секция от файла, тъй като някои от многобайтовите символи може да са непълни.

4. Писане с FileChannel

4.1. Записване във файл с помощта на FileChannel

Нека да проучим как да пишем с помощта на FileChannel :

@Test public void whenWriteWithFileChannelUsingRandomAccessFile_thenCorrect() throws IOException { String file = "src/test/resources/test_write_using_filechannel.txt"; try (RandomAccessFile writer = new RandomAccessFile(file, "rw"); FileChannel channel = writer.getChannel()){ ByteBuffer buff = ByteBuffer.wrap("Hello world".getBytes(StandardCharsets.UTF_8)); channel.write(buff); // verify RandomAccessFile reader = new RandomAccessFile(file, "r"); assertEquals("Hello world", reader.readLine()); reader.close(); } }

4.2. Отваряне на FileChannel

За да запишем във файл с помощта на FileChannel , трябва да го отворим.

Нека да видим как да отворите FileChannel с помощта на RandomAccessFile :

RandomAccessFile writer = new RandomAccessFile(file, "rw"); FileChannel channel = writer.getChannel();

Режим „rw“ показва, че каналът е „отворен за четене и писане“.

Нека също да видим как да отворите FileChannel с помощта на FileOutputStream :

FileOutputStream fout = new FileOutputStream(file); FileChannel channel = fout.getChannel(); 

4.3. Записване на данни с FileChannel

За да запишем данни с FileChannel , можем да използваме един от методите за запис .

Нека да видим как да напишем последователност от байтове, като използваме ByteBuffer за съхраняване на данните:

ByteBuffer buff = ByteBuffer.wrap("Hello world".getBytes(StandardCharsets.UTF_8)); channel.write(buff); 

След това ще видим как да напишем последователност от байтове, започвайки от файлова позиция:

ByteBuffer buff = ByteBuffer.wrap("Hello world".getBytes(StandardCharsets.UTF_8)); channel.write(buff, 5); 

5. Текуща позиция

FileChannel ни позволява да получим и променим позицията, на която четем или пишем.

Нека да видим как да получим текущата позиция:

long originalPosition = channel.position();

След това нека видим как да зададем позицията:

channel.position(5); assertEquals(originalPosition + 5, channel.position());

6. Вземете размера на файла

Нека да видим как да използваме метода FileChannel.size , за да получим размера на файла в байтове:

@Test public void whenGetFileSize_thenCorrect() throws IOException { RandomAccessFile reader = new RandomAccessFile("src/test/resources/test_read.in", "r"); FileChannel channel = reader.getChannel(); // the original file size is 11 bytes. assertEquals(11, channel.size()); channel.close(); reader.close(); }

7. Съкратете файл

Нека разберем как да използваме метода FileChannel.truncate , за да отсечем файл до зададения размер в байтове:

@Test public void whenTruncateFile_thenCorrect() throws IOException { String input = "this is a test input"; FileOutputStream fout = new FileOutputStream("src/test/resources/test_truncate.txt"); FileChannel channel = fout.getChannel(); ByteBuffer buff = ByteBuffer.wrap(input.getBytes()); channel.write(buff); buff.flip(); channel = channel.truncate(5); assertEquals(5, channel.size()); fout.close(); channel.close(); } 

8. Принудително актуализиране на файлове в хранилището

Операционната система може да кешира промени в файлове от съображения за производителност и данните могат да бъдат загубени, ако системата се срине. За да принудим съдържанието на файла и метаданните да записват непрекъснато на диск, можем да използваме метода на сила :

channel.force(true);

Този метод е гарантиран само когато файлът се намира на локално устройство.

9. Заредете секция от файл в паметта

Let's see how to load a section of a file in memory using FileChannel.map. We use FileChannel.MapMode.READ_ONLY to open the file in read-only mode:

@Test public void givenFile_whenReadAFileSectionIntoMemoryWithFileChannel_thenCorrect() throws IOException { try (RandomAccessFile reader = new RandomAccessFile("src/test/resources/test_read.in", "r"); FileChannel channel = reader.getChannel(); ByteArrayOutputStream out = new ByteArrayOutputStream()) { MappedByteBuffer buff = channel.map(FileChannel.MapMode.READ_ONLY, 6, 5); if(buff.hasRemaining()) { byte[] data = new byte[buff.remaining()]; buff.get(data); assertEquals("world", new String(data, StandardCharsets.UTF_8)); } } }

Similarly, we can use FileChannel.MapMode.READ_WRITE to open the file into both read and write mode.

We can also useFileChannel.MapMode.PRIVATE mode, where changes do not apply to the original file.

10. Lock a Section of a File

Let's understand how to lock a section of a file to prevent concurrent access of a section using the FileChannel.tryLock method:

@Test public void givenFile_whenWriteAFileUsingLockAFileSectionWithFileChannel_thenCorrect() throws IOException { try (RandomAccessFile reader = new RandomAccessFile("src/test/resources/test_read.in", "rw"); FileChannel channel = reader.getChannel(); FileLock fileLock = channel.tryLock(6, 5, Boolean.FALSE )){ //do other operations... assertNotNull(fileLock); } }

The tryLock method attempts to acquire a lock on the file section. If the requested file section is already blocked by another thread, it throws an OverlappingFileLockException exception. This method also takes a boolean parameter to request either a shared lock or an exclusive lock.

We should note that some operating systems may not allow a shared lock, defaulting instead to an exclusive lock.

11. Closing a FileChannel

Finally, when we are done using a FileChannel, we must close it. In our examples we have used try-with-resources.

If necessary, we can close the FileChannel directly with the close method:

channel.close();

12. Conclusion

В този урок видяхме как да използваме FileChannel за четене и писане на файлове . Освен това проучихме как да четем и променяме размера на файла и текущото му местоположение за четене / запис и разгледахме как да използваме FileChannels в едновременни приложения или приложения, критични за данни.

Както винаги, изходният код за примерите е достъпен в GitHub.