当前位置: 首页 > news >正文

java 新 IO 的运用

JDK1.4开始,加入了java.nio.*包,在这个包中加入了新的JAVA I/O类库,以便加快I/O操作的速度。在nio中读写之所以提高,只要是采用了更接近操作系统执行I/O操作的结构——通道和缓冲区。在《Thinking in Java》中有举了一个例子来说明通道和缓冲区的作用。

可以想象一个煤矿,通道时一个包含煤层(数据)的矿藏,而缓冲区则是派送到矿藏的卡车。卡车载满煤炭而归,而我们再从卡车上获得煤炭。也就就说,我们并没有与通道直接交互,我们只是和缓冲区交互,并把缓冲区派送到通道。通道要么从缓冲区获得数据,要么像缓冲区发送数据。

唯一直接与通道交互的缓冲区是ByteBuffer,从名字上就可以看出,这是一个可以存储字节的存储器。下面是ByteBuffer的部分JDK源码(重要的方法)

复制代码
public abstract class ByteBuffer  extends Bufferimplements Comparable<ByteBuffer>

/**
* Allocates a new byte buffer.
*
* <p> The new buffer's position will be zero, its limit will be its
* capacity, and its mark will be undefined. It will have a {@link #array
* </code>backing array<code>}, and its {@link #arrayOffset </code>array
* offset<code>} will be zero.
*
* @param capacity
* The new buffer's capacity, in bytes
*
* @return The new byte buffer
*
* @throws IllegalArgumentException
* If the <tt>capacity</tt> is a negative integer
*/
public static ByteBuffer allocate(int capacity) {
if (capacity < 0)
throw new IllegalArgumentException();
return new HeapByteBuffer(capacity, capacity);
}

/**

* 静态方法,得到原始字节形式的缓冲区。。

*/

public static ByteBuffer wrap(byte[] array) {
return wrap(array, 0, array.length);
}

/**

* 下面这些方法用于得到其他基本类型的缓冲区

*/

public abstract ShortBuffer asShortBuffer();

public abstract CharBuffer asCharBuffer();

public abstract IntBuffer asIntBuffer();

public abstract long getLong();

public abstract DoubleBuffer asDoubleBuffer();

public abstract FloatBuffer asFloatBuffer();

复制代码

从上面的代码片段中,我们可以看到,这个类其实一个很基础的类:通过告知分配多少存储空间来创建一个BuyeBuffer对象,并且有一些方法,用于产生成其他基本类型的缓冲区,从而以字节或者其他基本类型方式读取数据或者输出数据,但是,要注意的是,没有办法输出或者读取对象(即使是String字符串对象也不行,但是可以通过String.getBytes()转换为字节类型处理),这样的处理方式虽然比较低级,但是,这正是大多数操作系统中更有效的映射方式。

旧有的I/O类库中有三个类被修改,用以产生FileChannel。这三个被修改的类是FileInputStream,FileOutStream,RandomAccessFile。

下面是一个简单的文件复制程序

复制代码
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;


public class ChannelCopy {
    public static final int BSIZE = 1024;

    /**
     * @param args
     * @throws Exception 
     */
    public static void main(String[] args) throws Exception {
        String arg1 = "E:/test/in.txt";
        String arg2 = "E:/test/out.txt";
        
            FileChannel inChannel = new FileInputStream(new File(arg1)).getChannel();
            FileChannel outChannel = new FileOutputStream(new File(arg2),false).getChannel();//false:覆盖写,true:追加写
            ByteBuffer buffer = ByteBuffer.allocate(BSIZE);
            
            while(inChannel.read(buffer)!=-1) {
                buffer.flip();//一定要加上 ,read操作后需要次方法
                outChannel.write(buffer);
                buffer.clear();/一定要加上,如果read一次后,还需要对缓冲区进一步read,那么也必须要加上次方法
            }
        System.out.println("Sucess!");    
        
    }

}
复制代码

这个程序很简单,就不要做过多的介绍,但是需要注意的是,是最后几行代码。

1)一旦调用read来告知FileChannel向ByteBuffer存储字节,那就必须调用缓冲区的flip方法,让别人做好读取字节的准备,在上述程序中,如果没有flip()方法,那么就无法把内容写到目标文件中(其实写了,但是内容是空白)。为什么会出现这种情况呢,下面是加上flip()方法和没有加上flip()方法ByteBuffer对象的状态。

java.nio.HeapByteBuffer[pos=0 lim=21 cap=1024] (加上flip()方法)

java.nio.HeapByteBuffer[pos=21 lim=1024 cap=1024](没有加上flip()方法)

我们可以很清楚的看到,如果不加上flip方法,那么读取的内容其实是缓冲区当前位置往后的内容(之后的内容当然是空白了,缓冲区还没数据写到那呢。),而真正的内容应该是当前位置之前的内容。 个人觉得read操作后,必须就跟上flip()方法。

2)write()操作后,信息仍然在缓冲区中,接着clear()操作会重新设置缓冲区内部的指针,以便缓冲区在另一个read()操作期间能够做好接受数据的准备。换句话说,如果我们打算使用缓冲区执行进一步的read()操作,我们也必须得调用clear()方法来为每个read()做好准备。为什么?我代码没有跟下去,如果没有clear()方法,inChnanel.read(buffer)会一直等于0,导致了死循环,有高人解释下为什么?

ByteBuffer缓冲区的细节

1)ByteBuffer是唯一能将数据写入或读出的方式,我们只能使用通过创建一个独立的基本类型缓冲器,或者使用“as”方法从ByteBuffer中获得。也就说,我们不能

把基本类型的缓冲器转换成ByteBuffer(这个其实看源码就知道了,他们直接表示继承关系,不能强制转化)

2)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() return position < limit;
reset() 把postion设置为mark

内存映射文件

内存映射文件允许我们创建和修改那些因为太大而不能放入内存的文件,有了内存映射文件,我们就可以假定整个文件都在内存中,而且完全可以把他们当做非常大的数组来访问。

我们可以看到ByteBuffer是一个abstract class.其有两个子类,一个是HeapByteBuffer,我们之前将的nio的操作,就是通过其来实现的。另一个就是MappedByteBuffer这个就是我们内存映射文件需要用到的。我们看到在FileChannel有一个map方法,如下所示,该方法就可以得到相应的MappedByteBuffer

* @see java.nio.channels.FileChannel.MapMode
     * @see java.nio.MappedByteBuffer
     */
    public abstract MappedByteBuffer map(MapMode mode,
                     long position, long size)
        throws IOException;   //mode = FileChannel.MapMode.READ_WRITE等等,position = 映射文件的起始位置,size = 映射区域大小

注意:“映射读"可以通过FileInpuStream.getChannel().map()来处理,但是”映射写(读写)“ 必须得用RandomAccesssFile().getChannel().map(),而不能通过FileOutputStream得到缓冲器。

补充内容:

不同的机器可能会使用不同的字节排序方法来存储数据,"big endian"(高位优先)将最重要的字节地址存放地址最低的存储器单元(最低地址存放高位字节),"big endian"存放方式正是我们的书写方式,大数先写(比如,总是按照千、百、十、个位来书写数字)。而“little endian”(低位优先)则是将最重要的字节放在地址最高的存储单元(最低地址存放低位字节)。当存储量大雨一个字节是(如short,int,float)就要考虑字节的顺序问题了。ByteBuffer是以高位优先的形式存储数据的,但是可以通过order()方法来改变顺序。参数是ByteOrder.BIG_ENDIAN ,ByteOrder.LITTLE_ENDIAN。

看下面的例子

0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 1

如果我们以short方式读取数据

1)以默认的高位优先方式:则得到的数字时97(二进制为00000000 01100001)

2)低位优先的方式,则得到的数字是24832(二进制为0110000100000000)

相关文章:

  • 『开源』源码在线阅读工具
  • 视频编解码学习:理论基础
  • 每天一道算法_4_Hangover
  • 一初探js特效魅力之鼠标悬浮事件01
  • linux系统的组成——The composition of the Linux system
  • snprintf与printf
  • 一初探js特效魅力之div显示隐藏变色02
  • 帮豆包刷“天天爱消除”,“天天连萌”
  • 『开源』一个简单的 字符串计算 算法开源
  • 一初探js特效魅力之函数传参03
  • 每天一道算法_5_Financial Management
  • Android生命周期
  • 九度oj 1545奇怪的连通图
  • android中类似 QQ震动窗口的实现,带声音和振动效果
  • Linux下多任务间通信和同步-概述
  • 【399天】跃迁之路——程序员高效学习方法论探索系列(实验阶段156-2018.03.11)...
  • 【EOS】Cleos基础
  • CSS盒模型深入
  • GDB 调试 Mysql 实战(三)优先队列排序算法中的行记录长度统计是怎么来的(上)...
  • gf框架之分页模块(五) - 自定义分页
  • javascript 哈希表
  • Mysql优化
  • Web Storage相关
  • 盘点那些不知名却常用的 Git 操作
  • 使用 QuickBI 搭建酷炫可视化分析
  • 深度学习之轻量级神经网络在TWS蓝牙音频处理器上的部署
  • 湖北分布式智能数据采集方法有哪些?
  • ​iOS实时查看App运行日志
  • # 安徽锐锋科技IDMS系统简介
  • #LLM入门|Prompt#1.8_聊天机器人_Chatbot
  • #我与Java虚拟机的故事#连载15:完整阅读的第一本技术书籍
  • #我与虚拟机的故事#连载20:周志明虚拟机第 3 版:到底值不值得买?
  • (cos^2 X)的定积分,求积分 ∫sin^2(x) dx
  • (html5)在移动端input输入搜索项后 输入法下面为什么不想百度那样出现前往? 而我的出现的是换行...
  • (动手学习深度学习)第13章 计算机视觉---微调
  • (二) Windows 下 Sublime Text 3 安装离线插件 Anaconda
  • (附源码)spring boot火车票售卖系统 毕业设计 211004
  • (附源码)ssm本科教学合格评估管理系统 毕业设计 180916
  • (附源码)计算机毕业设计SSM在线影视购票系统
  • (切换多语言)vantUI+vue-i18n进行国际化配置及新增没有的语言包
  • (一)硬件制作--从零开始自制linux掌上电脑(F1C200S) <嵌入式项目>
  • .apk文件,IIS不支持下载解决
  • .net 前台table如何加一列下拉框_如何用Word编辑参考文献
  • .Net各种迷惑命名解释
  • .NET上SQLite的连接
  • @RequestBody与@ResponseBody的使用
  • [Angular 基础] - 自定义指令,深入学习 directive
  • [APIO2012] 派遣 dispatching
  • [BIZ] - 1.金融交易系统特点
  • [BUAA软工]第一次博客作业---阅读《构建之法》
  • [C#]winform部署PaddleOCRV3推理模型
  • [CTO札记]如何测试用户接受度?
  • [Django 0-1] Core.Checks 模块
  • [FFmpeg学习]从视频中获取图片
  • [HarekazeCTF2019]encode_and_encode 不会编程的崽