Java基础整理 (NIO,Buffer,Channel通道)

NIO和NIO.2

随着JDK7的发布,Java对NIO进行极大的扩张,增强了对文件的处理和文件系统性的支持,以至于得到了一个新名称NIO.2(java实际开发用的少,但是Netty通讯框架就是NIO开发,这个Nitty是Spark2.x之后的通讯框架)

NIO和IO的主要区别

IONIO
面向流编程(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(); 
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值