Java基础知识——NIO

Java基础知识——NIO,第1张

Java基础知识——NIO

文章目录

一、NIO

1.1 缓冲区(Buffer)1.2 通道(Channel)

1.2.1 通过FileChannel向文件中写入数据1.2.2 通过FileChannel读取文件1.2.3 通过FileChannel赋值文件1.2.4 TransferFrom和TranserTo 1.3 选择器(Selector)1.4 NIO通信实例 二、练习


一、NIO

NIO支持面向缓冲区的、基于通道的IO *** 作。NIO以更高效的方式进行文件的读写 *** 作。传统的IO的read和write只能阻塞执行,线程在读写IO期间不能干其他事情,比如调用socket.read()时,如果服务器一直没有数据传输过来,线程就一直阻塞,但NIO中可以配置socket为非阻塞模式。

NIO的三大核心:Channel(通道)、Buffer(缓冲区)、Selector(选择器)。NIO基于通道和缓冲区进行 *** 作,数据总是从通道读取到缓冲区中,或者从缓冲区写入通道中。选择器用于监听多个通道的事件,如连接请求、数据到达等,因此可以用单个线程就可以监听多个用客户端通道。

NIO是可以做到用一个线程来处理多个 *** 作的。假设有1000个请求,根据实际情况,可以分配20或者80个线程进行处理。不像BIO,一定要分配1000个线程来进行处理。

1.1 缓冲区(Buffer)

容量(capacity):作为一个内存块,缓冲区具有一定的大小,成为容量,缓冲区容量不能为负,并且创建后不能更改。限制(limit):表示缓冲区中可以 *** 作数据的大小,limit后数据不能进行读写。写入模式下,限制等于buffer的容量;读取模式下,限制等于写入的数据量位置(position):下一个要读取或写入的数据的索引。标记(mark)与重置(reset):标记是一个索引,通过mark()方法指定Buffer中一个特定的position,之后可以通过调用reset()方法恢复到这个position

常见方法:

Buffer clear():清空缓冲区并返回对缓冲区的引用Buffer flip():将缓冲区的界限设置为当前位置,并将当前位置重置为0int capacity():返回缓冲区的容量大小boolean hasRemaining():判断缓冲区中是否还有元素int limit():返回缓冲区的界限的位置Buffer limit(int n):设置缓冲区界限为n,返回一个具有新limit的缓冲区对象Buffer mark():对缓冲区设置标记int position():返回缓冲区的当前位置Buffer position(int n):设置当前缓冲区的位置为n,并返回修改后的Buffer对象int remaining():返回position和limit之间的元素个数Buffer reset():将position转到以前设置的mark位置Buffer rewind():将位置设置为0,并取消设置mark

获取Buffer中的数据:

get():读取单个字节get(byte[] dst):批量读取多个字节到dst中get(int index):读取指定索引位置的字节(不会移动position)

向Buffer中充入数据:

put(byte b):将给定的单个字节写入缓冲区的当前位置put(byte[] src):将src中的字节写入缓冲区的当前位置put(int index,byte b):将指定字节写入缓冲区的索引位置(不会移动position)

package NIO;

import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;

public class BufferTest {
    public static void main(String[] args) {
        ByteBuffer buffer = ByteBuffer.allocate(10);
        System.out.println(buffer.position());//0
        System.out.println(buffer.limit());//10
        System.out.println(buffer.capacity());//10

        //2、使用put向缓冲区充入数据
        String name = "itheima";
        buffer.put(name.getBytes());
        System.out.println(buffer.position());//7
        System.out.println(buffer.limit());//10
        System.out.println(buffer.capacity());//10

        //3、Buffer flip():可读模式
        buffer.flip();
        System.out.println(buffer.position());//0
        System.out.println(buffer.limit());//7
        System.out.println(buffer.capacity());//10

        //4、get获取数据
        char ch = (char)buffer.get();
        System.out.println(ch);
        System.out.println(buffer.position());//1
        System.out.println(buffer.limit());//7
        System.out.println(buffer.capacity());//10
    }
}

1.2 通道(Channel)

通道可以同时进行读写,而流只能读或者只能写通道可以实现异步读写数据通道可以从缓冲读数据,也可以写数据到缓冲

Channel在NIO中是一个接口:public interface Channel extends Closeable{}

常用的Channel实现类:

FileChannel:用于读取、 写入、映射和 *** 作文件的通道DatagramChannel:通过UDP读写网络中的数据通道SocketChannel:通过TCP读写网络中的数据ServerSocketChannel:可以监听新进来的TCP连接,对每一个新进来的连接都会创建一了SocketChannel

FileChannel的常用方法:

int read(ByteBuffer dst):从Channel中读取数据到ByteBufferlong read(ByteBuffer[] dsts):将Channel中的数据读取到数组中int write(ByteBuffer src):将ByteBuffer中的数据写入到Channellong write(ByteBuffer[] srcs):将ByteBuffer[]中的数据写入Channellong position():返回此通道的文件位置FileChannel position(long p):设置此通道的文件位置long size():返回此通道的文件的当前大小FileChannel truncate(long s):将此通道的文件截取为给定的大小void force(boolean metaData):强制将所有对此通道的文件更新写入到存储设备中 1.2.1 通过FileChannel向文件中写入数据

package NIO;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class FileChannelTest1 {
    public static void main(String[] args) {
        try {
            // 1、字节输出流定位到目标文件
            FileOutputStream fos = new FileOutputStream("c:\d.txt");
            //2、得到字节输出流对应的通道
            FileChannel channel = fos.getChannel();
            //3、分配缓冲区
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            buffer.put("hello".getBytes());
            //4、向缓冲区写入数据
            buffer.flip();
            channel.write(buffer);
            channel.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

1.2.2 通过FileChannel读取文件
package NIO;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class FileChannelTest2 {
    public static void main(String[] args) {
        try {
            //1、定义一个文件字节输入流
            FileInputStream is = new FileInputStream("c:\d.txt");
            //2、得到文件字节输入流的文件通道
            FileChannel channel = is.getChannel();
            //3、定义一个缓冲区
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            //4、读取数据到缓冲区
            channel.read(buffer);
            buffer.flip();
            //5、输出数据
            String rs = new String(buffer.array(),0,buffer.remaining());
            System.out.println(rs);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

1.2.3 通过FileChannel赋值文件
package NIO;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class FileChannelTest3 {
    public static void main(String[] args) {
        File srcFile = new File("c:\d.txt");
        File desFile = new File("c:\e.txt");
        try {
            FileInputStream fis = new FileInputStream(srcFile);
            FileOutputStream fos = new FileOutputStream(desFile);
            FileChannel isChannel = fis.getChannel();
            FileChannel osChannel = fos.getChannel();
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            while(true){
                //可能要读写多次,每次要先清空缓冲区再写入数据
                buffer.clear();
                int flag = isChannel.read(buffer);
                if(flag == -1)
                    break;
                //已经读取了数据
                buffer.flip();
                osChannel.write(buffer);
                isChannel.close();
                osChannel.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

1.2.4 TransferFrom和TranserTo
package NIO;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;

public class FileChannelTest4 {
    public static void main(String[] args) throws IOException {
        FileInputStream fis = new FileInputStream("c:\d.txt");
        FileChannel isChannel = fis.getChannel();
        FileOutputStream fos = new FileOutputStream("c:\e.txt");
        FileChannel osChannel = fos.getChannel();
        //第一种
        osChannel.transferFrom(isChannel,isChannel.position(),isChannel.size());
        //第二种
        isChannel.transferTo(isChannel.position(),isChannel.size(),osChannel);
        isChannel.close();
        osChannel.close();
    }
}

1.3 选择器(Selector)

Selector是SelectableChannel对象的多路复用器,Selector可以同时监控说个SelectorChannel的IO状况,也就是说,利用一个Selector可使一个单独的线程管理多个Channel。Selector是非阻塞IO的核心。

创建Selector:Selector selector = Selector.open();

向选择器注册通道:SelectableChannel.register(Selector sel , int ops)

//1、获取通道
ServerSocketChannel ssChannel = ServerSocketChannel.open();
//2、切换非阻塞模式
ssChannel.configureBlocking(false);
//3、绑定连接
ssChannel.bind(new InetSocketAddress(9898));
//4、获取选择器
Selector selector = Selector.open();
//5、将通道注册到选择器上,并且指定“监听接收事件”
ssChannel.register(selector , SelevtionKey.OP_ACCEPT);

监听的事件:

读:SelectionKey.OP_READ写:SelectionKey.OP_WRITE连接:SelectionKey.OP_CONNECT接收:SelectionKey.OP_ACCEPT若注册时不止监听一个事件,则可以用位或 *** 作符连接 1.4 NIO通信实例

服务器端:

package NIO;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;

public class Server {
    public static void main(String[] args) throws IOException {
        //1、获取通道
        ServerSocketChannel ssChannel = ServerSocketChannel.open();
        //2、切换为非阻塞方式
        ssChannel.configureBlocking(false);
        //3、绑定连接的端口
        ssChannel.bind(new InetSocketAddress(9999));
        //4、获取选择器
        Selector selector = Selector.open();
        //5、将通道都注册到选择器上,并且开始制定监听接收事件
        ssChannel.register(selector, SelectionKey.OP_ACCEPT);
        //6、使用选择器轮询就绪事件
        while(selector.select()>0){
            //7、选取选择器中的所有注册的通道中已就绪好的事件
            Iterator it = selector.selectedKeys().iterator();
            //8、开始遍历这些准备好的事件
            while(it.hasNext()){
                //提取这个事件
                SelectionKey sk = it.next();
                //9、判断这个事件具体是什么
                if(sk.isAcceptable()){
                    //10、直接获取当前接入的客户端通道
                    SocketChannel schannel = ssChannel.accept();
                    //11、切换成非阻塞模式
                    schannel.configureBlocking(false);
                    //12、将本客户端通道注册到选择器
                    schannel.register(selector,SelectionKey.OP_READ);
                }else if(sk.isReadable()){
                    //13、读取当前选择器上的读就绪事件
                    SocketChannel sChannel = (SocketChannel) sk.channel();
                    //14、读取数据
                    ByteBuffer buf = ByteBuffer.allocate(1024);
                    int len = 0 ;
                    while((len = sChannel.read(buf))>0){
                        buf.flip();
                        System.out.println(new String(buf.array(),0,len));
                        buf.clear();
                    }
                }
                it.remove();//处理完毕之后需要移出当前事件
            }
        }
    }
}

客户端:

package NIO;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;

public class Client {
    public static void main(String[] args) throws IOException {
        //1、获取通道
        SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",9999));
        //2、切换成非阻塞模式
        sChannel.configureBlocking(false);
        //3、分配指定的缓冲区大小
        ByteBuffer buf = ByteBuffer.allocate(1024);
        //4、发送数据
        Scanner sc = new Scanner(System.in);
        while(true){
            System.out.println("请输入:");
            String msg = sc.nextLine();
            buf.put(msg.getBytes());
            buf.flip();
            sChannel.write(buf);
            buf.clear();
        }
    }
}

二、练习

1、如果是 Windows 系统,以递归方式读取 C 盘中所有的目录和文件,并打印出每个文 件的大小和每个目录中文件的数量

package File;

import java.io.File;

public class Test12_01 {
    public static void main(String[] args) {
        test("C:\",0);
    }

    private static void test(String fileDir , int num){
        File file = new File(fileDir);
        File[] files = file.listFiles();
        if(files == null)
            return;
        else {
            for(File f : files){
                if(f.isFile()){
                    System.out.println(f.getName()+"的大小为:"+f.length());
                    num++;//目录中文件数量加1
                }else{
                    test(f.getName(),0);
                }
            }
        }
        System.out.println(file.getName()+"中文件的数量为:"+num);
    }
}

2、在网络上下载一个大文件(任何文件都可以,可以是 exe 文件,也可以是日志、电影 或其它类型的大文件),然后使用 Java 标准 I/O 模型读取并复制成另一份文件: 用字节流实现,用字符流实现。

字节流:

package File;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class Test12_02 {
    public static void main(String[] args) throws IOException {
        FileInputStream fis = new FileInputStream("D:.rmvb");
        FileOutputStream fos = new FileOutputStream("F:.rmvb");
        byte[] bytes = new byte[1024];
        int len;
        while((len = fis.read(bytes))!=-1)
            fos.write(bytes,0,len);
        fos.close();;
        fis.close();;
    }
}


字符流:

package File;

import java.io.*;

public class Test12_02 {
    public static void main(String[] args) throws IOException {
        FileReader fr = new FileReader("D:.rmvb");
        FileWriter fw = new FileWriter("F:.rmvb");
        int len = 0;
        char[] buf = new char[1024];
        while((len = fr.read(buf))!=-1){
            fw.write(buf,0,len);
        }
        fw.flush();
        fw.close();
        fr.close();
    }
}

3、针对练习 2,用 NIO 重新实现,然后比较一下执行效率

package File;

import java.io.*;
import java.nio.channels.FileChannel;

public class Test12_02 {
    public static void main(String[] args) throws IOException {
        FileInputStream fis = new FileInputStream("D:.rmvb");
        FileChannel isChannel = fis.getChannel();
        FileOutputStream fos = new FileOutputStream("F:.rmvb");
        FileChannel osChannel = fos.getChannel();
        osChannel.transferFrom(isChannel,isChannel.position(),isChannel.size());
        isChannel.close();
        osChannel.close();
    }
}

4、创建一个文本文件,然后随意输入一些格式固定的编号,例如: 100011720988、100009077459 100009077473 … 然后将下面的这个 URL 地址用这些编号替换:https://item.csdn.com/xxx.html,也 就是将 xxx 替换为对应的编号,例如:https://item.csdn.com/100011720988.html。 最后再借助开源的二维码组件,将这些 URL 批量生成为二维码图片(图片存储目录自 己指定)。

package File;

import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;

import java.io.*;
import java.nio.file.Path;
import java.util.HashMap;

public class Test12_04 {
    public static void main(String[] args) throws IOException, IOException {

        

        File file = new File("F:\number.txt");
        FileWriter writer = null;
        writer = new FileWriter(file,false);
        if (file.exists()){
            System.out.println("创建成功!");
        }

        String result = "100011720988";
        writer.append(result);
        writer.flush();
        
        InputStreamReader isr = new InputStreamReader(new FileInputStream(file), "utf-8");
        BufferedReader br = new BufferedReader(isr);
        String lineTxt = null;
        String line1 = br.readLine();
        System.out.println(line1);

        
        String str = "https://item.csdn.com/"+result+".html";
        writer.append(str);
        writer.flush();
        String line2 = br.readLine();
        System.out.println(line2);


        

        int width  = 300;  //二维码图片的宽度
        int height = 300; //二维码图片的高度
        String format = "png";  //二维码格式
        String content = line2;

        //定义二维码内容参数
        HashMap hints = new HashMap();
        //设置字符集编码格式
        hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
        //设置容错等级,在这里我们使用M级别
        hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);
        //设置边框距
        hints.put(EncodeHintType.MARGIN, 2);
        try {
            //指定二维码内容
            BitMatrix bitMatrix = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, width, height,hints);
            //指定生成图片的保存路径
            Path newfile = new File("F:\imooc.png").toPath();
            //生成二维码
            MatrixToImageWriter.writeToPath(bitMatrix, format, newfile);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

}

欢迎分享,转载请注明来源:内存溢出

原文地址: http://www.outofmemory.cn/zaji/5717176.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-18
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存