NIO
/*
*
- 从JDK1.4开始,Java提供了一些改进输入/输出处理的新功能,这些新功能被统称为新IO(New IO 简称NIO),新增了许多用于处理
- 输入/输出的类,这些类都被放在java.nio包以及子包下,并对原java.io中的很多类都以NIO为基础进行改写,新增了满足NIO的功能
Java NIO 由以下几个核心部分组成
- Channels:通道 Buffer:缓冲区 Selectors:选择器
- Channels和Buffer是新IO中的两个核心对象,Channel是对传统的输入/输出系统的模拟,在新IO系统中所有的数据都需要通过通道传输。
- Channels与传统的InputStream,OutputStrem最大的区别在于它提供了一个map()方法,通过该map()方法可以直接将"一块数据"映射到
- 内存中,如果说传统的输入/输出系统是面向流处理,则新IO则是面向块的处理
- Buffer可以被理解为一个容器(缓冲区,数组),发送到Channel中的所有对象都必须首先放到Buffer中,而从Channel中读取的数据也必须
- 放到Buffer中,也就是说数据可以从Channel读取到Buffer中,也可以从Buffer写到Channel中
使用Buffer
- Buffer就像一个数组,它可以保存多个类型相同的数据,Buffer是一个抽象类其最常用的类是ByteBuffer,它可以在底层字节数组上进行
- get/set操作,除了ByteBuffer之外,对应于其他基本数据类型(boolean除外)都有相应的Buffer类
NIO与IO的区别:
- 1.IO面向流,NIO面向缓冲区
-
Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。
-
此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区。
-
Java NIO的缓冲导向方法略有不同。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。
-
这就增加了处理过程中的灵活性
- 2.IO是阻塞式的,NIO有非阻塞式的
-
Java IO的各种流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,
-
或数据完全写入。该线程在此期间不能再干任何事情了。Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,
-
但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取,而不是保持线程阻塞,
-
所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此。
- 3.IO没有选择器,NIO有选择器
-
Java NIO的选择器允许一个单独的线程来监视多个输入通道,你可以注册多个通道使用一个选择器,
-
然后使用一个单独的线程来“选择”通道:这些通道里已经有可以处理的输入,或者选择已准备写入的通道。
-
这种选择机制,使得一个单独的线程很容易来管理多个通道
/
public class Demo1 {
public static void main(String[] args) {
/
* 使用Buffer读写数据一般遵循以下四个步骤:
1. 写入数据到Buffer
2. 调flip()方法
3. 从Buffer中读取数据
4. 调用clear()方法或者compact()方法
当向buffer写入数据时,buffer会记录下写了多少数据。一旦要读取数据,需要通过flip()方法将Buffer从写模式切换到
读模式。在读模式下,可以读取之前写入到buffer的所有数据。
一旦读完了所有的数据,就需要清空缓冲区,让它可以再次被写入。有两种方式能清空缓冲区:调用clear()或compact()方法。
clear()方法会清空整个缓冲区。compact()方法只会清除已经读过的数据。任何未读的数据都被移到缓冲区的起始处,
新写入的数据将放到缓冲区未读数据的后面。
*/
//获取缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
//向缓冲区添加数据
buffer.put(“hello”.getBytes());
//转换成读模式
buffer.flip();
//读取一个字节
// byte b = buffer.get();
// System.out.println((char)b);
//读取多个字节
//这里必须先将读取单个字符注释掉,因为执行一次get后相当于指针已经指向了下标1,
//所以再继续读取buffer.limit个字符后越界.报错BufferUnderflowException
byte[] arr = new byte[buffer.limit()];
buffer.get(arr);
System.out.println(new String(arr));
System.out.println(“底层:”+new String(buffer.array()));
//array返回此缓冲区的底层实现数组。 }
}
缓冲区
-
Buffer的capacity,position和limit
-
缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存。这块内存被包装成NIO Buffer对象,并提供了一组方法,
-
用来方便的访问该块内存。
-
为了理解Buffer的工作原理,需要熟悉它的三个属性:
-
capacity(容量) position(容量开始的位置) limit(容量结束的位置)
-
position和limit的含义取决于Buffer处在读模式还是写模式。不管Buffer处在什么模式,capacity的含义总是一样的。
/
public class Demo2 {
public static void main(String[] args) {
/
* 在创建buffer对象的时候传递的参数就是capacity
* 容量为1024的缓冲区
* 此时buffer的limit和capacity都为1024
* 此时的position是0
*/
ByteBuffer buffer = ByteBuffer.allocate(1024);//开辟容量1024字节
System.out.println(“position:”+buffer.position());//0
System.out.println(“limit:”+buffer.limit());//1024/* * position是5,说明写入了5个字节,position指向的是当前内容的结尾,方便接着往下写 */ buffer.put("hello".getBytes()); System.out.println(buffer.position());//5 System.out.println(buffer.limit());//1024 //可以继续写 buffer.put("world".getBytes()); System.out.println(buffer.position());//10 //切换为读模式 /*这一步很重要 flip可以理解为模式切换 之前的代码实现的是写入操作 *当调用这个方法后就变成读取操作,那么position和limit的值就要发生变换 *此时capacity为1024不变 *此时limit就移动到原来position所在的位置,相当于把buffer中没有数据的空间 *"封印起来"从而避免读取Buffer数据的时候读到null值 *相当于 limit = position limit = 10 *此时position的值相当于 position = 0 * */ buffer.flip(); System.out.println("3:"+buffer.position());//0 System.out.println("3:"+buffer.limit());//10
// //获取单个字节
// //buffer.get();
// //获取多个字节
byte[] data=new byte[buffer.limit()];
buffer.get(data);
System.out.println(“data:”+new String(data));
System.out.println(“读取data后:”+buffer.position());//从0变成了10,position相当于读字节的指针,内容读完了指到了结尾
System.out.println(“读取data后:”+buffer.limit());
//将position设回0,所以你可以重读Buffer中的所有数据。limit保持不变,仍然表示能从Buffer中读取多少个元素
// buffer.rewind();
// byte[] data1=new byte[buffer.limit()];
// buffer.get(data1);
// System.out.println(new String(data1));
// System.out.println(buffer.position());//从0变成了10,position相当于读字节的指针,内容读完了指到了结尾
// System.out.println(buffer.limit());
/*
* clear():
* API中的意思是清空缓冲区
* 而是将缓冲区中limit和position恢复到初始状态
* 即limit和capacity都为1024 position是0
* 此时可以完成写入模式
*/
buffer.clear();
System.out.println("clear后:"+buffer.position());
System.out.println("clear后:"+buffer.limit());
//可以继续写
buffer.put("temp".getBytes());
//继续读
buffer.flip();
byte[] arr = new byte[buffer.limit()];
buffer.get(arr);
System.out.println("temp:"+new String(arr));
}
}
Channel
/*
- 使用Channel
- Channel类似于传统的流对象,但与传统的流对象有两个主要的区别:
- 1.Channel可以直接将我指定文件的部分或全部直接映射成Buffer
- 2.程序不能直接访问Channel中的数据,包括读取,写入都不行.Channel只能与Buffer进行交互
- Java为Channel接口提供了FileChannel,DategramChannel,ServerSocketChannel,SocketChannel等
- 现在以FileChannl来使用
- 在使用FileChannel之前,必须先打开它。但是,我们无法直接打开一个FileChannel,需要通过使用一个InputStream、
- OutputStream的getChannel()方法来返回对应的Channel
/
public class Demo3 {
public static void main(String[] args) throws IOException {
/
* 写入文本文件
*/
//创建文件写出流
FileOutputStream fileOutputStream = new FileOutputStream(“temp1.txt”);
//获取通道
FileChannel fileChannel = fileOutputStream.getChannel();
//创建缓冲流
ByteBuffer buffer = ByteBuffer.allocate(1024);
//向缓冲流中放入数据
buffer.put(“helloworld”.getBytes());
//转换模式为读取内容
buffer.flip();
//利用通道写入
fileChannel.write(buffer);
//关闭资源
fileChannel.close();
fileOutputStream.close();
System.out.println(“写入完毕”);
}
}
demo
public class Demo4 {
public static void main(String[] args) throws IOException {
/*
* 文件读取
*/
//创建文件输入流
FileInputStream fileInputStream = new FileInputStream(“temp1.txt”);
//获取通道
FileChannel fileChannel = fileInputStream.getChannel();
//创建缓冲流对象
ByteBuffer buffer = ByteBuffer.allocate(1024);
//利用通道读取内容
int num = fileChannel.read(buffer);
System.out.println(num);
//转换模式为读取模式
buffer.flip();
//利用缓冲流读取数据到控制台
System.out.println(new String(buffer.array(),0,num));
byte[] bs = new byte[buffer.limit()];
buffer.get(bs);
System.out.println(Arrays.toString(bs));
//关闭资源
fileChannel.close();
fileInputStream.close();
System.out.println(“读完了”);
}
}
文件的复制
public class Demo5 {
public static void main(String[] args) throws IOException {
/*
* 文件的复制
* //打开或创建一个文件,返回一个文件通道来访问该文件
//StandardOpenOption是枚举里面显示的是文件打开方式
*/
//创建通道
FileChannel inChannel = FileChannel.open(Paths.get(“temp1.txt”), StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(Paths.get(“temp2.txt”), StandardOpenOption.WRITE,StandardOpenOption.CREATE);
//创建缓冲流
ByteBuffer buffer = ByteBuffer.allocate(1024);
int num = 0;
//复制
while ((num = inChannel.read(buffer)) != -1) {
buffer.flip();
outChannel.write(buffer);
buffer.clear();//让指针重新回到0
}
inChannel.close();
outChannel.close();
System.out.println("复制完毕");
}
}
内存映射读写文件
public class Demo6 {
public static void main(String[] args) throws IOException {
/*
* 内存映射读写文件
*/
//创建通道
FileChannel inChannel = new FileInputStream(“temp1.txt”).getChannel();
FileChannel outChannel = new FileOutputStream(“temp3.txt”).getChannel();
//2使用内存映射缓冲区
/*
* 只读:任何试图修改导致缓冲区会造成ReadOnlyBufferException被。(MapMode.READ_ONLY)
* 读/写:对导致缓冲区的变化最终会传递到文件;他们可能会或可能不会有映射相同文件的其他程序可见。
* (MapMode.READ_WRITE)
* 私人:对导致缓冲区的更改不会传播到文件不会有映射同一文件的其他程序可见;
* 相反,他们会导致缓冲区的修改部分 私有副本被创建。(MapMode.PRIVATE)
* */
//这里注意第一个参数是操作的模式是只读 第二个参数是读取文件的起始位置 第三个参数是终止位置
//如果文件的大小超过2G,一般建议建立多个映射
MappedByteBuffer mappedByteBuffer = inChannel.map(MapMode.READ_ONLY, 0, inChannel.size());
outChannel.write(mappedByteBuffer);
inChannel.close();
outChannel.close();
System.out.println("读写完毕");
System.out.println(inChannel.getClass().toString());
}
}
NIO.2
/*
-
NIO.2
-
Java7对原有NIO进行了重大改进,改进主要包括如下两个方面的内容
-
1.提供了全面的文件IO和文件系统访问支持
-
2.基于异步Channel的IO
-
Path、Paths和Files
-
之前学习中学习过File类来访问文件系统,但是File类的功能比较有限,NIO.2为了弥补这种不足,
-
引入了一个Path接口还提供了Files和Paths两个工具类,其中Files分装了包含大量静态方法来操作文件,
-
Paths则包含了返回Path的静态工厂方法
/
Paths Files工具类
public class Demo7 {
public static void main(String[] args) throws IOException {
//Path就是用来替代File
//构建Path对象的两种方式
//传一个路径
Path path1 = Paths.get(“D:\123”);
//第一个参数是盘符 ,第二个参数是可变参数 下面有多少文件路径就写多少
Path path2 = Paths.get(“D:\”, “123”,“456.txt”);
//Path是结合Files工具类使用的
//创建目录
Files.createDirectories(path1);
//判断文件是否存在
if(!Files.exists(path2)) {
//创建文件
Files.createFile(path2);
}
//复制文件
//第一个参数原文件路径, 第二个参数目标文件路径
// Files.copy(new FileInputStream(new File(“D:\123\456.txt”)), Paths.get(“D:\”, “123”,“222.txt”));
// Files.copy(path2, new FileOutputStream(new File(“D:\123\789.txt”)));
// Files.copy(path2, Paths.get(“D:\”, “123”,“111.txt”));
//一次读取文件中所有的行
List readAllLines = Files.readAllLines(Paths.get(“src/com/qiangfeng/test/Demo1.java”));
for (String str : readAllLines) {
System.out.println(“haha:”+str);
}
//将集合中的内容写入到文件中
Files.write(Paths.get(“D:\”, “123”,“Demo.java”), readAllLines);
/
static BufferedReader newBufferedReader(Path path)
打开一个文件进行读取,返回一个 BufferedReader以有效方式从文件中读取文本。
static BufferedReader newBufferedReader(Path path, Charset cs)
打开一个文件进行读取,返回一个 BufferedReader可以用来有效方式从文件中读取文本。
static BufferedWriter newBufferedWriter(Path path, Charset cs, OpenOption… options)
打开或创建一个文件写入,返回一个 BufferedWriter,可以有效的方式将文件写入文本。
static BufferedWriter newBufferedWriter(Path path, OpenOption… options)
打开或创建一个文件写入,返回一个 BufferedWriter能够以有效的方式的文件写入文本。直接创建缓冲流对象 可以执行 字符集 和访问权限 */
}
}
使用FileVisitor遍历文件和目录
/* -
使用FileVisitor遍历文件和目录
-
在以前的Java版本中,如果程序要遍历指定目录的所有文件和子目录,则只能使用递归进行遍历,
-
但这种方法不仅复杂,而且灵活性也不高,有了Files工具类的帮助,现在可以使用更优雅的方式来遍历和子目录
/
public class Demo8 {
public static void main(String[] args) throws IOException {
/
* FileVisitor参数代表的是一个文件访问器,walkFileTree()方法会自动遍历
* start路径下的所有文件和子目录,遍历文件和子目录都会触发FileVisitor中相应
* 的方法
* FileVisitResult postVisitDirectory(T dir, IOException exc)
访问子目录之后会触发这个方法
FileVisitResult preVisitDirectory(T dir, BasicFileAttributes attrs)
访问子目录之前会触发这个方法
FileVisitResult visitFile(T file, BasicFileAttributes attrs)
访问file文件时触发该方法
FileVisitResult visitFileFailed(T file, IOException exc)
访问file文件失败时触发该方法
返回FileVisitResult是一个枚举
CONTINUE 代表继续访问的后续行为
SKIP_SIBLINGS 代表继续访问的后续行为,但不访问该文件后目录的兄弟文件或目录
SKIP_SUBTREE 代表继续访问的后续行为,但不访问该文件或目录的子目录树
TERMINATE 代表终止访问的后续行为
实际开发中没有必要4个方法都要重写,可以通过FileVisitor的子类SimpleFileVisitor(适配器)来
创建自己的文件访问器,选择性的重写方法
*/
//遍历文件
Files.walkFileTree(Paths.get("D:\\workspace\\BigDataNIO"), new SimpleFileVisitor<Path>() {
//访问文件时触发该方法
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
System.out.println("正在访问"+file+"文件");
// if(file.endsWith(“MergeSort.java”)) {
// System.out.println("–找到了文件–");
// return FileVisitResult.TERMINATE;
// }
return FileVisitResult.CONTINUE;
}
//开始访问目录时触发的方法
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
System.out.println(“正在访问”+dir+“路径”);
return FileVisitResult.CONTINUE;
}
});
}
}