NIO和NIO.2
随着JDK7的发布,Java对NIO进行极大的扩张,增强了对文件的处理和文件系统性的支持,以至于得到了一个新名称NIO.2(java实际开发用的少,但是Netty通讯框架就是NIO开发,这个Nitty是Spark2.x之后的通讯框架)
NIO和IO的主要区别
IO | NIO |
---|---|
面向流编程(Stream) | 面向缓冲区编程(Buffer) |
单向流 | 既可以单向也可以双向 |
阻塞 | 非阻塞 |
面向流和面向缓冲
IO面向流就意味着每次从流中读取一个或多个字节,直到读取所有字节完成,流的单向是指从内存中获取数据需要创建一个管道,这个管道只能读取数据或者写出数据,单向执行
NIO的通道既可以单向,也可以双向的,提供了一个类Channel类 可以实现read方法而类中也提供了write方法
NIO.2之Path,Paths,Files的使用
早期的java提供一个File类可以对文件系统进行操作,类对文件操作提供反馈,这个反馈只是一个boolean类型值,并会提示异常信息,所以Java提供一个加强类在NIO.2 中引用Path接口,代表一个路径,这个路径可以是文件或者是文件名,Path是file类的升级版本
Path和Paths
Paths是Path创建对象的方式
在以前操作IO的时候是这样写
File file = new File("C:\\test.txt")
但是正在Java7以后,可以这样写 --> 是为了NIO中提供的
Path path = Paths.get("C:\\test.txt")
操作Path
// 1.创建path对象没有第二个参数
//等价于 File file = new File("dir/file.txt")
Path path1 = Paths.get("dir/file.txt");
//2.创建path对象有第二个参数
//等价于 File file = new File("E:\\","新建文本文档.txt");
//Path除了支持文件还支持目录(文件夹)
Path path2 = Paths.get("E:\\","新建文本文档.txt");
//可以直接打印path对象即路径
System.out.println(path1);
System.out.println(path2);
//常用API
//toString 返回Path对象的字符串形式
String p = path1.toString();
System.out.println(p);
//判断路径是以什么开头或以什么结尾
System.out.println(path2.startsWith("E:\\"));
System.out.println(path2.endsWith("新建文本文档.txt"));
//判断路径是不是绝对路径
System.out.println(path1.isAbsolute()); //返回Path对象包个路径(父路径)
System.out.println(path1.getParent()); //返回Path对象的根路径
System.out.println(path2.getRoot()); //返回path对象中文件文件名
System.out.println(path1.getFileName());
//返回值是一个int get NameCount():返回 是Path对象根后的路径数量
Path path3 = Paths.get("E:\\", "新建文件夹\\新建文件夹");
for(int i = 0 ;i<path3.getNameCount();i++) {
System.out.println(path3.getName(i)); //需要和getNameCount配合使用
}
//合并两个路劲对象后返回新的路径 //ps:路径一定要合理,不能那个随便拼接
Path path4 = Paths.get("E:\\");
Path path4_1 = Paths.get("新建文件夹");
//调用方法的是拼接的路径开始位置, 参数是路径拼接位置
Path path4_2 = path4.resolve(path4_1);
System.out.println(path4_2);
//File和 Path之间转换 //将一个Path对象转化为File对象
File file = path1.toFile(); //File对象转化为Path对象
Path newpath = file.toPath();
操作Files
Path p1 = Paths.get("dir/file.txt");
Path p2 = Paths.get("dir/file1.txt");
//文件复制
//第一个参数是要复制的文件
//第二个参数是要复制到的位置
//第三个参数是复制方式(若不存在就创建,存在就是覆盖)
Files.copy(p1,p2,StandardCopyOption.REPLACE_EXISTING);
//创建一个文件(这个文件 必须不存在,不然会抛出异常)
//ps:File中的这个方法必须记住 mkdir 创建文件夹 mkdirs创建多个文件夹
// 最长用的Linux方法 --> ls ll 查看目录下所有文件 或 所有文件详细信息
Path p3 = Paths.get("dir/createFile.txt");
Files.createFile(p3);
//删除文件/文件夹 删除文件的路径必须存在不然会抛出异常
Files.delete(p3);
//删除文件或目录,若果存在执行删除,如果不存在就正常执行结束
Files.deleteIfExists(p3);
//io包中File有一个方法 renameTo --> 即移动 复制 剪切,重命名于一身的方法
//移动文件
//第一个参数是要移动(复制)文件
//第二个参数是要移动(复制)的文件到什么位置(需要给一个你文件名)
//第三个参数是是够可以移动
//AccessDeniedException 文件夹或文件没有访问权限
//系统无法将文件移到不同的磁盘驱动器 只能是同一个磁盘内移动
//Path p4 = Paths.get("dor/mfile.txt");
//Files.move(p2, p4, StandardCopyOption.ATOMIC_MOVE);
//获取文件大小 System.out.println(Files.size(Paths.get("dor/mfile.txt")));
//创建文件夹 文件夹必须不存在不然会抛出异常 Path path = Paths.get("dfr"); Files.createDirectory(path);
//判断文件是否存在
//第一个参数是路径
//第二个参数是 文件方式 (不能可以连接 (快捷方式))
System.out.println(Files.exists(p1, LinkOption.NOFOLLOW_LINKS));
//判断是不是文件夹
System.out.println(Files.isDirectory(path, LinkOption.NOFOLLOW_LINKS));
//判断是否是可读文件
System.out.println(Files.isReadable(p1));
//判断是否是可写文件
System.out.println(Files.isWritable(p2)); //判断是否是隐藏文件(这个文件必须存在,才可以判断是否是隐藏),否则抛出异常
System.out.println(Files.isHidden(p1));
通过Files操作channel通道
/**
* READ 表示Channel是可读的 WRITE 表示Channel是可以写
* CREATE :表示文件不存在则创建,存在没有任何操作(推荐使用)
* CREATE_NEW :表示文件不存在则创建,但是若文件存在着抛出异常 * 第二个参数是一个可变参数,所以在创建通道的时候可以指定多种状态 */
SeekableByteChannel newByteChannel = Files.newByteChannel(path,
StandardOpenOption.READ,StandardOpenOption.WRITE,StandardOpenOption.CREATE);
//遍历目录
Path path1 = Paths.get("E:\\新建文件夹");
DirectoryStream<Path> newDirectoryStream = Files.newDirectoryStream(path1); //通过当前DirectoryStream对象可以创建迭代器对象
//这个迭代器中存储的都是Path对象
Iterator<Path> iterator = newDirectoryStream.iterator();
while(iterator.hasNext()) {
System.out.println(iterator.next());
}
//获取输入输出流对象
//需要主要若是InutStream对象那么必须是READ权限
InputStream is = Files.newInputStream(path, StandardOpenOption.READ);
//若是OutputStream对象那么必须是WRITE权限
OutputStream os = Files.newOutputStream(path, StandardOpenOption.WRITE);
核心NIO
NIO中的核心部分组成:缓冲区Buffer和通道Chanel
JavaNIO的核心在于通道和缓冲区,通道表示IO源的连接,若使用NIOname就需要获取连接方式即通道,连接后数据的储存时储存到缓冲区的
channel:代表内存和磁盘之间的连接,负责传输缓冲区
buffer和channel二者的交互:主要是数据从通道读入缓冲区,从缓冲区写入通道
Channel负责传输,Buffer负责储存数据
**缓冲区:**一个用与定义基本数据类型的容器,低层是一个数组组成(除Boolean),是有NIO包所定义的,所有的缓冲区都是Buffer是一个抽象的子类,Buffer主要是用于与Channel进行交互,数据是通过通道读取到缓冲区中,再从缓冲区写入通道
Buffer常用子类:
ByteBuffe——>子类MappedByteBuffer(NIO最常用)
除Boolean类型外,所有的都是XXXBuffer,都是Buffer的子类。byteBuffer有自己的子类MappedByteBuffer
这些子类都使用相似的数据管理方式,管理各自的数据
若需要创建对应的子类缓冲区,可以使用一个静态方法 xxxBuffer.allocate(int capacity)创建一个容量为capacity大小的xxxBuffer对象(xxx是除boolean类型的所有基本数据类型)
缓冲区的基本属性
容量(capacity)表示buffer中最大怼数据容量,一旦声明后,不能更改,通过Buffer中的capacity()方法获取缓冲区大小,并且capacity不能负数
ps:capacity就相当于byte[] buf = new byte[1024] ——>其实就相当于1024
**限制(limit)?*第一个不应该读取或写入的数据的索引,即唯一limit后的数据不可重写,通过buffer中limit()获取对应着,并且缓冲区中limit不能为负数,不能大于capacity
**位置(position)?*当前要读取或写入书记的索引,通过buffer中的position方法可以获取值,缓冲区中的position不能为负数,且不能大于limit
**标记(mark):**标记一个索引,通过buffer中mark()方法可以获取其值,mark方法将mark标记为当前position位置,之后可以用reset ()方法将position恢复到mark的位置, 默认-1
mark<=position<=limit<=capacity
Buffer(缓冲区)代码演示
put(byte b):将给定单个字节写入缓冲区的当前位置
get():读取单个字节
NIO非阻塞:针对最常用的ByteBuffer来讲
它可以创建“非直接缓冲区” ——>>allocate
也可以创建“直接缓冲区”——>>allocateDirect
ByteBuffer byteBuffer = ByteBuffer.allocate(10);
byteBuffer.put("hello".getByte);
//每put一个字节,position就会+1
byteBuffer.flip();
//flip方法,切换读取数据模式,此时limit将被设置为position的位置,并且position会归零
byteBuffer.get();//读取数据,每get一次获取一个字节,且position+1
byte[] bs = new byte[3];
byteBuffer.get(bs);//创建一个数组,通过字节数组对数据进行读取
//重置(重新初始化)
byteBuffer.rewind();//重置position
//清空(三个属性重置到最初的值)
byteBuffer.clear();
Buffer缓冲中的mark
//创建一个缓冲区
ByteBuffer bf = ByteBuffer.allocate(10);
bf.put("hello".getByte);
//转换模式到读取
bf.flip();
byte[] dst = new byte[5];
bf.get(dst,0,2);//数组,从什么地方开始写即数组位置,书写多少个字节
//mark标记 默认值是-1
bf.mark();
//再次读取缓冲区位置
bf.get(dst,2,2);
//使用reset方法重启标记,这个方法必须和mark配合使用,不然会出现异常
bf.reset();//position会获取mark所记录的位置 position = mark
//判断是否还有元素读取到
boolean isFull = bf.hasRemaining();
bf.remaining();//还有结果没有读取到
//循环读取
while(bf.hasRemaining()){
System.out.println((char)bf.get());
}
NIO中buffer另一种获取数据方式
ByteBuffer bf = ByteBuffer.allocate(10);
bf.put("hello".getByte());
byte[] array = bf.array();
ByteBuffer直接与非直接缓冲区
ps:直接与非直接缓冲区针对的是ByteBuffer的
如果是直接字节缓冲区,则java虚拟机会尽最大怼努力在缓冲区上执行本机IO操作
如果为非直接字节缓冲区,则java虚拟机会拷贝一个系统对应内中的数据,然后再执行本机IO操作
channel通道
Channel是在java.nio包下,
FileChannel用于读取。写入操作本地文件
SocketChannel通过TCP读写网络中的数据
ServerSockedChannel:可以监听新进来的TCP连接,对每一个新捡来的连接都会创建一个SockedChannel
DatagramChannel:通过UDP读取网络中数据
channel本身是一个接口,并且不负责存数据,数据是储存在buffer中,channel的目的就是传输Buffer中
//使用第一种方式新建channel,使用的是非直接缓冲区
public static void FristChannel(){
// 读取
FileChannel inChannel = new InputStream("dir/非直接缓冲区.bmp").getChannel();
//写
FileChannel outChannel = new FileOutStream("dfr/1.bmp").getChannel();
//提供byteBuffer缓冲区
ByteBuffer buffer = Buffer.allocate(1024);
while(inChannel.read(buffer) != -1){
//需要转换模式
buffer.flip();
outChannel.write(buffer);
buffer.clear();//这里必须,必须,必须要清空,不然会无限循环
}
outChannel.close();
inChannel.close();
}
//使用第二种方式新建channel,使用的是直接缓冲区
//这个缓冲区储存文件大小不能超过2G
public static void SecondChannel(){
// 读取
FileChannel inChannel = FileChannel.open(Paths.get("dir/非直接缓冲区.bmp"),StandardOpenOption.READ);
//写
FileChannel outChannel = FileChannel.open(Paths.get("dfr/1.bmp"),StandardOpenOption.READ,StandardOpenOption.WRITE,StandardOpenOption.CREATE);
MappedByteBuffer inMappedBudder = inChannel.map(MapMode.READ_ONLY,0,inChannel.size());//READ_ONLY读取模式 position开始位置 创建一个多大的缓冲区---》一般Channel对象.size()
MappedByteBuffer outMappedBudder = outChannel.map(MapMode.READ_WRITE,0,inChannel.size());//
//数据读写操作 需要一个byte类型数据即存文件大小的数组
byte[] buffer = new byte[inMappedBudder.limit()];
//将数据写入数组中
inMappedBudder.get(buffer);
//将数据写出去
outMappedBudder.put(buffer);
//提供byteBuffer缓冲区
ByteBuffer buffer = Buffer.allocate(1024);
outChannel.close();
inChannel.close();
}