zoukankan      html  css  js  c++  java
  • 12.9 NIO


    当BufferedReader读取输入流中的数据,如果没有读到有效数据,程序将阻塞该线程的执行(使用InputStream的read()方法从流中读取数据时,如果数据源中没有数据,它也会阻塞线程),也就是传统的输入流、输出流都是阻塞式输入、输出。不仅如此传统的输入流、输出流都是通过字节的移动来处理的(即使不直接去处理字节流,但底层的实现还是依赖于字节处理),也就是说,面向流的输入输出系统一次只能处理一个字节,因此面向流的输入、输出效率不高。
    JDK 1.4开始,Java提供一些改进的输入/输出新功能,这些功能被称为新IO(New IO,简称NIO),新增了许多输入输出类,这些类都被放在java.nio包及其子包下。

    一、Java新IO概述

    新IO和传统的IO有相同的目的,都是用于进行输入/输出功能,但新IO使用了不同的方式来处理处理输入/输出,新IO采用内存映射文件的方式来处理输入/输出,新IO将文件或文件的一段区域映射到内存中,这样就可以像访问内存一样来访问文件了(这种方式模拟了操作系统上的虚拟内存的概念),通过这种方式来进行输入/输出比传统的输入/输出要快得多。
    下面示意图展示了Java新IO包和各种类之间的树关系:

    1.1 Java中NIO相关包介绍

    Java中NIO相关的包如下:
    java.nio包:主要提供了一些和Buffer相关的类。
    java.nio.channels包:主要包括Channel和Selector相关的类。
    java.nio.charset包:主要包含和字符集相关的类。
    java.nio.channels.spi包:主要包含提供Channel服务的类。
    java.nio.charset.spi包:主要包含提供字符集服务的相关类。

    1.2 新IO中的核心对象Channel(通道)和Buffer(缓冲)

    Channel(通道)和Buffer(缓冲)是新IO中的两个核心对象,Channel是对传统输入/输出系统中里的模拟,在新IO系统中所有数据都需要通过通道传输;Channel与传统的InputStream、OutputStream最大的区别在于它提供了一个map方法,通过该map方法可以直接将“一块数据”映射到内存中。如果说传统的输入/输出系统是面向流的处理,而新IO则是面向块的处理。
    Buffer可以被理解成一个容器,它的本质是一个数组,发送到Channel中的所有对象都必须首先放到Buffer中,而从Channel中读取的数据也必须先读到Buffer中。此处的Buffer有点类似于前面我们介绍的“竹筒”,但该Buffer既可以像前面那样一次、一次去Channel中取水,也允许使用Channel直接将文件的某块数据映射成Buffer。

    1.3 其他类:Charset类、Selector类

    除了Channel和Buffer之外,新IO还提供了用于将UNICODE字符串映射成字节序列以及逆映射操作的Charset类,还提供了用于支持非阻塞式输入/输出的Selector类。

    二、使用Buffer

    2.1 Buffer的种类——class XxxBuffer extends Buffer;

    从内部结构上来看,Buffer就像一个数组,它可以保存多个类型相同的数据。Buffer是一个抽象类,其最常用的子类是ByteBuffer,它可以在底层字节数组上进行get/set操作,除了ByteBuffer之外,其他基本数据类型(boolean除外)都有相应的Buffer类:ByteBuffer、CharBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer。

    2.2 获取Buffer对象

    除了ByteBuffer之外,上面的Buffer都采用相同或相似的方法来管理数据,只是各自管理数据的类型不同而已。这些Buffer没有提供构造器,只提供如下方法来获取一个Buffer对象:

    static XxxBuffer allocate(int capacity)
    //allocate拨…(给); 划…(归); 分配…(给);
    

    ByteBuffer和CharBuffer用得最多,其他Buffer子类用的较少。ByteBuffer类还有一个子类:MappedByteBuffer,它用于表示Channel将磁盘文件的部分或全部内容映射到内存中得到的结果,通常MappedByteBuffer对象由Channel的map()方法返回

    2.3 Buffer的三个重要概念

    ★容量(capacity):缓冲区的 容量(capacity) 表示该Buffer的最大数据容量,即最多可以存储多少数据。缓冲区的容量不可能为负值,在创建后也不能改变。
    ★界限(limit):第一个不应该被读出或者写入的缓冲区位置索引。也就是说,位于limit后的数据既不可被读,也不可被写。
    ★位置(position):用于指明下一个可以被读出的或者写入的缓冲区位置索引(类似于IO流中的记录指针)。当使用Buffer从Channel中读取数据时,position的值恰好等于已经读到了多少数据。当刚刚新建一个Buffer对象时,其position为0,如果从Channel中读取了2个数据到该Buffer中,则postion为2,指向Buffer中第三个(第一个位置的索引为0)位置。
    除此之外,Buffer还支持一个可选的标记(mark,类似于传统IO流中的mark),Buffer允许直接将position定位到mark处,这些值满足的关系是:

    0<=mark<=position<=limit<=capacity
    

    如图显示了某个Buffer读入了一些数据后的示意图:

    2.4 Buffer装入数据,然后输出数据的过程

    Buffer的主要作用就是装入数据,然后输出数据(其作用类似于输入输出流的水管),开始时Buffer的position为0,limit为capacity,程序通过put()方法像Buffer中放入一些数据(或则从Channel中取出一些数据),没放入一些数据,Buffr的position位置相应地向后移动一些位置。
    Buffer装入数据的示意图:

    当Buffer装入数据结束后,调用Buffer的flip()方法,该方法将limit设置为position所在位置,并将position设置为0,这使得Buffer的读写指针又移到开始的位置。也即是说,Buffer调用flip()方法之后,Buffer为输出数据做好了准备;

    当Buffer输出数据结束后,Buffer调用clean()方法,clear()方法不是清空Buffer数据,它仅仅只是将position置为0,将limit置为capacity,这样再次向Buffer中装入数据做准备。

    2.5 Buffer常用方法

    Buffer的基本常用方法:
    (1)int capacity():返回Buffer的capacity大小。
    (2)boolean hasRemaining():判断当前位置(position)和界限之间是否还具有元素可供处理。
    (3)int limit():返回Buffer界限(limit)的位置。
    (4)Buffer mark():设置Buffer的mark位置,它只能在0和position之间做mark。
    (5)int position():返回position的值。
    (6)Buffer position(int newPs):设置Buffer的position,并返回position被修改后的Buffer对象。
    (7)int remianing():返回当前位置和界限之间元素个数。
    (8)Buffer reset:将位置(position)转到mark所在位置。
    (9)Buffer rewind():将位置position设置为0,取消设置的mark。
    Buffer的所有子类还提供了两个重要的方法:put()、get()方法,用于向Buffer中放入数据和从Buffer中取出数据。支持单个数据访问,也支持毗连数据访问(以数组作为参数)。
    当使用put()、get()方法访问Buffer中的数据时,分为相对和绝对两种:
    (1)相对(Relative):从Buffer的当前position处开始读取或写入数据,然后将位置position的值按处理后的元素的个数增加。
    (2)绝对(Absolute):直接根据索引向Buffer中读取或写入数据,使用绝对的方式访问Buffer里的数据时,并不会影响位置(position)的值。

    2.6 Buffer使用示例

    package section9;
    
    import java.nio.CharBuffer;
    
    public class BufferTest
    {
        public static void main(String[] args)
        {
            //创建Buffer,准备装入数据
            CharBuffer buff=CharBuffer.allocate(8);//①
            System.out.println("Buffer的容量capacity:"+buff.capacity());//8
            System.out.println("Buffer的界限(limit):"+buff.limit());//8
            System.out.println("Buffer的位置(position):"+buff.position());//0
    
            //放入元素
            buff.put('a');
            buff.put('b');
            buff.put('c');//②
            System.out.println("加入三个元素后,position="+buff.position());//3
    
            //调用flip()方法,Buffer为输出数据准备阶段
            buff.flip();//③
            System.out.println("执行flip()方法后,limit="+buff.limit());//3
            System.out.println("执行flip()方法后,position="+buff.position());//0
            //取出第一个元素
            System.out.println("第一个元素(position=0):"+buff.get());//④  输出为'a'
            System.out.println("取出第一个元素后,position="+buff.position());//1
    
            //调用clear()方法,为下次装入数据做好准备
            buff.clear();//⑤
            System.out.println("执行clear()方法后,limit="+buff.limit());//8
            System.out.println("执行clear()方法后,position="+buff.position());//0
            System.out.println("执行clear()方法后,Buffer中的内容并没有被清除,第三个元素为"+buff.get(2));//c
            System.out.println("执行绝对读取后,position="+buff.position());//0
        }
    }
    

    下面分析整个程序运行过程:
    1、调用CharBuffer的静态方法allocate()创建一个capacity为8的CharBuffer,此时capacity=8,position=0,limit=0,进入准备装入数据的阶段:

    2、代码2处王Buffer中装入三个元素,放入元素后的CharBuffer效果图如下所示:

    3、代码3处,调用Buffer的flip()方法,该方法将把limit设为position处,再把position设置为0:

    当Buffer调用flip()方法之后,limit就就移动到原来的position所在的位置,这时相当于把Buffer中没有数据的存储空间"封印"起来,从而避免读取Buffer数据时读取到null值。
    4、代码4处取出第一个元素,取出第一个元素后position向后移动一位,也就是position等于1.

    5、代码5处,Buffer调用clear()放啊,将position设为0,将limit设为与capacity相同。执行clear()方法后的Buffer的示意图如下所示:

    6、执行代码6处依然可以取出位置2的值,也就是字符'c'。代码6处根据索引来取值,属于绝对方式的get(),所以不会影响到position的位置。

    2.7★ Buffer的拓展

    通过allocate()只能创建普通Buffer,ByteBuffer还提供了一个allocateDirect()方法来创建直接Buffer,直接Buffer的创建成本比普通Buffer的创建成本高,但直接Buffer的读取效率更高。
    直接Buffer只适用于生存期长的Buffer,而不适用于短期、一次性用完就丢弃的Buffer。且只有ByteBuffer才提供allocateDirect()方法,所以只能再ByteBuffer的级别上创建直接Buffer。如果希望使用其他类型,则应该将该Buffer转换为其他类型的Buffer。
    直接Buffer的用法上和普通Buffer用法基本相同,没有太大区别

    三、使用Channel

    3.1 Channel与传统流对象的区别

    Channel类似于传统的流对象,但与传统的流不同的是,Channel有两个主要的区别:
    1、Channel可以直接将指定文件的部分或全部映射成Buffer。
    2、程序不能直接访问Channel中的数据,包括读取、写入都不行,Channel只能与Buffer进行交互。
    也就是说,如果要从Channel中取得数据,必须先用Buffer从Channel中取出一些数据,然后让程序从Buffer中取出这些数据;如果要将程序中的数据写入Channel,一样先让程序将数据放入Buffer中,程序再将Buffer里的输入写入Channel中。

    3.2 Channel及其实现类

    Channel是一个接口,位于java.nio.channels包下,系统为该接口提供了DatagramChannel、 FileChannel、Pipe.SinkChannel、Pipe.SourceChannel、SelectableChannel、ServerSocketChannel, SocketChannel等实现类,本节主要介绍FileChannel的用法,根据这些Channel的名字我们不难发现新IO里的Channel是按功能来划分的,例如Pipe.SinkChannel、Pipe.SourceChannel用于支持线程之间通信的管道Channel,而ServerSocketChannel、SocketChannel则是用于支持TCP网络通信的Channel。

    3.3 Channel获取

    所有Channel都不应该通过构造器来直接创建,而是通过传统节点InputStream、OutputStream的getChannel()方法返回对应的Channel,不同节点流获取的Channel不一样。例如FileInputStream、FileOutputStream的getChannel()返回的是FileChannel,而PipedInputStream、PipedOutputStream的getChannel()返回的是Pipe.SinkChannel、Pipe.SourceChannel。

    3.4 Channel的常用方法

    1、map():用于将Channel对应的部分或全部数据映射成ByteBuffer;
    方法签名->MappedByteBuffer map(FileChannel.MapMode mode,long position,long size),第一个参数执行映射时的模式,分别有只读、读写等模式;第二个、第三个参数用于控制将Channel的哪些数据映射成ByteBuffer.
    2、read()和write():都有一系列的重载形式,这些方法用于从Buffer中读取数据或向Buffer中写入数据。

    3.5 从FileInputStream和FileOutputStream中获取FileChannel应用举例

    下面程序示范了直接将FileChannel的全部数映射成ByteBuffer的效果:

    package section9;
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.nio.CharBuffer;
    import java.nio.MappedByteBuffer;
    import java.nio.channels.FileChannel;
    import java.nio.charset.Charset;
    import java.nio.charset.CharsetDecoder;
    
    public class FileChannelTest
    {
        public static void main(String[] args)
        {
            File f=new File("src//section9//FileChannelTest.java");
            System.out.println(f.length());//返回文件内容的长度:1045
            try(
                    //创建FileInputStream,以该文件输入流创建FileChannel
                    var inChannel=new FileInputStream(f).getChannel();
                    //以文件输出流创建FileChannel,用于控制输出
                    var outChannel=new FileOutputStream("src//section9//a.txt").getChannel()
                    )
            {
                //将FileChannel里的全部数据全部映射成ByteBuffer
                MappedByteBuffer buffer=inChannel.map(FileChannel.MapMode.READ_ONLY,0,f.length());//代码1
                for(int i=0;i<20;i++)//每个转义字符转换占两个位置,一个字母占一个byte
                {System.out.print((char)buffer.get());}
                System.out.println("
    "+buffer.position());
                buffer.position(0);
                System.out.println(buffer.position());
                //使用GBK的字符集创建解码器
                Charset charset=Charset.forName("utf-8");
                //直接将Buffer中的数据全部输出
                outChannel.write(buffer);//代码2
                //再次调用buffer中的clear()方法,复原limit、position
                buffer.clear();
                //创建解码器(CharsetDecoder)对象
                CharsetDecoder decoder=charset.newDecoder();
                //使用解码器将ByteBuffer转换成CharBuffer
                CharBuffer charBuffer=decoder.decode(buffer);
                //CharBuffer的toString方法可以获取对应的字符串
                System.out.println(charBuffer);
    
            }
            catch(IOException ioe)
            {
                ioe.printStackTrace();
            }
    
        }
    }
    2159
    package section9;
    i
    20
    0
    package section9;
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.nio.CharBuffer;
    ...
    

    上面分别使用了FileInputStream、FileOutputStream来获取Channel,虽然FileChannel既可以读也可以写入,但是FileInputSream获取的FileChannel只能读,而FileOutputStrem获取的FileChannel只能写/代码1处直接将Channel中的全部数据映射成ByteBuffer,然后代码2处直接将整个ByteBuffer的全部数据写入一个FileChannel中,这就完成了文件的复制。
    程序后面为了将FileChannelTest.java文件中的内容全部打印出来,使用了Charset类和CharsetDecoder类将ByteBuffer全部转换成CharSetBuffer。后面会详细介绍。

    3.6 RandomAccessFile获取FileChannel应用举例

    在RandomAccessFile中也包含一个getChannel()方法,RandomAccessFile返回得FileChannel是只读还是读写,则取决于RandomAccessFile打开文件得模式,例如下面程序将会对a.txt文件得内容进行复制,最佳在该文件后面。

    package section9;
    
    import java.io.File;
    import java.io.IOException;
    import java.io.RandomAccessFile;
    import java.nio.ByteBuffer;
    import java.nio.channels.FileChannel;
    
    public class RandomAccessFileChannelTest
    {
        public static void main(String[] args)
                throws IOException
        {
            var f=new File("src//section9//a.txt");
            try(
                    //创建一个RandomAccessFile对象
                    var raf=new RandomAccessFile(f,"rw");
                    //获取RandomAccessFile对应得Channel
                    FileChannel randomChannel=raf.getChannel()
                    )
            {
                //将Channel中得所有数据全部映射成ByteBuffer
                ByteBuffer buffer=randomChannel.map(FileChannel.MapMode.READ_ONLY,0,f.length());
                //把Channel得记录指针移动到最后
                randomChannel.position(f.length());
                //将buffer中得所有数据全部是输出
                randomChannel.write(buffer);
            }
        }
    }
    

    上面得程序将Channel得记录指针移动到Channel得最后,从而让程序指定ByteBuffer得数据追加到Channel得后面。每次运行运行上面得程序都会将a.txt文件得内容全部复制一遍,并将全部得内容追加到文件得后面。

    3.7 Channel通道可以像传统IO一样多次重复读写数据

    如果习惯了传统IO的“用竹筒多次重复取水”的过程,或者担心Channel对应的文件过大,使用map()方法一次将所有文件内容全部映射到内存中引起性能下降,也可以使用Channel和Buffer传统的“用竹筒多次取水”的方式:

    package section9;
    
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.nio.ByteBuffer;
    
    
    public class ReadFile
    {
        public static void main(String[] args)
                throws IOException
        {
            try(
                    //创建文件输入流
                    var fis=new FileInputStream("src//section9//ReadFile.java");
                    //创建文件输出流
                    var fos=new FileOutputStream("src//section9//b.txt");
                    //创建一个FileChannel
                    var fcIn=fis.getChannel();
                    var fcOut=fos.getChannel()
                    )
            {
                //定义一个Buffer对象,用于重复取水
                ByteBuffer buff=ByteBuffer.allocate(256);
                //将FileChannel中的全部数据放入ByteBuffer中
                while(fcIn.read(buff)!=-1)
                {
                    //锁定Buffer的空白区域
                    buff.flip();//将limit移动带position处
                    //将buffer中的数据写入输出Channel中
                    fcOut.write(buff);
                    //将buffer初始化,为下一次读取数据做准备
                    buff.clear();
                }
            }
        }
    }
    

    上面的程序将生成一个b.txt文件,复制了Read.java文件的内容。
    下面程序改为打印出ReadFile.java文件的内容

    package section9;
    
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.nio.ByteBuffer;
    import java.nio.CharBuffer;
    import java.nio.charset.Charset;
    import java.nio.charset.CharsetDecoder;
    
    
    public class ReadFile
    {
        public static void main(String[] args)
                throws IOException
        {
            try(
                    //创建文件输入流
                    var fis=new FileInputStream("src//section9//ReadFile.java");
                    //创建文件输出流
                    var fos=new FileOutputStream("src//section9//b.txt");
                    //创建一个FileChannel
                    var fcIn=fis.getChannel();
                    var fcOut=fos.getChannel()
                    )
            {
                //定义一个Buffer对象,用于重复取水
                ByteBuffer bbuff=ByteBuffer.allocate(256);
                //将FileChannel中的全部数据放入ByteBuffer中
                while(fcIn.read(bbuff)!=-1)
                {
                    //锁定Buffer的空白区域
                    bbuff.flip();//将limit移动带position处
    
                    //创建Charser对象
                    Charset charset=Charset.forName("UTF-8");
                    //创建解码器对对象
                    CharsetDecoder decoder=charset.newDecoder();
                    //将ByteBuffer中的内容转码
                    CharBuffer cbuffer=decoder.decode(bbuff);
                    //打印数
                    System.out.print(cbuffer);
                    //将buffer中的数据写入输出Channel中
                    fcOut.write(bbuff);
    
    //                //将buffer中的数据写入输出Channel中—
    //                fcOut.write(bbuff);
    
                    //将buffer初始化,为下一次读取数据做准备
                    bbuff.clear();
                }
            }
        }
    }
    

    上面代码虽然使用了FileChannel和Buffer来读取文件,但处理方式使用了InputStream、Byte[]来读取文件的方式几乎一样,都是采用"用竹筒多次重复取水"的方式。但Buffer的flip()和clear()两个方法,程序处理起来也比较方便。每次读写数据后调用flip()方法将没有数的区域封印起来,避免程序从Buffer中取出null值;数据取出后立即调用clear方法将Buffer的position取为0,为下一次读取数据做好准备。

    四、字符集和Charset

    4.1 解码和编码简介

    通常而言,把明文的字符串序列转换成计算机理解的字节序列(二进制文件,普通人看不懂)成为编码,把字节序列转化成普通人能看懂的明文字符串称为解码

    计算机底层是没有文本文件、图片文件之分的,它只是忠诚地记录每个文件的二进制序列而已,当需要保存文本文件时,程序先把文件中的每个字符翻译成二进制序列;当需要读取文本文件时,程序必须把二进制序列转化为一个个的字符。
    Java默认使用Unicode字符集,但很多操作系统并不采用Unicode字符集,那么从系统中读取数据到Java程序中时,就可能出现乱码问题。

    4.2 Charset介绍和常用字符集

    JDK1.4提供了Charset处理字节序列和字符序列之间的转换关系,该类用于创建解码器和编码器,还提供Charset所支持字符集方法,Charset类是不可变。
    Charset类提供了一个availableCharsets()静态方法来获取当前JDK所支持的所有字符集。所以程序可以使用以下程序来获取JDK所支持的全部字符集。

    package section9;
    
    import java.nio.charset.Charset;
    import java.util.SortedMap;
    
    public class CharsetTest
    {
        public static void main(String[] args)
        {
            //获取Java支持的全部字符集
            SortedMap<String, Charset> map=Charset.availableCharsets();
            for(var alias:map.keySet())
            {
                //输出字符集的别名和对应的Charset对象
                System.out.println(alias+"--->"+map.get(alias));
            }
        }
    }
    Big5--->Big5
    Big5-HKSCS--->Big5-HKSCS
    CESU-8--->CESU-8
    EUC-JP--->EUC-JP
    EUC-KR--->EUC-KR
    GB18030--->GB18030
    GB2312--->GB2312
    ...
    

    上面程序SortedMap<String, Charset> map=Charset.availableCharsets();获取了Java所支持的全部字符集,并用遍历的方式打印了所有字符集的别名(字符集的字符串名称)和Charset对象。从上面的程序可以看出每个字符集都有一个字符串名称,也成为字符串的别名。
    对于中国程序员,有几个常用字符集:

    字符集 介绍
    GBK 简体中文字符集
    BIG5 繁体字中文字符集
    ISO-8859-1 ISO拉丁字母表,也叫作ISO-LATIN-1
    UTF-8 8位UCS转换格式
    UTF-16BE 16位UCS转换格式,Big-endian(最低地址存放高位字节)字节顺序
    UTF-16LE 16位UCS转换格式,Little-endian(最高地址存放低位字节)字节顺序
    UTF-16 16位UCS转换格式,字节顺序由可选的字节顺序标记来标识

    一旦知道了字符集别名后,程序就可以调用Charset的forName()方法来创建Charset对象,forName()方法的参数就是相应字符集的别名。例如下面代码:

    Charset cs=Charset.forName("ISO-8859-1");
    Charset csCn=Charset.forName("GBK");
    

    拓展:java 7 新增了一个StandardCharsets类,该类里包含ISO-8859-1、UTF-8、UTF-16等常用变量,这些变量代表了最常用的字符集对应的Charset对象:

    4.3 解码器和编码器使用

    一旦获取了Charset对象后,就可以通过该对象的newDecoder()、newEncoder()这两个方法分别返回CharsetDecoder和CharsetEncoder对象,代表了CharSet的解码器和编码器。
    调用CharsetDecoder的decode()就可以将ByteBuffer(字节序列)转换为CharBuffer(字符序列),调用CharsetEncoder的encode()就可以将CharBuffer(字符序列转换为ByteBuffer(字节序列)。
    如下程序就使用了CharsetDecoder的decode()和CharsetEncoder的encode()完成ByteBuffer和CharBuffer之间的转换。

    package section9;
    
    import java.nio.ByteBuffer;
    import java.nio.CharBuffer;
    import java.nio.charset.CharacterCodingException;
    import java.nio.charset.Charset;
    import java.nio.charset.CharsetDecoder;
    import java.nio.charset.CharsetEncoder;
    
    public class CharsetTransform
    {
        public static void main(String[] args) throws CharacterCodingException {
            //创建简体中文所对应的Charset
            Charset cn=Charset.forName("GBK");
            //获取cn对象的解码器和编码器
            CharsetEncoder cnEncoder=cn.newEncoder();
            CharsetDecoder cnDecoder=cn.newDecoder();
    
    
            //创建一个CharBuffer对象
            CharBuffer cbuff= CharBuffer.allocate(8);
            cbuff.put('孙');
            cbuff.put('悟');
            cbuff.put('空');
            cbuff.flip();//将limit设置到position,position=0
            System.out.println(cbuff);
            System.out.println(cbuff.position());
            //将CharBuffer中字符系列转化为字节序列
          **  ByteBuffer bbuff=cnEncoder.encode(cbuff);**
            System.out.println(cbuff);//此时cbuff不存在了,也就输出为空
            //循环输出ByteBuffer中的每个字节
            for(var i=0;i<bbuff.capacity();i++)
            {
                System.out.println(bbuff.get()+" ");
            }
            bbuff.flip();
            //将ByteBuffer的数据全部解码成字符序列
           ** System.out.println("
    "+cnDecoder.decode(bbuff));**
        }
    }
    孙悟空
    0
    
    -53 
    -17 
    -50 
    -14 
    -65 
    -43 
    
    孙悟空
    

    ByteBuffer bbuff=cnEncoder.encode(cbuff);
    System.out.println(" "+cnDecoder.decode(bbuff));
    上面两行代码分别实现了将CharBuffer转换成ByteBuffer,将ByteBuffer转换成CharBuffer的功能。实际上Charset里也提供了如下三个方法:
    CharBuffer decode(ByteBuffer bb):将ByteBuffer中字节序列转换成字符序列的的便捷方法。
    ByteBuffer encode(CharBuffer cb):将CharBuffer中的字符序列转换成字节序列的便捷方法。
    ByteBuffer encode(String str):将String中的字符序列转换成字节序列的便捷方法。
    也就是说,获取了Charset对象后,如果仅仅只需要进行简单的编码、解码操作,起始可以不用创建CharsetEncoder和CharsetDecoder对象,直接调用Charset的encode()和decode()方法进行编码、解码。

    package section9;
    
    import java.nio.ByteBuffer;
    import java.nio.CharBuffer;
    import java.nio.charset.Charset;
    
    public class CharsetTransformTest
    {
        public static void main(String[] args)
        {
            //创建简体中文的Charset
            Charset cn=Charset.forName("GBK");
            //创建一个Buffer对象
            CharBuffer cbuff=CharBuffer.allocate(8);
            cbuff.put('孙');
            cbuff.put('悟');
            cbuff.put('空');
            //将limit移动到position所在位置,position=0
            cbuff.flip();
            System.out.println(cbuff);//孙悟空
            System.out.println(cbuff.position());//0
    
            //将字符序列转化为字节序列的便捷方式
            ByteBuffer bbuff=cn.encode(cbuff);
            System.out.println(cbuff.position());//3
            cbuff.flip();//执行上面的准换后,position位置发生改变
            System.out.println(cbuff);//孙悟空
            System.out.println(bbuff);//java.nio.HeapByteBuffer[pos=0 lim=6 cap=6]
    
            //将字节序列转化为字符序列的便捷方式
            CharBuffer cbuff1=cn.decode(bbuff);
            System.out.println(cbuff1.position());
            System.out.println(cbuff1);//孙悟空
        }
    }
    

    提示:在String类里也提供了一个getBytes(String charset)方法,该方法返回byte[],该方法也是使用指定的字符集将字符串转换为字节序列。

    五、文件锁

    如果多个系统需要并发修改同一个文件时,程序之间需要某种机制来进行通信,使用文件锁可以有效地阻止多个进程并发修改同一个文件,所以现在大部分操作系统都提供了文件锁的功能。
    文件锁控制文件或者文件部分字节的访问,但文件锁在不同操作系统的差别较大,所以早期的JDK版本并未提供文件锁的支持。从JDK1.4的新IO开始,Java开始提供文件锁的支持。

    5.1 锁定文件

    在NIO中,Java提供了FileLock来支持文件锁定功能,在FileChannel中提供的lock()/tryLock()方法可以获取文件锁FileLock对象,从而锁定文件。
    Lock()和tryLock()方法的区别在于:
    当Lock试图锁定某个文件时,如果无法得到文件锁,程序将一直阻塞;而tryLock()是尝试锁定文件,他将直接返回而不是阻塞,如果获得了文件锁,该方法返回文件锁,否则将返回null。

    5.2 部分锁定

    如果FileChannel只是像锁定文件部分内容,而不是锁定全部内容,可以使用如下的lock()或tryLock()方法:
    1、lock(long position,long size,boolean shared):对文件的position开始,长度为size的内容加锁,该方法是阻塞式的。
    2、tryLock(long position,long size,boolean shared):非阻塞式的加锁方式。参数同上类似。
    当shared为true时,表明该锁是一个共享锁,它允许多个线程来读取该文件,但阻止其他进程获得对该文件的排他锁。当shared为false时,表明该锁是一个排他锁,它将锁住对文件的读写。程序可以通过调用FileLock的isShared来判断它获得的锁是不是共享锁。
    直接使用lock()或tryLock()方法获取的文件锁是排他锁
    处理完文件后通过FileLock的release()释放文件锁。下面程序示范了使用FileLock锁定文件。

    package section9;
    
    import java.io.FileOutputStream;
    import java.nio.channels.FileLock;
    
    public class FileLockTest
    {
        public static void main(String[] args)
        {
            try(
                    //使用FileOutputStream获取FileChannel
                    var channel=new FileOutputStream("src//section9//a.txt").getChannel()
                    )
            {
                //使用非阻塞式方式对文件加锁
                FileLock lock=channel.tryLock();
                System.out.println(lock);//sun.nio.ch.FileLockImpl[0:9223372036854775807 exclusive valid]
                //程序暂停10s
                Thread.sleep(10000);
                //释放锁
                lock.release();
            }
            catch (Exception ex)
            {
                ex.printStackTrace();
            }
        }
    }
    

    上面程序FileLock lock=channel.tryLock();对指定文件加锁,接着程序调用Thread.sleep(10000)暂停10s后才释放文件锁,因此在这10s内,其他程序无法对a.txt文件进行修改。

    5.3 文件锁的注意事项

    在某些平台上,文件锁仅仅是建议性的,并不是强制式的。这意味着即使一个程序不能获得文件锁,它也可以对该文件进行读写。
    在某些平台上,不能同步地锁定一个文件并把它映射到内存中。
    文件锁是由Java虚拟机所持有的,如果两个Java程序使用同一个Java虚拟机运行,则它们不能对同一个文件进行加锁。
    在某些平台上当关闭 FileChannel时,会释放Java虚拟机在该文件上的所有锁,因此应该避免对同一个被锁定的文件打开多个FileChannel。

  • 相关阅读:
    数组里面为对象根据某一属性排序
    理由<a>标签跳转到对应锚点
    jquery对复选框选中
    Fullcalendar
    es6学习
    vue 关于树杈图问题
    input 复选框样式修改
    GAMES101 作业2
    一份自己iOS 面试题,拿到15K35K,分享出来
    Cesium 一款面向三维地球和地图的,世界级的JavaScript开源产品
  • 原文地址:https://www.cnblogs.com/weststar/p/12828711.html
Copyright © 2011-2022 走看看