I/O简介
1、I/O 输入或者输出指的是计算机与外部世界或者一个程序与计算机的其余部分之间的接口,它对于任何计算机系统都非常重要,因面所有的I/O的主体实际上是内置在操作系统中的,单独的程序一般是让系统为它们完成大部分的工作。
2、在Java编程中,直到JDK1.4之前一直使用的是流的方式来完成I/O,所的有I/O都被看作是单个字节的移动,通过一个称为Stream的对象一次移动一个字节。流I/O用于与外部世界。它也在内部使用,用于将对象转换为字节,然后再转换回对象。
为什么要使用NIO
NIO的特性
Buffer
是一个对象, 它包含一些要写入或者刚读出的数据。 在 NIO 中加入
Buffer
对象,体现了新库与原 I/O 的一个重要区别。在面向流的 I/O 中,您将数据直接写入或者将数据直接读到
Stream
对象中。
在 NIO 库中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的。在写入数据时,它是写入到缓冲区中的。任何时候访问 NIO 中的数据,您都是将它放到缓冲区中。
缓冲区实质上是一个数组。通常它是一个字节数组,但是也可以使用其他种类的数组。但是一个缓冲区不
仅仅
是一个数组。缓冲区提供了对数据的结构化访问,而且还可以跟踪系统的读/写进程。
ByteBuffer
。一个 ByteBuffer
可以在其底层字节数组上进行 get/set 操作(即字节的获取和设置)。
ByteBuffer
不是 NIO 中唯一的缓冲区类型。事实上,对于每一种基本 Java 类型都有一种缓冲区类型:
-
ByteBuffer
-
CharBuffer
-
ShortBuffer
-
IntBuffer
-
LongBuffer
-
FloatBuffer
-
DoubleBuffer
Buffer
类都是 Buffer
接口的一个实例。 除了 ByteBuffer
,每一个 Buffer 类都有完全一样的操作,只是它们所处理的数据类型不一样。因为大多数标准 I/O 操作都使用 ByteBuffer
,所以它具有所有共享的缓冲区操作以及一些特有的操作。package com.io.newio;
import java.nio.FloatBuffer;
public class UseFloatBuffer {
public static void main(String[] args) {
/*allocation()方法表示初始化缓冲区,注意在ByteBuffer中,allocate和allocateDirect都可以初始化缓冲区,它们的区别如下:
* allocate和allocateDirect方法都做了相同的工作,不同的是allocateDirect方法直接使用操作系统来分配Buffer。因而它将提供更快的访问速度。
* 不幸的是,并非所有的虚拟机都支持这种直接分配的方法。Sun推荐将以字节为单位的直接型缓冲区allocateDirect用于与大型文件相关并具有较长生命周期的缓冲区。
* */
FloatBuffer buffer=FloatBuffer.allocate(10);
for(int i=0;i<buffer.capacity();i++){
float f=(float)Math.sin((((float)i)/10)*(2*Math.PI));
buffer.put(f);
}
/*在读取缓冲区数据之前,必须调用flip方法。具体的原因如下:在对缓冲区读,写的时候,会共用Buffer的position,limit,capacity属性,
* 而读写中,position和limit的意思是不一样的。position和limit的意思总结起来如下:position:缓冲区中的一个可以读写的位置,limit:缓冲区中的一个不可以读写的位置。
* 在完成缓冲区的读写操作以后,如果想复用缓冲区,那么应该有一个clear操作。
* */
buffer.flip();
buffer.mark();// 注意mark和reset方法的配合使用
while(buffer.hasRemaining()){
System.out.println(buffer.get());
}
System.out.println("*********************");
buffer.reset();
float [] fs=new float[buffer.limit()];
buffer.get(fs); // 注意这里的get方法会有移位操作,也就是改变position的值
for(float f:fs){
System.out.println(f);
}
}
}
缓冲区(Buffer)的内部细节
get()
来完成的。同样,如果要将原始数据放入缓冲区中,就要使用访问方法 put()
。
-
byte get();
-
ByteBuffer get( byte dst[] );
-
ByteBuffer get( byte dst[], int offset, int length );
-
byte get( int index );
ByteBuffer
类中有五个 put()
方法:
-
ByteBuffer put( byte b );
-
ByteBuffer put( byte src[] );
-
ByteBuffer put( byte src[], int offset, int length );
-
ByteBuffer put( ByteBuffer src );
-
ByteBuffer put( int index, byte b );
package com.io.newio;
import java.nio.ByteBuffer;
/*put方法与get方法的使用*/
public class TypesInByteBuffer {
<span style="white-space:pre"> </span>public static void main(String[] args) {
<span style="white-space:pre"> </span>ByteBuffer buffer = ByteBuffer.allocate(64);
<span style="white-space:pre"> </span>buffer.putInt(30);
<span style="white-space:pre"> </span>buffer.putLong(70000000000L);
<span style="white-space:pre"> </span>buffer.putDouble(Math.PI);
<span style="white-space:pre"> </span>buffer.flip();
<span style="white-space:pre"> </span>System.out.println(buffer.getInt());// 根据放入的顺序来取
<span style="white-space:pre"> </span>System.out.println(buffer.getLong());
<span style="white-space:pre"> </span>System.out.println(buffer.getDouble());
<span style="white-space:pre"> </span>System.out.println("************");
<span style="white-space:pre"> </span>buffer.clear();
<span style="white-space:pre"> </span>buffer.putInt(10);
<span style="white-space:pre"> </span>buffer.putInt(20);
<span style="white-space:pre"> </span>buffer.putInt(30);
<span style="white-space:pre"> </span>buffer.flip();
<span style="white-space:pre"> </span>// 如下三行输出代码,注意使用的index值,请读者自己考虑原因。(ps:1 int = 4 byte)
<span style="white-space:pre"> </span>System.out.println(buffer.getInt(4));
<span style="white-space:pre"> </span>System.out.println(buffer.getInt(0));
<span style="white-space:pre"> </span>System.out.println(buffer.getInt(8));
<span style="white-space:pre"> </span>System.out.println("***********************");
<span style="white-space:pre"> </span>// 上面的getInt(int index)方法并不影响position和limit,所以下面的语句可以奏效
<span style="white-space:pre"> </span>System.out.println(buffer.getInt());
<span style="white-space:pre"> </span>System.out.println(buffer.getInt());
<span style="white-space:pre"> </span>System.out.println(buffer.getInt());
<span style="white-space:pre"> </span>}
}
只读缓冲区非常简单 ― 您可以读取它们,但是不能向它们写入。可以通过调用缓冲区的 asReadOnlyBuffer()
方法,将任何常规缓冲区转换为只读缓冲区,这个方法返回一个与原缓冲区完全相同的缓冲区(并与其共享数据),只不过它是只读的。只读缓冲区对于保护数据很有用。在将缓冲区传递给某个对象的方法时,您无法知道这个方法是否会修改缓冲区中的数据。创建一个只读的缓冲区可以 保证 该缓冲区不会被修改。不能将只读的缓冲区转换为可写的缓冲区。
另一种有用的 ByteBuffer
是直接缓冲区。 直接缓冲区 是为加快 I/O 速度,而以一种特殊的方式分配其内存的缓冲区。实际上,直接缓冲区的准确定义是与实现相关的。Sun 的文档是这样描述直接缓冲区的:给定一个直接字节缓冲区,Java 虚拟机将尽最大努力直接对它执行本机 I/O 操作。也就是说,它会在每一次调用底层操作系统的本机 I/O 操作之前(或之后),尝试避免将缓冲区的内容拷贝到一个中间缓冲区中(或者从一个中间缓冲区中拷贝数据)。还可以用内存映射文件创建直接缓冲区。
内存映射文件 I/O 是一种读和写文件数据的方法,它可以比常规的基于流或者基于通道的 I/O 快得多。内存映射文件 I/O 是通过使文件中的数据神奇般地出现为内存数组的内容来完成的。这其初听起来似乎不过就是将整个文件读到内存中,但是事实上并不是这样。一般来说,只有文件中实际读取或者写入的部分才会送入(或者 映射 )到内存中。内存映射并不真的神奇或者多么不寻常。现代操作系统一般根据需要将文件的部分映射为内存的部分,从而实现文件系统。Java 内存映射机制不过是在底层操作系统中可以采用这种机制时,提供了对该机制的访问。尽管创建内存映射文件相当简单,但是向它写入可能是危险的。仅只是改变数组的单个元素这样的简单操作,就可能会直接修改磁盘上的文件。修改数据与将数据保存到磁盘是没有分开的。在下面的例子中,我们要将一个 FileChannel
(它的全部或者部分)映射到内存中。为此我们将使用 FileChannel.map()
方法。下面代码行将文件的前 1024 个字节映射到内存中:map()
方法返回一个 MappedByteBuffer
,它是 ByteBuffer
的子类。因此,您可以像使用其他任何 ByteBuffer
一样使用新映射的缓冲区,操作系统会在需要时负责执行行映射。
package com.io.newio;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
public class UseMappedFile {
static private final int start=0;
static private final int size=1024;
public static void main(String[] args) throws Exception{
RandomAccessFile raf=new RandomAccessFile("user.txt", "rw");
FileChannel fc=raf.getChannel();
MappedByteBuffer mbb=fc.map(FileChannel.MapMode.READ_WRITE, start, size);
mbb.put(0,(byte)97);
mbb.put(1023,(byte)122);
System.out.println(raf.length());
raf.close();
}
}