新 I/O--文件I/O
JDK 1.4的java.nio.*包中引入了新的IO类库,其目的在于提高速度。实际上,旧的IO包已经使用nio重新实现过,以便充分利用这种速度提高。
速度提高源自于所使用的结构更接近于操作系统执行IO的方式:通道和缓冲器.我们可以把它想象成一个煤矿,通道是一个包含煤层(数据)的矿藏,而缓冲器则是派送到矿藏的卡车。卡车载满煤炭而归,我们再从卡车上获得煤炭。也就是说,我们并没有直接和通道交互,我们只是和缓冲器交互,并把缓冲器派送到通道。要么从缓冲器获得数据,要么向缓冲器发送数据。
唯一直接与通道交互的缓冲器是ByteBuffer——可以存储未加工字节的缓冲器。java.nio.ByteBuffer是相当基础的类:通过告知分配多少存储空间来创建一个ByteBuffer对象,并且还有一个方法选择集,用于以原始的字节形式或基本数据类型输出和读取数据。但是,没办法输出或读取对象,即使是字符串对象也不行。这种处理虽然很低级,但却正好,因为这是大多数操作系统中更有效的映射方式。
旧IO类库有三个类被修改了,用以产生FileChannel。这三个被修改类是FileInputStream、FileOutputStream以及用于既读又写的RandomAccessFile。这些都是字节操作流,与底层nio性质一致。Reader和Writer这些字符模式类不能用于产生通道;但是java.nio.channels.Channels类提供了适用方法,用于在通道中产生Reader和Writer。
//演示上面三种类型的流,用于产生可写的,可读可写的及可读的通道
package com.wise.tiger;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class GetChannel {
private static final int BUFF_SIZE = 1024;
public static void main(String[] args) throws IOException {
// 写文件
FileChannel fc = new FileOutputStream("data.txt").getChannel();
/*
* 使用warp()方法将已存在的字节数组包装到ByteBuffer中 也可以使用put方法直接进行填充
*/
fc.write(ByteBuffer.wrap("I'm Peppa Pig.".getBytes()));
fc.close();
// 文件末尾添加内容
fc = new RandomAccessFile("data.txt", "rw").getChannel();
fc.position(fc.size());// 移动到文件末尾
fc.write(ByteBuffer.wrap("This is my little brother, George".getBytes()));
fc.close();
// 读文件
fc = new FileInputStream("data.txt").getChannel();
/*
* 分配ByteBuffer,对于只读访问,必须显式地使用静态的allocate()方法来分配ByteBuffer
* nio的目标就是快速移动大量数据,因此ByteBuffer的大小就显得尤为重要(必须通过实际运行程序找到最佳尺寸)
*/
ByteBuffer buff = ByteBuffer.allocate(BUFF_SIZE);
/*
* 一旦调用read()来告知FileChannel向ByteBuffer存储字节,就必须调用缓冲器上的flip(),让它做好让别人读取字节的准备。
* 如果打算使用缓冲器执行进一步read()操作,也必须得使用clear()来为每个read()做好准备,如下copy文件
*/
fc.read(buff);
buff.flip();
while (buff.hasRemaining()) {
System.out.print((char) buff.get());
}
fc.close();
}
}
这里展示的任何流类,getChannel()将会产生一个FileChanel。通道是一个相当基础的东西:可以向它传送用于读写的ByteBuffer,并且可以锁定文件的某些区域用于独占式访问。
public class ChannelCopy {
private static final int CAPACITY = 1024;
public static void main(String[] args) throws IOException {
if(args.length != 2)System.exit(1);
FileChannel
in = new FileInputStream(args[0]).getChannel(),
out = new FileOutputStream(args[1]).getChannel();
ByteBuffer buff = java.nio.ByteBuffer.allocate(CAPACITY);
while(in.read(buff) != -1) {
buff.flip();
out.write(buff);
buff.clear();
}
out.close();
in.close();
}
}
然而,这个程序并不是处理此类操作的理想方式,特殊方法transferTo()和transferFrom()则允许我们将一个通道和另一个通道直接相连
public class ChannelCopy {
private static final int CAPACITY = 1024;
public static void main(String[] args) throws IOException {
if(args.length != 2)System.exit(1);
FileChannel
in = new FileInputStream(args[0]).getChannel(),
out = new FileOutputStream(args[1]).getChannel();
in.transferTo(0,in.size(),out);//or out.transferFrom(in,0,in.size);
out.close();
in.close();
}
}
转换数据
在GetChannel.java中,必须每次只读取一个字节的数据,然后将每个byte类型强制转换成char类型。而java.nio.CharBuffer有一个toString方法:返回一个包含缓冲器中所有字符的字符串。ByteBuffer可以看做是具有asCharBuffer()方法的CharBuffer。
public class BufferToText {
private static final int CAPACITY = 1024;
public static void main(String[] args) {
try{
var fc = new FileOutputStream("data.txt").getChannel();
fc.write(ByteBuffer.wrap("来来,我是一个香蕉。。".getBytes()));
fc.close();
fc = new FileInputStream("data.txt").getChannel();
var buff = ByteBuffer.allocate(CAPACITY);
fc.read(buff);
buff.flip();
System.out.println(buff.asCharBuffer());
buff.rewind();//返回到数据开始部分
var encoding = System.getProperty("file.encoding");//获取默认字符集
System.out.println("使用" + encoding + "解码结果: " + Charset.forName(encoding).decode(buff));
fc = new FileOutputStream("data.txt").getChannel();
fc.write(ByteBuffer.wrap("来来,我是一个榴莲".getBytes("UTF-8")));
fc.close();
fc = new FileInputStream("data.txt").getChannel();
buff.clear();
fc.read(buff);
buff.flip();
System.out.println(buff.asCharBuffer());
fc = new FileOutputStream("data.txt").getChannel();
buff = ByteBuffer.allocate(24);
buff.asCharBuffer().put("如花貌美容颜");
fc.write(buff);
fc.close();
fc = new FileInputStream("data.txt").getChannel();
buff.clear();
fc.read(buff);
buff.flip();
System.out.println(buff.asCharBuffer());
}catch (IOException e) {
e.printStackTrace();
}
}
}
缓冲器容纳的是普通的字节,为了把它们转换成字符,我们要么在输入它们的时候对其进行编码,要么在将其从缓冲器输出时对他们进行解码(可以使用java.nio.charset.Charset类实现这些功能).
获取基本类型
尽管ByteBuffer只能保存字节类型数据,但是它可以从其所容纳的字节中产生出各种不同的基本类型值的方法
- CharBuffer ====> asCharBuffer();
-
ShorBuffer ====> asShortBuffer();
-
IntBuffer ====> asIntBuffer();
-
LongBuffer ====> asLongBuffer();
-
FloatBuffer ====> asFloatBuffer();
-
DoubleBuffer ====> asDoubleBuffer();
这些Buffer覆盖了你能通过IO发送的基本数据类型:byte, short, int, long, float, double 和 char。
注意:使用shortBuffer的put()方法时,需要进行类型转换。
视图缓冲器
视图缓冲器(view buffer)可以让我们通过某个特定的基本数据类型的视窗查看其底层的ByteBuffer。ByteBuffer依然是实际存储数据的地方,“支持”着前面的视图,因此对视图的任何修改都会映射成为对ByteBuffer中数据的修改。
一旦底层的ByteBuffer通过视图缓冲器填满了整数或其他基本类型时,就可以直接被写到通道中。正像从通道中读取那样容易,然后使用视图缓冲器可以把任何数据都转化为某一特定的基本类型。
用缓冲器操作数据
下图阐明了nio类之间的关系,便于我们理解怎么移动和转换数据。
如果想把一个字节数组写到文件中去,那么就应该使用ByteBuffer.wrap()方法把字节数组包装起来,然后用getChannel()方法在FileOutputStream上打开一个通道,接着将来自于ByteBuffer的数据写到FileChannel。
注意:BytBuffer是将数据移进移出通道的唯一方式,并且只能创建一个独立的基本类型缓冲器,或者使用as方法从ByteBuffer中获得。也就是说,不能把基本类型的缓冲器转换成ByteBuffer。然而,我们可以经由视图缓冲器将基本类型数据移进移出Bytebuffer,所以也就不是什么真正的限制了。
缓冲器细节
Buffer有数据和可以高效地访问及操作这些数据的四个索引组成,mark(标记)、position(位置)、limit(界限)和capacity(容量)。下面是用于设置和复位索引以及查询它们的值的方法。
capacity() |
返回缓冲区容量 |
clear() |
清空缓存区,将position设置为0,limit设置为容量。可以调用此方法覆写缓冲区 |
flip() |
将limit设置为position,position置位0.此方法用于准备从缓冲区读取已经写入的数据 |
limit() |
返回limit的值 |
limit(int lim) |
返回limit的值 |
mark() |
将mark设置为position |
position() |
返回position值 |
position(int pos) |
返回position值 |
remaining() |
返回(limit - position) |
hasRemaining() |
是否有介于position和limit之间的元素 |
在缓冲器中插入和提取数据的方法会更新这些索引,用于反应所发生的变化。
内存映射文件
内存映射文件允许创建和修改因为太大而不能放入内存的文件。可以假定整个文件都放在内存中,而且可以完全把它当作非常大的数组访问。
public class LargeMappedFiles {
static int length = 0x8FFFFFF; // 128 MB
public static void main(String[] args) throws Exception {
MappedByteBuffer out =
new RandomAccessFile("test.dat", "rw").getChannel()
.map(FileChannel.MapMode.READ_WRITE, 0, length);
for (int i = 0; i < length; i++) {
out.put((byte) 'x');
}
print("Finished writing");
for (int i = length / 2; i < length / 2 + 6; i++) {
printnb((char) out.get(i));
}
}
}
使用map()产生MappedByteBuffer,一个特殊类型的直接缓冲器,注意:必须指定映射文件初始位置和映射区域长度,意味着可以映射某个大文件的较小部分。
MappedByteBuffer由ByteBuffer继承而来,因此它具有ByteBuffer的所有方法。
前面程序创建的文件为128MB,这可能比操作系统所允许一次载入内存的空间大。但似乎我们可以一次访问到整个文件。因为只有一部分文件放入了内存,其他被交换了出去。用这种方式,很大的文件(可达2GB)也可以很容易地修改。注意底层操作系统的文件映射工具是用来最大化地提高性能的。
映射文件的所有输出必须使用RandomAccessFile。
文件加锁
JDK 1.4引入了文件加锁机制,允许同步访问某个作为共享资源的文件。文件锁对其他的操作系统进程是可见的,因为Java的文件加锁直接映射到本地操作系统的加锁工具。
public class FileLocking {
public static void main(String[] args) throws Exception {
FileOutputStream fos= new FileOutputStream("file.txt");
FileLock fl = fos.getChannel().tryLock();
if(fl != null) {
System.out.println("Locked File");
TimeUnit.MILLISECONDS.sleep(100);
fl.release();
System.out.println("Released Lock");
}
fos.close();
}
} /*
Locked File
Released Lock
*/
通过对FileChannel
调用tryLock()或lock(),就可以获得整个文件的FileLock。(SocketChannel、DatagramChannel和ServerSocketChannel不需要加锁,因为它们是从单进程实体继承而来,通常不在两个进程之间共享socket。)tryLock()是非阻塞式的,它设法获取锁,如果不能得到(当其他一些进程已经持有相同的锁,并且不共享时),它将直接从方法调用返回。lock()是阻塞式的,它要阻塞进程直至锁可以获得,或调用lock()的线程中断,或调用lock()的通道关闭。使用FileLock.release()可以释放锁。
也可以使用如下方法对文件的一部分上锁:
tryLock(long position, long size, boolean shared)
或者
lock(long position, long size, boolean shared)
其中加锁区域由size-position决定,第三个参数指定是否共享锁。
尽管无参的加锁方法将根据文件尺寸变化而变化,但是具有固定尺寸的锁不随文件尺寸变化而变化。如果你获得了某一区域(从position到position+size)上的锁,当文件增大超出position+size时,那么在position+size之外的部分不会被锁定。无参数的加锁方法会对整个文件进行加锁,甚至文件变大后也是如此。
对于独占锁或者共享锁的支持必须有底层的操作系统提供。如操作系统不支持共享锁并未每一个请求都创建锁,那么它就会使用独占锁。锁的类型可以通过FileLock.isShared()进行查询。
对映射文件的部分加锁
文件映射通常应用于极大的文件。我们可能需要对这种巨大的文件进行部分加锁,以便其他进程可以修改文件中未被加锁的部分。例如,数据库就是这样,因此多个用户可以同时访问到它。
public class LockingMappedFIles {
static final int LENGTH = 0x8FFFFFF;//128M
static FileChannel fc;
public static void main(String[] args) throws IOException {
fc = new RandomAccessFile("test.dat", "rw").getChannel();
MappedByteBuffer out = fc.map(FileChannel.MapMode.READ_WRITE, 0, LENGTH);
for(int i = 0; i < LENGTH; i++)out.put((byte)'x');
new LockAndModify(out, 0, 0 + LENGTH / 3);
new LockAndModify(out, LENGTH / 2, LENGTH / 2 + LENGTH / 4);
}
private static class LockAndModify extends Thread{
private ByteBuffer buff;
private int start,end;
public LockAndModify(ByteBuffer buff, int start, int end) {
this.start = start;
this.end = end;
buff.limit(end);
buff.position(start);
this.buff = buff.slice();
}
@Override
public void run() {
try {
FileLock lock = fc.lock(start,end,false);
System.out.println("Locked:" + start + "to" + end);
while(buff.position() < buff.limit() - 1)
buff.put((byte)(buff.get() + 1));
lock.release();
System.out.println("Released:" + start + "to" + end);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
线程类LockAndModify创建了缓冲区和利于修改的slice(),然后再run中,获得文件通道上的锁(不能获得缓冲器上的锁,只能获得通道上的)。lock()类似于获得一个对象的线程锁----现在处在临界区,即对该部分文件具有独占访问权。
如果有java虚拟机,它会自动释放锁,或者关闭加锁的通道。不过也可以像程序中那样,显式地为FileLock对象调用release()来释放。
资料来源:《Thinking in Java》 fourth Edition
- 大小: 34.1 KB
分享到:
相关推荐
WAGO-I/O-SYSTEM 750系列和WAGO-I/O-PRO 32是WAGO公司生产的一系列高性能的工业控制器和PLC产品。WAGO-I/O-SYSTEM 759系列控制器基于IEC 61131-3标准,支持CODESYS开发环境,是用于自动化控制任务的工业计算机。 ...
本篇文章将探讨BIOS的入门知识,特别是与I/O(输入输出)相关的部分,并提供相关的参考代码实现。本文会涉及到C语言编程、CMOS配置以及SupperIO技术。 首先,我们来理解一下C语言在BIOS编程中的作用。C语言是一种...
WAGO公司作为知名的电气连接和自动化解决方案提供商,其750系列远程I/O模块凭借可靠性和灵活性赢得了广泛赞誉。为了帮助用户更好地管理和调试这些设备,WAGO推出了“WAGO_IOCheck3_Setup”在线调试工具。这个工具...
深入探究文件 I/O:深入了解了文件 I/O 中的一些细节,譬如文件的管理方式、 错误返回的处理、空洞文件、O_APPEND 和 O_TRUNC 标志、原子操作与竞争冒险等等
文件I/O操作是API的一个重要部分,包括打开文件(open)、读取文件(read)、写入文件(write)、改变文件位置(lseek)以及关闭文件(close)等功能。这些函数允许程序与文件系统进行交互,完成数据的读写。 文件描述符是...
在Zabbix Agent的配置文件(通常是`/etc/zabbix/zabbix_agentd.conf`)中,我们需要确保`EnableRemoteCommands`参数设置为`1`,这样Agent才能执行远程命令以获取磁盘I/O信息。 接下来,我们需要在Zabbix Server端...
在Java编程环境中,I/O(输入/输出)操作是程序与外部世界交互的关键部分,包括读取文件、网络通信等。对于高效的系统设计,理解并掌握阻塞I/O和非阻塞I/O是非常重要的。这两种I/O模型在处理数据传输时有着显著的...
Unix I/O系统是操作系统的核心部分,它提供了对文件和设备进行输入输出操作的接口。本文主要总结了Unix下的I/O模型及其相关系统调用。 在Unix中,每个进程都有一个打开文件描述符表,用于存储指向文件inode的指针和...
内存映射I/O(Memory-Mapped I/O)是一种在操作系统中高效处理大文件的技术,它允许应用程序将文件的内容直接映射到进程的虚拟地址空间,从而实现文件与内存的直接交互,无需通过传统的I/O系统调用。这种方法在处理...
在Java编程中,I/O(Input/Output)处理是与外部世界交互的关键技术,涉及文件读写、网络通信等场景。I/O的核心思想是通过流(Stream)来传输数据,使得程序能从数据源读取数据或将数据写入目标。 **1.1 I/O简介** I...
Redis 安装遇到的问题——Linux Centos7.5 Redis 是一个开源的、基于内存的数据结构存储系统,常用于数据库、缓存...同时,需要分别进入 redis-stable 下的 deps 下的 hiredis、lua 运行 make,以避免文件缺失的错误。
- SIMATIC ET 200SP 系列是西门子推出的分布式I/O系统,该系统用于工业自动化领域,旨在提高系统的灵活性和扩展性。 - AI 4xU/I 2-wire ST 是该系列中的模拟量输入模块,型号为6ES7134-6HD00-0BA1。该模块特别适用...
在嵌入式Linux系统中,文件I/O是操作系统与外部设备进行数据交换的重要途径。本文将深入探讨嵌入式Linux下的文件I/O机制,包括基本概念、API接口、以及实际应用示例。我们将通过分析提供的课件、源码和说明,帮助你...
"IO_Card_Dev______test"这个压缩包子文件的名称可能暗示了这是一个测试程序或者测试用例,用于验证I/O卡驱动的功能和性能。在驱动开发过程中,这样的测试文件至关重要,因为它可以帮助开发者确保驱动能够正确地与...
例如,一个服务器可能有多个线程,每个线程都处于就绪状态,随时准备处理新的I/O请求。当某个线程发起异步I/O操作后,它可以处理其他任务,而当I/O完成时,操作系统会调度合适的线程来处理结果。 值得注意的是,...
根据提供的文件内容,可以看出SHENZHEN I/O是一款集成了电子电路设计、编程模拟和经营策略元素的电脑游戏。以下是从手册内容中提取的详细知识点: 1. SHENZHEN I/O游戏手册的使用建议: 手册建议玩家为了获得最佳的...
在Windows操作系统中,进行网络编程时,我们常常会遇到多种Socket I/O模型。这些模型决定了如何处理输入/输出操作,从而影响程序的性能和效率。本文将深入探讨六种主要的Socket I/O模型,并通过实例解析它们的工作...
该作业文件定义了测试的具体参数和设置,如目标文件/设备、I/O类型、块大小等。 #### fio的工作原理 fio通过提交I/O请求来模拟真实的读写操作,并收集有关这些操作的性能数据。它能够同时处理多个线程和进程,从而...