IO模型与NIO基础--NIO网络传输选择器--字符编码

news/2025/2/22 13:48:24

放进NIO体系进行网络编程的工作流程:

在这里插入图片描述

Selector的创建

通过调用Selector.open()方法创建一个Selector,如下:
Selector selector = Selector.open();

向Selector注册通道

通过Channel.register()方法来实现,
注意:Channel和Selector一起使用时,Channel必须处于非阻塞模式下。
channel.configureBlocking(false); //设置通道为非阻塞模式
SelectionKey key = channel.register(selector,Selectionkey.OP_READ);

register()方法的第二个参数:是一个“兴趣(interest)集合”,意思是在通过Selector监听Channel时对什么事件感兴趣。
可以监听四种不同类型的事件:

  1. Connect 链接就绪,某个channel成功连接到另一个服务器称为“连接就绪”。
  2. Accept 接收就绪,一个server socket channel准备好接收新进入的连接称为“接收就绪”。
  3. Read 读就绪,一个有数据可读的通道可以说是“读就绪”。
  4. Write 写就绪,等待写数据的通道可以说是“写就绪”。
    这四种事件用SelectionKey的四个常量来表示:
    1.SelectionKey.OP_CONNECT
    2.SelectionKey.OP_ACCEPT
    3.SelectionKey.OP_READ
    4.SelectionKey.OP_WRITE
    如果你对不止一种事件感兴趣,那么可以用“位或”操作符将常量连接起来,如下:
    int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;

SelectionKey说明
      当向Selector注册Channel时,register()方法会返回一个SelectionKey对象。
这个对象包含了一些有用的属性:
1.interest集合
2.ready集合
3.Channel
4.Selector
5.附加的对象(可选)
interest集合
interest集合是你所选择的感兴趣的事件集合。
可以通过SelectionKey读写interest集合,像这样:

SelectionKey selectionKey=channel.register(selector, SelectionKey.OP_xxxx);
int interestSet = selectionKey.interestOps();
boolean isInterestedInAccept  = (interestSet & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT;
boolean isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT;
boolean isInterestedInRead    = interestSet & SelectionKey.OP_READ;
boolean isInterestedInWrite   = interestSet & SelectionKey.OP_WRITE;

      可以看到,用“和”操作interest 集合和给定的SelectionKey常量,可以确定某个确定的事件是否在interest 集合中。
ready集合
      ready 集合是通道已经准备就绪的操作的集合。在一次选择(Selection)之后,你会首先访问这个ready set。
可以这样访问ready集合:
int readySet = selectionKey.readyOps();
      可以用像检测interest集合那样的方法,来检测channel中什么事件或操作已经就绪。但是,也可以使用以下四个方法,它们都会返回一个布尔类型:

selectionKey.isAcceptable();
selectionKey.isConnectable();
selectionKey.isReadable();
selectionKey.isWritable();

另外
从SelectionKey访问Channel和Selector很简单。如下:

Channel  channel  = selectionKey.channel();
Selector selector = selectionKey.selector();  

附加的对象
      可以将一个对象或者更多信息附着到SelectionKey上,这样就能方便的识别某个给定的通道。
例如,可以附加 与通道一起使用的Buffer,或是包含聚集数据的某个对象。使用方法如下:
selectionKey.attach(theObject);
Object attachedObj = selectionKey.attachment();
还可以在用register()方法向Selector注册Channel的时候附加对象。如:
SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);

选择器的select()
一旦向Selector注册了一或多个通道,就可以调用几个重载的select()方法。
这些方法返回你所感兴趣的事件(如连接、接受、读或写)同时这些事件已经准备就绪的那些通道。
换句话说,如果你对“读就绪”的通道感兴趣,select()方法会返回读事件已经就绪的那些通道。
下面是select()方法:
1.select()阻塞到至少有一个通道在你注册的事件上就绪了。
2.select(long timeout)和select()一样,除了最长会阻塞timeout毫秒(参数),一般用这个,不能无限阻塞。
3.selectNow()不会阻塞,不管什么通道就绪都立刻返回,此方法执行非阻塞的选择操作。
如果自从前一次选择操作后,没有通道变成可选择的,则此方法直接返回零。

select()方法有返回值,返回的int值表示有多少通道已经就绪。亦即,自上次调用select()方法后有多少通道变成就绪状态。
如果调用select()方法,因为有一个通道变成就绪状态,返回了1,若再次调用select()方法,
如果另一个通道就绪了,它会再次返回1。
如果对第一个就绪的channel没有做任何操作,现在就有两个就绪的通道,
但在每次select()方法调用之间,只有一个通道就绪了。
选择器的selectedKeys()
      一旦调用了select()方法,并且返回值表明有一个或更多个通道就绪了,然后可以通过调用selector的selectedKeys()方法获取到:

Set selectedKeys = selector.selectedKeys();

   当像Selector注册Channel时,Channel.register()方法会返回一个SelectionKey对象。
这个对象代表了注册到该Selector的通道。
可以通过SelectionKey的selectedKeySet()方法访问这些对象。

可以遍历这个已选择的键集合来访问就绪的通道。如下:

Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
while(keyIterator.hasNext()) {
    SelectionKey key = keyIterator.next();
    if(key.isAcceptable()) {
        // 一个连接被ServerSocketChannel接受
    } else if (key.isConnectable()) {
        // 与远程服务器建立了连接
    } else if (key.isReadable()) {
        // 一个channel做好了读准备
    } else if (key.isWritable()) {
        // 一个channel做好了写准备
    }
    keyIterator.remove();
}

      这个循环遍历已选择键集中的每个键,并检测各个键所对应的通道的就绪事件。
注意每次迭代末尾的keyIterator.remove()一定要调用,Selector不会自己从已选择键集中移除SelectionKey实例。
必须在处理完通道时自己移除。下次该通道变成就绪时,Selector会再次将其放入已选择键集中。若不移除,下次还是读取原来的数据,这是要命的。
      SelectionKey.channel()方法返回的通道需要转型成你要处理的类型,如ServerSocketChannel或SocketChannel等。
选择器的wakeUp()
      某个线程调用select()方法后阻塞了,即使没有通道已经就绪,也有办法让其从select()方法返回。
只要让其它线程在第一个线程调用select()方法的那个对象上调用Selector.wakeup()方法即可。
阻塞在select()方法上的线程会立马返回。
如果有其它线程调用了wakeup()方法,但当前没有线程阻塞在select()方法上,上一个调用select()方法的阻塞线程会立即醒来(wake up)。

选择器的close()
      用完Selector后调用其close()方法会关闭该Selector,且使注册到该Selector上的所有SelectionKey实例无效。
通道本身并不会关闭。

客户端与服务端简单交互实例

下面的程序涉及到一些网络编程的知识:

  1. 服务器必须先建立ServerSocket或者ServerSocketChannel 来等待客户端的连接
  2. 客户端必须建立相对应的Socket或者SocketChannel来与服务器建立连接
  3. 服务器接受到客户端的连接受,再生成一个Socket或者SocketChannel与此客户端通信

服务器

/*
 * nio网络编程实例,这是服务端
 * 1.建立ServerSocketChannel通道,设置非阻塞 ,并绑定一个地址,客户链接到这个地址,等待客户端的链接
 * 2.获取选择器的实例,注册通道到里面去,设置感兴趣的事件,ServerSocketChannel这种通道只有一个事件--链接事件
 * 3.准备缓冲区:两个,一个读,一个写
 * 4.实现与客户端交互,首先一个死循环,不停的获取各种客户端链接请求通道,轮询作用似的
 *   a.选择器的select(1000)方法,会获取各通道对象个数(通道感兴趣事件的)1000是表示阻塞一秒,如果一秒之内没结果,重新轮询
     b.有通道就绪,则拿到通道对象键值集合Set,遍历集合,找到接入链接的通道,让其接入,返回一个能读写的通道,不是上面的接入通道
     c.读写通道也要注册到选择器里面,先读事件,同一个通道的多次注册,如是不同的事件改变,只是注删了一个通道
 */
public class ServerDemo {
	public static void main(String[] args) {
		try {
		    
			ServerSocketChannel serverSocketChannel= ServerSocketChannel.open();
			serverSocketChannel.socket().bind(new InetSocketAddress("127.0.0.1",8000));
			serverSocketChannel.configureBlocking(false);
			
			Selector selector=Selector.open();
			serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);  //接入事件就绪
			
			ByteBuffer readBuffer= ByteBuffer.allocate(1024);
			ByteBuffer writeBuffer=ByteBuffer.allocate(1024);
			writeBuffer.put("this is server".getBytes());  //写缓冲区中写入这个字符串
			writeBuffer.flip();             //翻转非直接缓冲区,非直接缓冲区,postion指针不能自动指向开缓冲区开头处
			
			while(true) {
				int nReady=selector.select(1000);  //捕获所有通道
				if(nReady==0)  continue;    //一个通道都没有,一秒后继续查询
				Set<SelectionKey> keys =selector.selectedKeys();
				Iterator<SelectionKey> iterator = keys.iterator();
				while(iterator.hasNext()) {
					SelectionKey key=iterator.next();
					iterator.remove();  //手动移除,让下次轮询通道是全新的,而不是这次的
					if(key.isAcceptable()) {  //如果是准备链接的通道,就让客户端链接, 当然了,这个程序简单只有一个通道
						SocketChannel channel=serverSocketChannel.accept();
						channel.configureBlocking(false);
						channel.register(selector, SelectionKey.OP_READ);  //不用担心,下次读客户发送的信息,因为有死循在那不的轮询
					}else if(key.isReadable()) {  //如果通道对象key是有可读事件,就读到缓冲区中
						SocketChannel channel = (SocketChannel) key.channel();
						readBuffer.clear();
						channel.read(readBuffer);
						readBuffer.flip();
						/*while(readBuffer.hasRemaining()) {  //判断缓冲区还没有数据,有的话,从缓冲区读数据,打印到控制台
				    		System.out.print("读到的数据是:"+(char)readBuffer.get());  //读得是字节码,中文会乱码,一次读一个字节
				    	}*/
						System.out.println("服务器端收到的数据,直接打印:"+ new String(readBuffer.array()));
					    key.interestOps(SelectionKey.OP_WRITE);
					}else if(key.isWritable()){
						SocketChannel channel =(SocketChannel)key.channel();
						writeBuffer.rewind();
						channel.write(writeBuffer);
						key.interestOps(SelectionKey.OP_READ);  //以便下次再读
						
					}
				}
			}
			
	     }catch (Exception e) {
			e.printStackTrace();
		}
	}

}

客户端

/*
 * nio网络编程实例,这是客户端
 * 不用实例化,链接通道(类似洒店的迎宾)
 * 1.通道
 * 2.缓冲区,两个,一个读,一个写
 * 3.先向服务器写,再读数据
 * 
 */
public class ClientDemo {
	public static void main(String[] args) {
		try {
			SocketChannel channel = SocketChannel.open();
			channel.connect(new InetSocketAddress("127.0.0.1",8000));  //链接到服务器
			
			ByteBuffer readBuffer= ByteBuffer.allocate(1024);
			ByteBuffer writeBuffer=ByteBuffer.allocate(1024);
			writeBuffer.put("this is client".getBytes());  //写缓冲区中写入这个字符串
			writeBuffer.flip(); 
			
			while(true) {
				writeBuffer.rewind();    //重置缓冲区,写入
				channel.write(writeBuffer);
				
				readBuffer.clear();
				channel.read(readBuffer);
				readBuffer.flip();
				System.out.println("客户端收到的数据:"+new String(readBuffer.array()));
				
				
			}
		}catch (IOException e) {
			e.printStackTrace();
		}
	}

}

字符集和Charset

基础认识

计算机中储存的信息都是用二进制数表示的;
      而我们在屏幕上看到的英文、汉字等字符是二进制数转换之后的结果。
      通俗的说,按照何种规则将字符存储在计算机中,如’a’用什么表示,称为"编码";
      反之,将存储在计算机中的二进制数解析显示出来,称为"解码",如同密码学中的加密和解密。
      在解码过程中,如果使用了错误的解码规则,则导致’a’解析成’b’或者乱码。

      字符集(Charset):多个字符的集合,字符集种类较多,每个字符集包含的字符个数不同,字符和二进制数字的对应规则不同,常见字符集名称:ASCII字符集、GB2312字符集、BIG5字符集、GBK字符集、UTF-8字符集等。
      字符编码(Character Encoding):是一套法则,使用该法则能够对自然语言的字符的一个集合(如字母表或音节表),
与其他东西的一个集合(如号码或电脉冲)进行配对。即在符号集合与数字系统之间建立对应关系,它是信息处理的一项基本技术。通常人们用符号集合(一般情况下就是文字)来表达信息。
而以计算机为基础的信息处理系统则是利用元件(硬件)不同状态的组合来存储和处理信息的。
      元件不同状态的组合能代表数字系统的数字,因此字符编码就是将符号转换为计算机可以接受的数字系统的数,称为数字代码。
常用字符集和字符编码

  1. ASCII字符集&编码
    ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)是基于拉丁字母的一套电脑编码系统。它主要用于显示现代英语。
  2. GBXXXX字符集&编码
    计算机发明之处及后面很长一段时间,只用应用于美国及西方一些发达国家,ASCII能够很好满足用户的需求。
    但是当天朝也有了计算机之后,为了显示中文,必须设计一套编码规则用于将汉字转换为计算机可以接受的数字系统的数。
    规定:一个小于127的字符的意义与原来相同,但两个大于127的字符连在一起时,就表示一个汉字,
    前面的一个字节(他称之为高字节)从0xA1用到 0xF7,后面一个字节(低字节)从0xA1到0xFE,
    这样我们就可以组合出大约7000多个简体汉字了。
    上面的编码规则形成的字符集就是GB2312。
    GB2312或GB2312-80是中国国家标准简体中文字符集,全称《信息交换用汉字编码字符集·基本集》
       由于GB 2312-80只收录6763个汉字,有不少汉字并未有收录在内,如部分在GB 2312-80推出以后才简化的汉字(如"啰"),部分人名用字(如中国前总理朱镕基的"镕"字),台湾及香港使用的繁体字,日语及朝鲜语汉字等。因此1995年发布了GBK编码,是在GB2312-80标准基础上的进行了扩展,共收录了21003个汉字
  3. BIG5字符集&编码
    Big5,又称为大五码或五大码,是使用繁体中文(正体中文)社区中最常用的电脑汉字字符集标准。
  4. Unicode
    像天朝一样,当计算机传到世界各个国家时,为了适合当地语言和字符,设计和实现类似GB2312/GBK/BIG5的编码方案。这样各搞一套,在本地使用没有问题,一旦出现在网络中,由于不兼容,互相访问就出现了乱码现象。
          为了解决这个问题,产生了——Unicode。Unicode编码系统为表达任意语言的任意字符而设计。
    它使用4字节的数字来表达每个字母、符号,或者表意文字(ideograph)。每个数字代表唯一的至少在某种语言中使用的符号。
    在计算机科学领域中,Unicode(统一码、万国码、单一码、标准万国码)是业界的一种标准,它可以使电脑得以体现世界上数十种文字的系统。
    Unicode是标准,是规则,
    UTF-32/ UTF-16/ UTF-8是三种字符编码方案就是Unicode的具体实现。
    UTF-8:电子邮件、网页及其他存储或传送文字的应用中,优先采用的编码方案,也是用的最多的.

java的字符集

      Java默认使用Unicode字符集,但很多系统并不使用Unicode字符集,那么当从系统中读取数据到Java程序时,就可能出现乱码的问题。
      JDK1.4提供了Charset来处理字节序列和字符序列(字符串)之间的转换关系,该类包含了用于创建解码器和编码器的方法,还提供了获取Charset所支持字符集的方法,Charset类是不可变的。
      Charset类提供了一个静态方法availableCharset()来获取当前JDK所支持的所有字符集。返回的是一个SortedMap<String,Charset>
例:查看JDK支持的所有字符集

public class CharsetTest{
	public static void main(String[] args) {
		SortedMap<String,Charset> map = Charset.availableCharsets();
		for(String s:map.keySet()) {
			System.out.println(s);
		}
	}
}
-------------------------------------------------------------------------
Big5
Big5-HKSCS
CESU-8
EUC-JP
EUC-KR
GB18030
GB2312
GBK
....
UTF-8
...

创建字符集对象与获取相应的编码解码对象

      一旦知道字符集的别名后,就可以用Charset的forName()方法来创建对应的Charset对象,
forName方法的参数就是字符集的别名。
拿到Charset对象,就可以调用它的newEncoder和newDecoder方法,创建对应的编码器和解码器对象。
解码器对象CharsetDecoder里的decode()方法就可以将ByteBuffer转为CharBuffer,CharsetEncoder里的encode()方法作用相反,将CharBuffer转为ByteBuffer。

编码与解码缓冲区

接下来使用编码器将CharBuffer中的字符序列转换为字节序列ByteBuffer。
CharBuffer和ByteBuffer是java NIO中的IO操作类。
编码器,编码字符缓冲区后,转换为字节序列缓冲区
解码器,解码字节缓冲区后,转换为字符序列缓冲区。
编码

public class CharsetTest{
	public static void main(String[] args) throws CharacterCodingException {
		Charset charset = Charset.forName("GBK");
		CharsetEncoder encoder = charset.newEncoder();
		
		CharBuffer charBuffer = CharBuffer.allocate(20);
		charBuffer.put("熊少文");
		charBuffer.flip();
		ByteBuffer byteBuffer=encoder.encode(charBuffer);
		for(int i=0;i<byteBuffer.limit();i++) {
			System.out.println(byteBuffer.get(i)+"");
		}
		
	}
}
-------------------------------------------------------
-48
-36
-55
-39
-50
-60

解码

public class CharsetTest{
	public static void main(String[] args) throws Exception {
		Charset charset = Charset.forName("GBK");
		CharsetEncoder encoder = charset.newEncoder();
		
		CharBuffer charBuffer = CharBuffer.allocate(20);
		charBuffer.put("熊少文");
		charBuffer.flip();
		ByteBuffer byteBuffer=encoder.encode(charBuffer);
		for(int i=0;i<byteBuffer.limit();i++) {
			System.out.println(byteBuffer.get(i)+"");
		}
		
		
		CharsetDecoder decoder=charset.newDecoder();
		//byteBuffer.flip();  //解码器不用翻转,
		CharBuffer charBuffer2 = decoder.decode(byteBuffer);
		//charBuffer2.flip();   //也不用翻转
		
		for(int j=0;j<charBuffer2.limit();j++) {
			System.out.print(charBuffer2.get(j));
		}
	}
}
-----------------------------------------------------------------
-48
-36
-55
-39
-50
-60
熊少文

NIO2.0即AIO

      JDK7在java.nio这个工具包里加入了很多新的元素
我们把这些改变叫NIO2.0(即AIO,Asynchronous IO:异步非阻塞IO)和NIO相比,同样有Channel和Buffer, 但没有Selector。

Path雷同于File路径

      Java Path接口是Java NIO 2.0更新的一部分,同Java NIO一起已经包括在Java6和Java7中。Java Path接口是在Java7中添加到java.nio的。
      Path接口位于java.nio.file包中,所以Path接口的完全限定名称为java.nio.file.Path。
      Java Path实例表示文件系统中的路径。一个路径可以指向一个文件或一个目录。路径可以是绝对路径,也可以是相对路径。
      在许多方面,java.nio.file.Path接口类似于java.io.File类,但是有一些细微的差别。不过,在许多情况下,您可以使用Path接口来替换File类的使用。
创建一个Path寮例
为了使用java.nio.file.Path实例必须创建一个Path实例。
您可以使用Paths类(java.nio.file.Paths)中的静态方法来创建路径实例,名为Paths.get()。

public class PathTest {
	public static void main(String[] args) {
		//您可以使用Paths类(java.nio.file.Paths)中的静态方法来创建路径实例,名为Paths.get()。
		Path path=Paths.get("c:\\xiong\\aaa.txt");//相对路径写法与File类一样
		Path path2=Paths.get("c:\\xiong","aaa.txt");
		System.out.println(path+"  "+path2);
		
		Path path3 = Paths.get(".","aaa.txt");    //当前目录下
		Path path4= Paths.get("..","aaa.txt");    //上一级目录下
		System.out.println(path3+"   "+path4);
		
	}

}
------------------------------------------------------------
c:\xiong\aaa.txt  c:\xiong\aaa.txt
.\aaa.txt   ..\aaa.txt

      在Unix系统(Linux、MacOS、FreeBSD等)上,上面的绝对路径可能如下:

Path path = Paths.get("/home/jakobjenkov/myfile.txt");

Java NIO Files

Java NIO Files类(java.nio.file.Files)提供了几种操作文件系统中的文件的方法。
java.nio.file.Files类与java.nio.file.Path实例一起工作。

Files.exists()
Files.exists()方法检查给定的Path在文件系统中是否存在。
可以创建在文件系统中不存在的Path实例。

public class FilesTest {
	public static void main(String[] args) {
		Path path=Paths.get("c:\\xiong\\aaa.txt");
		boolean exist = Files.exists(path, new LinkOption[] {LinkOption.NOFOLLOW_LINKS});
		System.out.println(exist);
	}

}
------------------------------------------------------------------
true

Files.createDirectory()
      Files.createDirectory()方法,用于根据Path实例创建一个新目录,下面是一个Files.createDirectory()例子:

public class FilesTest {
	public static void main(String[] args) {

		Path path=Paths.get("c:\\bbb\\");
		if(!Files.exists(path, new LinkOption[] {LinkOption.NOFOLLOW_LINKS})) {
			try {
				Files.createDirectory(path);
			}catch(IOException e) {
				e.printStackTrace();
			}
		}
		
	}

}

第一行创建表示要创建的目录的Path实例。
在try-catch块中,用路径作为参数调用Files.createDirectory()方法。
如果创建目录成功,将返回一个Path实例,该实例指向新创建的路径。

如果该目录已经存在,则是抛出一个java.nio.file.FileAlreadyExistsException。
如果出现其他错误,可能会抛出IOException。
例如,如果想要的新目录的父目录不存在,则可能会抛出IOException。

Files.copy()
      Files.copy()方法从一个路径拷贝一个文件到另外一个目录:这个方法在File中没有。当需要复制文件时,用这个是十分方便的。

   Path spath = Paths.get("c:\\xiong\\aaa.txt");
   Path dpath = Paths.get("c:\\xiong\\aaa.txt");
   try{
       Files.copy(spath,dpath);
       //Files.copy(spath,dpath,StandardCopyOption.REPLACE_EXISTING);  //覆盖复制
    }catch(IOException e){
       e.printStackTrace();
     }
     

Files.move()
Java NIO Files还包含一个函数,用于将文件从一个路径移动到另一个路径。
移动文件与重命名相同,但是移动文件既可以移动到不同的目录,也可以在相同的操作中更改它的名称。
java.io.File类也可以使用它的renameTo()方法来完成这个操作,但是现在已经在java.nio.file.Files中有了文件移动功能。

Path sourcePath = Paths.get("data/logging-copy.properties");
Path destinationPath = Paths.get("data/subdir/logging-moved.properties");
try {
    Files.move(sourcePath, destinationPath,
            StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
    //移动文件失败
    e.printStackTrace();
}

Files.delete()
Files.delete()方法可以删除一个文件或者目录。

Path path = Paths.get("data/subdir/logging-moved.properties");
try {
    Files.delete(path);
} catch (IOException e) {
    // 删除文件失败
    e.printStackTrace();
}

首先,创建指向要删除的文件的Path。然后调用Files.delete()方法。
如果Files.delete()由于某种原因不能删除文件(例如,文件或目录不存在),会抛出一个IOException。

Files.walkFileTree()
Files.walkFileTree()方法包含递归遍历目录树的功能。
      walkFileTree()方法将Path实例和FileVisitor作为参数。
Path实例指向您想要遍历的目录。
FileVisitor在遍历期间被调用。
FileVisitor接口必须自己实现,并将实现的实例传递给walkFileTree()方法。
在这里插入图片描述
在这里插入图片描述

      在目录遍历过程中,FileVisitor是一一个接口,实现的每个方法都将被调用。
如果不需要实现所有这些方法,那么可以扩展SimpleFileVisitor类,它包含FileVisitor接口中所有方法的默认实现。我们只要覆盖要用到的方法。

public class FilesVisit {
	public static void main(String[] args) {
		Path rootPath = Paths.get("C:\\Users\\Administrator\\Pictures\\");
		String desFile = File.separator+"aaaa.txt";   //前面File类中已介绍,separator自动识别本系统的目录分隔符
		System.out.println(File.separator);
		try {
		    Files.walkFileTree(rootPath, new SimpleFileVisitor<Path>() {

				@Override
				public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
					String stringFile = file.toAbsolutePath().toString();
					if(stringFile.endsWith(desFile)) {
						System.out.println("要找到的文件的位置: "+file.toAbsolutePath());
						return FileVisitResult.TERMINATE;    //找到了,终止程序,TERMINATE是一个枚举变量,可以看盾源代码找的到
					}
					return FileVisitResult.CONTINUE;   //没打到继续找
				}
		    	
			});
		}catch(IOException e) {
			e.printStackTrace();
		}
	}

} 
-----------------------------------------------------------------
\
要找到的文件的位置: C:\Users\Administrator\Pictures\aaaa.txt

FileVisitor实现中的每个方法在遍历过程中的不同时间都被调用:
在访问任何目录之前调用preVisitDirectory()方法。
在访问一个目录之后调用postVisitDirectory()方法。

调用visitFile()在文件遍历过程中访问的每一个文件。
它不会访问目录-只会访问文件。
在访问文件失败时调用visitFileFailed()方法。

这四个方法中的每个都返回一个FileVisitResult枚举实例。
FileVisitResult枚举包含以下四个选项:
CONTINUE 继续,意味着文件的执行应该像正常一样继续。
TERMINATE 终止,意味着文件遍历现在应该终止。
SKIP_SIBLING 跳过同级,意味着文件遍历应该继续,但不需要访问该文件或目录的任何同级。
SKIP_SUBTREE 跳过子级,意味着文件遍历应该继续,但是不需要访问这个目录中的子目录。
这个值只有从preVisitDirectory()返回时才是一个函数。如果从任何其他方法返回,
它将被解释为一个CONTINUE继续。

通过返回其中一个值,调用方法可以决定如何继续执行文件。

递归删除目录

Files.walkFileTree()也可以用来删除包含所有文件和子目录的目录。
Files.delete()方法只会删除一个目录,如果它是空的。
      通过遍历所有目录并删除每个目录中的所有文件(在visitFile())中,然后删除目录本身(在postVisitDirectory()中),您可以删除包含所有子目录和文件的目录。

public class FileDirDelete {
	public static void main(String[] args) {
		Path file= Paths.get("c:\\aaa");
		
		try {
			Files.walkFileTree(file, new SimpleFileVisitor<Path>() {

				@Override
				public FileVisitResult postVisitDirectory(Path dir, IOException e) throws IOException {
					System.out.println("delete dir:"+dir);
					Files.delete(dir);
					return FileVisitResult.CONTINUE;
				}

				@Override
				public FileVisitResult visitFile(Path file, BasicFileAttributes arg1) throws IOException {
					System.out.println("delete file:"+file.toString());
					Files.delete(file);
					return FileVisitResult.CONTINUE;
				}
				
			});
		} catch (IOException e) {
			
			e.printStackTrace();
		}
	}

}
----------------------------------------------------------------
delete dir:c:\aaa\bbb
delete file:c:\aaa\ccc.txt
delete dir:c:\aaa

异步通道文件传输

AIO在channel方面主要引入了三个组件:
AsynchronousFileChannel
AsynchronousSocketChannel (网络编程里我们在玩儿)
AsynchronousServerSocketChannel(网络编程里我们在玩儿)

AsynchronousFileChannel
      在Java 7中,AsynchronousFileChannel被添加到Java NIO。
      AsynchronousFileChannel让读取数据,并异步地将数据写入文件成为可能。
创建一个AsynchronousFileChannel
可以通过它的静态方法open()创建一个AsynchronousFileChannel。

Path path = Paths.get("data/test.xml");
AsynchronousFileChannel fileChannel =
    AsynchronousFileChannel.open(path, StandardOpenOption.READ);

      open()方法的第一个参数是指向与AsynchronousFileChannel相关联的文件的Path实例。第二个参数是一个或多个打开选项,它告诉AsynchronousFileChannel在底层文件上执行哪些操作。
      在本例中,我们使用了StandardOpenOption.READ选项。意味着该文件将以只读的方式被打开。

读取数据

      可以通过两种方式从AsynchronousFileChannel读取数据。
读取数据的每一种方法都调用AsynchronousFileChannel的read()方法之一,它返回一个Future对象

      通过Future阅读数据
从AsynchronousFileChannel读取数据的第一种方法是调用返回Future对象的read()方法。

Future<Integer> operation = fileChannel.read(buffer, 0);
public class AsynchronousFileChannelTest {
	public static void main(String[] args) {
		Path path=Paths.get("c:\\xiong\\aaa.txt");
		try {
			AsynchronousFileChannel channel = AsynchronousFileChannel.open(path, StandardOpenOption.READ);
		    ByteBuffer readBuffer= ByteBuffer.allocate(1024);
		    Future<Integer> futur = channel.read(readBuffer, 0);
		    while(!futur.isDone());//实际上是在等待读完毕,因为是异步操作,所以要这样干,不然执行输出,有时有的,有时没的
		    
		    readBuffer.flip();
		    System.out.println("缓冲区里的数据:"+new String(readBuffer.array()));
		    
		} catch (IOException e) {
			
			e.printStackTrace();
		}
		
	}

}
-------------------------------------------------------------------
缓冲区里的数据:魂牵梦萦富士达魂牵梦萦
fkdsaffjlksadjflasdjfasdl
fdsalkfasdlfskdjas

      这个例子创建了一个AsynchronousFileChannel,然后创建一个ByteBuffer,在调用read()之后,一直循环,直到返回的isDone()方法返回true。
      当然,这不是非常有效地使用CPU,但是需要等到读取操作完成之后才会执行。因为人为地阻塞了主线程,所以读取操作完成后,数据读取到ByteBuffer中,然后把缓冲区中的字符串打印到System.out中。

通过一个CompletionHandler读取数据
      从AsynchronousFileChannel读取数据的第二种方法
调用read()方法,让CompletionHandler作为第四个参数。

public class AsynchronousFileChannelTest {
	public static void main(String[] args) {
	   try {
			Path path=Paths.get("c:\\xiong\\aaa.txt");
			ByteBuffer readBuffer= ByteBuffer.allocate(1024);
			AsynchronousFileChannel channel = AsynchronousFileChannel.open(path, StandardOpenOption.READ);
		    channel.read(readBuffer, 0,readBuffer,new CompletionHandler<Integer,ByteBuffer>(){

				@Override
				public void completed(Integer result, ByteBuffer attachment) {
					System.out.println("result:"+result);
					attachment.flip();
					System.out.println(new String(attachment.array()));
					
				}

				@Override
				public void failed(Throwable exc, ByteBuffer attachment) {
					
					
				}
		    	
		    });
		    
		    
		    
		} catch (IOException e) {
			
			e.printStackTrace();
		}
		
		try {  //设置阴塞主线程1秒,不然立即执行可能看不到输出,主线程就走了
			Thread.sleep(1000);
		}catch(Exception e) {
			e.printStackTrace();
		}
	}

}

写数据

就像阅读一样,您可以通过两种方式将数据写入一个AsynchronousFileChannel。
写入数据的每一种方法都调用异步文件通道的write()方法之一。
通过Future写数据
      AsynchronousFileChannel还允许您异步地写数据。
下面是一个完整的Java AsynchronousFileChannel示例:

public class AsynchronousFileChannelTest {
	public static void main(String[] args) {

		Path path = Paths.get("c:\\xiong\\www.txt");
		ByteBuffer writeBuffer = ByteBuffer.allocate(1024);
		writeBuffer.put("AsynchronousFileChannelTest:测试 ".getBytes());
		System.out.println("写入数据后,缓冲区的position:"+writeBuffer.position());
		writeBuffer.flip();
		System.out.println("写入数据后翻转了,缓冲区的position:"+writeBuffer.position());
		if(!Files.exists(path)) {
			try {
				Files.createFile(path);
				
			}catch(IOException e) {
				e.printStackTrace();
			}
		}

		/*try {
			AsynchronousFileChannel channel = AsynchronousFileChannel.open(path,StandardOpenOption.WRITE);
		    Future<Integer> future=channel.write(writeBuffer,0);
		    while(!future.isDone());
		    System.out.println("写入数据完成!");
		
		} catch (IOException e) {
			e.printStackTrace();
		}*/
		
//不用Future对象写入数据,提高cpu效率		
		try {
			AsynchronousFileChannel channel = AsynchronousFileChannel.open(path,StandardOpenOption.WRITE);
		    channel.write(writeBuffer, 0,writeBuffer,new CompletionHandler<Integer,ByteBuffer>() {

				@Override
				public void completed(Integer result, ByteBuffer attachment) {
					System.out.println("写入数据完成,写入了"+result+"个数据!!!");
					
				}

				@Override
				public void failed(Throwable exc, ByteBuffer attachment) {
					
					
				}
		    	
			});
		
		} catch (IOException e) {
			e.printStackTrace();
		}
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			
			e.printStackTrace();
		}
		 
	}

http://www.niftyadmin.cn/n/5862344.html

相关文章

Canvas进阶-4、边界检测(流光,鼠标拖尾)

1. 什么是边界检测&#xff1f; 在之前的开发中&#xff0c;物体在运动过程中一旦超出画布&#xff0c;就会消失&#xff0c;今天学习如何去检测是否碰到了边界&#xff0c;碰到边界后又会有哪些处理的办法。 边界检测&#xff0c;就是物体运动的限制范围。边界检测的范围&…

[实现Rpc] Dispatcher类的实现 | 开闭原则 | 测试 | 传gitee

目录 程序设计原则 说明 Dispatcher Callback 类 CallbackT 类 Dispatcher 类 测试 client server Debug ⭕参数传递错误 排查方法&#xff1a; 运行 记录&#xff1a; &#xff08;1&#xff09;Dispatcher类的功能&#xff1a; 注册消息的类型。回调函数映射关…

二叉树层序遍历的三种情况(总结)

这道题就是一个比较简单的层序遍历&#xff0c;只需要利用队列存放二叉树结点&#xff0c;队列的size代表每层的节点数也就是平均值的除数&#xff0c;利用一个结果数组记录每层平均值&#xff0c;最后返回。 需要注意的是&#xff0c;平均值定义成double类型。 代码如下&…

通信系统中物理层与网络层联系与区别

在通信系统中&#xff0c;物理层和网络层是OSI&#xff08;开放系统互连&#xff09;模型中的两个重要层次&#xff0c;分别位于协议栈的最底层和第三层。它们在功能、职责和实现方式上有显著的区别&#xff0c;但同时也在某些方面存在联系。以下是物理层与网络层的联系与区别的…

进程(2)

1.进程的消亡 &#xff08;1&#xff09;进程的退出 &#xff08;2&#xff09;进程资源的回收 僵尸进程&#xff1a;进程已经结束&#xff0c;但是未被其父进程回收。 如何避免僵尸进程&#xff1a; 2.函数 &#xff08;1&#xff09;void exit(int status) (2)pid_t wait…

记录:Docker 安装记录

今天在安装 ollama 时发现无法指定安装目录&#xff0c;而且它的命令行反馈内容很像 docker &#xff0c;而且它下载的模型也是放在 C 盘&#xff0c;那么如果我 C 盘空间不足&#xff0c;就装不了 deepseek-r1:70b &#xff0c;于是想起来之前安装 Docker 的时候也遇到过类似问…

AI汽车新风向:「死磕」AI底盘,引爆线控底盘新增长拐点

2025开年&#xff0c;DeepSeek火爆出圈&#xff0c;包括吉利、东风汽车、上汽、广汽、长城、长安、比亚迪等车企相继官宣接入&#xff0c;掀起了“AI定义汽车”浪潮。 而这股最火的AI汽车热潮&#xff0c;除了深度赋能智能座舱、智能驾驶等AI竞争更白热化的细分场景&#xff0…

区块链中的递归长度前缀(RLP)序列化详解

文章目录 1. 什么是RLP序列化&#xff1f;2. RLP的设计目标与优势3. RLP处理的数据类型4. RLP编码规则详解字符串的编码规则列表的编码规则 5. RLP解码原理6. RLP在以太坊中的应用场景7. 编码示例分析8. 总结 1. 什么是RLP序列化&#xff1f; 递归长度前缀&#xff08;RLP&…