天天看点

java内功系列二(IO && NIO && AIO )

1.java.io下包括输入和输出2中IO流,每种IO流又分为字节流和字符流两大类(按照是否直接与目的源交互又分为节点流和处理流,处理流其实是封装了节点流在更高层实现数据的处理,这样java程序可以使用一致的代码爱处理不同的节点流,不管来之文件还是网络还是键盘,详见下图)。java7提供了NIO更高效。java程序可以把对象序列化成字节流放在磁盘或者网上传送,这是分布式编程的基础。

java内功系列二(IO && NIO && AIO )

2.中文是1个字符占用2个字节,英文是1个字符占用1个字节,其他情况还要根据编码格式。java使用Unicode所以一个字符占用2个字节。在处理流的时候如果需要对流中的内容进行逐行处理时需要用字符流,如果不需要处理使用字节流。java中一般以stream结尾都是字节流,writer和reader结尾的是字符流。

3.java.io下提供File类访问文件和文件夹,它与平台无关,对文件进行增删重命名等,但是对文件内容不能访问,文件内容需要使用输入输出流。提供文件名方法(如:getName())、文件检查方法(如:exists())、获取文件常规信息(如:length())、文件操作(如:delete())、目录操作(mkdir(),listFiles())。在file使用list方法列出文件夹下的其他文件时提供了一个方法接口(FilenameFilter),可以使用lambda表达式。

4.java输入流都是以inputstream和reader作为基类(都用read方法提供数据读取),输出流都是以outputstream和writer作为基类(都用),他们都是抽象基类。以下是IO模型和流的类关系

java内功系列二(IO && NIO && AIO )
java内功系列二(IO && NIO && AIO )
java内功系列二(IO && NIO && AIO )
java内功系列二(IO && NIO && AIO )
java内功系列二(IO && NIO && AIO )

5.inputstream提供(FileInputStream实现操作文件)和reader(FileReader实现类操作文件),以下是一个例子:

      FileInputStream d;

          byte[] ds=new byte[100];

          int flage;

          if((flage= d.read(ds))>0)

          {

            System.out.println(new String(ds, 0, flage));

          }

      d.close();//在java7以后重写了IO,只需要把代码放在try块中就可以实现自动关闭文件。

这种方法应该用字符流处理,因为字节流可能刚读到文字一半的字节,在转换成文字时可能出现乱码情况。(文字流处理类似)

          FileInputStream i=new FileInputStream("1.txt");

          FileOutputStream o=new FileOutputStream("2.txt");

          byte[] buff=new byte[32];

          int hasread=0;

          while((hasread=i.read(buff))>0)

          {

              o.write(buff, 0, hasread);

          }

结合输入实现

        FileWriter fw=new FileWriter("1.txt");

        fw.write("1111");

        fw.write("2222");

简易的直接写入文件,使用FileWriter

6.在操作流的时候一般是建立节点流,然后再用对应的缓冲流包装该节点流实现操作和性能更优。

          //把system.in转换成字符流

          InputStreamReader reader=new InputStreamReader(System.in);

          //把节点字符流转换换成缓冲流

          BufferedReader br=new BufferedReader(reader);

          String line=null;

          while((line=br.readLine())!=null)

          {

              System.out.println(line);

          }

7.文件的杀手锏randomAccessFile,它提供了对文件(其它IO点不行)的随机读写,比如读取某一段或者追加文件内容。此类有文件记录指针的概念,对指针有获取和设置2个方法。它的读类似inputstream的3个read方法,写类似outputstream的write方法。还有一些列的writeXX和readXX方法。如果想在文件某个位置插入先要把文件插入位置后的文件保存起来在加入插入内容后再加会之前的后面内容,不然会出现覆盖。

          try(RandomAccessFile raf=new RandomAccessFile("1.txt", "rw"))

          {

          //开始读文件

              raf.seek(300);

              byte[] bbuf=new byte[1024];

              int hasread=0;

              while((hasread=raf.read(bbuf))>0)

              {

                  System.out.println(new String(bbuf, 0, hasread));

              }

              //开始追加

              raf.seek(raf.length());

              raf.write("追加内容".getBytes());

              //

      }

      catch(IOException ex)

      {

          ex.printStackTrace();

      }

8.对象序列化(继承serializable或者Externalizable),当一个对象多次被序列化时只有第一次是真正序列化,其他都是只记录对象编号。对于可变对象第一次序列化后再修改对象内容后在序列化当反序列化时修改的内容不会生效。可以针对对象进行序列化和反序列化的定制达到哪些字段可以不序列化或者把一个对象序列化成其他对象都可以(完全交给开发人员,此处只是了解真正需要序列化时在详细查看)。

          //对象序列化到文件(把节点流转换成处理流,也可以用其他IO节点接,如二进制数组等),

          ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("1.txt"));

          Person per=new Person();

          oos.writeObject(per);

          //读出对象

          ObjectInputStream ois=new ObjectInputStream(new FileInputStream("1.txt"));

          Person per2=(Person)ois.readObject();

9.NIO采用内存映射文件方式处理输入输出。传统的面向流那NIO则面向块。主要包:java.nio,java.nio.channels,java.nio.charset,java.nio.channels.spi,java.nio.charset.spi。channel和buffer是NIO的2核心。channel相对于inputstream和outputstream最大区别是提供了map()方法,这样可以直接把一块数据映射到内存。nio提供charset类操作字符串到字节流的方法和提供非阻塞编程的selector类。在java.nio中有很多种 buffer如:bytebuffer,charbuffer,shortbuffer(基本数据除了bool都提供了,但是常用的是bytebuffer和charbuffer)。buffer中有flip()和clear()2个方法一个是为取数据做好准备,一个是再次向buffer中装入数据做好准备,以下规则:

java内功系列二(IO && NIO && AIO )

bytebuffer还有一个子类MappedByteBuffer,它是由channel的map方法返回。并且bytebuffer还提供了一个与allocate()类似的方法allocateDirect()创建直接buffer,直接buffer比普通buffer读取效率更高,但是创建成本也很大,所以短期使用不要用直接buffer。

10.channel是类似流对象,它只和buffer进行交互不和程序直接交互,程序要写入channel必须先写入buffer,要访问channel也是先要将channel数据写入到buffer后程序再给buffer交互。channel分为以下几种:DatagramChannel(UDP协议)、FileChannel、Pipe.SinkChannel,Pipe.SourceChannel(线程之间通信)、SelectableChannel、ServerSocketChannel、SocketChannel。channel中常用方法是map(),read(),write()。

以下展示channel直接读取文件:

          File f=new File("1.txt");

          try(

                  //创建输入流对应的channel

                  FileChannel inchannel=new FileInputStream(f).getChannel();

                  //创建输出流对应的channel

                  FileChannel outChannel=new FileOutputStream("2.txt").getChannel()

                  )

          {

              

              //将FileChannel里的全部数据映射成bytebuffer(如果文件太大可以不用这个方法,文件太大造成内存会消耗),map常用方法

              MappedByteBuffer buffer=inchannel.map(FileChannel.MapMode.READ_ONLY, 0, f.length());

              //直接将buffer中的数据全部输出

              outChannel.write(buffer);

              //复原Limite和position位置

              buffer.clear();

              //创建解码器

              Charset charset=Charset.forName("GBK");

              //创建解码器对象

              CharsetDecoder decoder=charset.newDecoder();

              //转换bytebuffer到charbuffer

              CharBuffer charBuffer=decoder.decode(buffer);

              System.out.println(charBuffer);

          }

          catch(IOException ex)

          {

          ex.printStackTrace();

          }

案例二(非一次性读取,和之前竹篮打水循环一样):

          File f=new File("1.txt");

          try(

                  //创建输入流对应的channel

                  FileChannel fcin=new FileInputStream(f).getChannel()                  

                  )

          {

              //针对一次性读取大文件可以使用传统的竹篮打水模式

              ByteBuffer bbuff=ByteBuffer.allocate(256);

              //将filechannel中的数据分成多次读取到bbuff中

              while(fcin.read(bbuff)!=-1)

              {

                  //锁定buffer

                  bbuff.flip();

                  Charset charset=Charset.forName("GBK");

                  CharsetDecoder decoder=charset.newDecoder();

                  CharBuffer charBuffer=decoder.decode(bbuff);

                  System.out.println(charBuffer);

                  bbuff.clear();

              }                  

          }

          catch(IOException ex)

          {

          ex.printStackTrace();

          }

11.java提供charset对bytebuffer和charbuffer之间的转换

          //获取系统中支持的编码

          SortedMap<String, Charset> map = Charset.availableCharsets();

          //解码

                Charset charset=Charset.forName("GBK");

          CharsetDecoder decoder=charset.newDecoder();

          CharBuffer charBuffer=decoder.decode(bytebuffer);

          //编码

          Charset charset=Charset.forName("GBK");

          CharsetEncoder ecoder=charset.newEncoder();

          ByteBuffer byteBuffer=ecoder.encode(charbuffer);

12.java7提供了文件IO和异步channel的IO。对于文件IO在java.nio.file中提供文件操作(针对文件提供了Path接口,Paths和Files是工具类。需要熟练掌握files和paths工具)。在java.nio.channels下提供了大量Asynchronous开头的channel接口和类。

nio提供了java.nio.file.Files.walk(Path star, FileVisitor<? super Path> visitor)方法更快捷的变量文件下的子文件或者子目录。

nio提供了文件夹监控的功能:

WatchService watchService=FileSystems.getDefault().newWatchService();

          Paths.get("c:/").register(watchService, 

                  StandardWatchEventKinds.ENTRY_CREATE,

                  StandardWatchEventKinds.ENTRY_DELETE,

                  StandardWatchEventKinds.ENTRY_MODIFY);

提供files包的Files.getFileAttributeView(arg0, arg1, arg2)可以对文件进一步操作。

13.selector实现channel的注册并实现一对多管理。如下面实例代码:

Selector selector = Selector.open();//获取selctor

channel.configureBlocking(false);//注册在selector的channel必须是非阻塞的

SelectionKey key = channel.register(selector, SelectionKey.OP_READ);//注册的channel被监听的事件类型,可以用|监听多种

while(true) {

  int readyChannels = selector.select();//有多少channel已经就绪

  if(readyChannels == 0) continue;//表示没有就绪channel

  Set selectedKeys = selector.selectedKeys();//返回就绪集合

  Iterator keyIterator = selectedKeys.iterator();

  while(keyIterator.hasNext()) {

    SelectionKey key = keyIterator.next();//这个对象包括channel、selector、事件监听类型、查看事件是否就绪、其他附加对象

    if(key.isAcceptable()) {

        // a connection was accepted by a ServerSocketChannel.

    } else

if  (key.isConnectable()) {

        // a connection was established with a remote server.

    } else

if  (key.isReadable()) {

        // a channel is ready for reading

    } else

if  (key.isWritable()) {

        // a channel is ready for writing

    }

    keyIterator.remove();

  }

}

14.ByteBuffer有两种模式:直接/间接.间接模式最典型(也只有这么一种)的就是HeapByteBuffer,即操作堆内存 (byte[]).但是内存毕竟有限,如果我要发送一个1G的文件怎么办?不可能真的去分配1G的内存.这时就必须使用”直接”模式,即 MappedByteBuffer,文件映射.先中断一下,谈谈操作系统的内存管理.一般操作系统的内存分两部分:物理内存;虚拟内存.虚拟内存一般使用的是页面映像文件,即硬盘中的某个(某些)特殊的文件.操作系统负责页面文件内容的读写,这个过程叫”页面中断/切换”. MappedByteBuffer也是类似的,你可以把整个文件(不管文件有多大)看成是一个ByteBuffer.MappedByteBuffer 只是一种特殊的ByteBuffer,即是ByteBuffer的子类。 MappedByteBuffer 将文件直接映射到内存(这里的内存指的是虚拟内存,并不是物理内存)。通常,可以映射整个文件,如果文件比较大的话可以分段进行映射,只要指定文件的那个部分就可以。

15.AIO操作案例

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

        //方式一异步读取文件数据(这种操作比较麻烦,不停的查看是否完成,不过可以让线程先去完成别的事后再来查看是否完成)

        Path path=Paths.get("C:\\Users\\Administrator\\Desktop\\本地知识\\06 java内功系列\\IO\\test.txt");    

        AsynchronousFileChannel fileChannel=AsynchronousFileChannel.open(path, StandardOpenOption.READ);

        ByteBuffer buffer = ByteBuffer.allocate(1024);

        long position = 0;

        Future<Integer> operation = fileChannel.read(buffer, position);

        while(!operation.isDone());

        buffer.flip();

        byte[] data = new byte[buffer.limit()];

        buffer.get(data);

        System.out.println(new String(data));

        buffer.clear();

        

        

        //方式二异步读取数据(推荐用法,任务完成后会自己启动一个线程处理IO完成的数据)        

        System.out.println("主线程: "+Thread.currentThread().getName());

        fileChannel.read(buffer, position, buffer, new CompletionHandler<Integer, ByteBuffer>() {

            @Override

            public void completed(Integer result, ByteBuffer attachment) {                

                System.out.println("回调线程: "+Thread.currentThread().getName());                    

                attachment.flip();

                byte[] data = new byte[attachment.limit()];

                attachment.get(data);

                System.out.println(new String(data));

                attachment.clear();

            }

            @Override

            public void failed(Throwable exc, ByteBuffer attachment) {

            }

        });    

    }

16.针对windows的异步socket,sun提供了专门的类处理,如:WindowsAsynchronousFileChannelImpl