Java Nioの高級レンガ運搬工(FileChannel)一

14929 ワード

Java NioシリーズJava NioのBuffer Java Nioの直接メモリJava Nioの高級レンガ運搬工(FileChannel)一
前言
皆さんはレンガを運ぶことに詳しいでしょう.小緑と小青はレンガを運ぶ工で、小緑は小青より少し早くレンガを運び始め、小緑はレンガを運ぶ方式である.ブルーちゃんの性格は怠け者で、最初は緑ちゃんと一緒にレンガを運ぶ方法と同じでしたが、レンガを運ぶのが疲れていることに気づき、ブルーちゃんは自分の仕事をどうやって軽減するかを考えてからもっとできるようになりました.そこで、レンガをカートに入れて目の前に運ぶと、ブルーちゃんの仕事が多くなるだけでなく、そんなに疲れていないように見えました.ここのカートは私たちのバッファです.青さん、私たちの高級レンガ運搬工は今日のテーマFileChannelです.
コンセプト
注記原文:A channel for reading,writing,mapping,and manipulating a file.翻訳:ファイルの読み取り、書き込み、マッピング、操作に使用されるチャネル.Java ioの読み取りと書き込み、操作ファイルに比べて、マッピングとは何か、次のトピックで説明します.
詳細な概念
 FileChannelは、ファイルに接続されたバイトチャネルであり、position()を提供して、現在の読み取りまたは書き込みの位置を問合せ、position(long)を提供して現在の読み取りまたは書き込みの位置を設定することができる.ファイル自体には可読で書き込み可能な可変長バイトシーケンスが含まれており、現在のバイトシーケンス長はsize()メソッドで取得できます.書き込みバイト数が現在のサイズを超えると、ファイルのsizeが増加します.truncate()メソッドを呼び出すと、ファイルのサイズが小さくなります.ファイルには、アクセス権、コンテンツタイプ、最終変更時間などの関連メタデータもあります.このクラスでは、メタデータへのアクセス方法は定義されません.
FileChannelの生成方法
FileChannel##open(Path path, OpenOption... options)
Pathは、ファイルシステム内のファイルの位置を特定できるオブジェクトであり、ファイルシステムに依存します.Path類はJDK 7からあるので、皆さんは理解して、ここではあまり説明しません.OpenOptionは、ファイルを開くか作成する方法を示します.これはインタフェースです.主な実装例StandardOpenOption:コードリスト4-1を見てみましょう.
    //  , WRITE  Append        ,       
    READ,
   //      ,        ,             
    WRITE,
    //         
    APPEND,
    //   WRITE        ,           , READ               
    TRUNCATE_EXISTING,
    //           
    // CREATE_NEW              
    CREATE,

    //      ,       
    //                         
    CREATE_NEW

上に3つの列挙が挙げられていないので、ここでは使えません.デモンストレーション1波コードリスト4-2:
public class FileChannelOpenStudy {
    public static final String JAVA_NIO = "java NIO";
    static String createAndWriteAndReadPath = "createAndWriteAndReadPath.txt";
    public static void main(String[] args) {
        FileChannel readChannel = null;
        FileChannel createAndWriteChannel = null;
        FileChannel appendChannel = null;
//        FileChannel dataSyncChannel = null;
        try{
            //create and write
            createAndWriteChannel = FileChannel.open(Paths.get(createAndWriteAndReadPath), StandardOpenOption.CREATE, StandardOpenOption.WRITE);
            ByteBuffer writeBuffer = ByteBuffer.allocate(6);
            writeBuffer.put("hello,".getBytes(Charset.forName("UTF-8")));
            //       
            writeBuffer.flip();
            int writed1 = createAndWriteChannel.write(writeBuffer);
            System.out.println("createAndWriteChannel write in " + writed1 + " bytes");

            readChannel = FileChannel.open(Paths.get(createAndWriteAndReadPath), StandardOpenOption.READ);
            ByteBuffer readBuffer = ByteBuffer.allocate(writed1);
            int readed1 = readChannel.read(readBuffer);
            readBuffer.flip();
            System.out.println("readChannel read " + readed1 + " bytes:" + new String(readBytesFrromBuffer(readBuffer)));
            createAndWriteChannel.close();

            appendChannel = FileChannel.open(Paths.get(createAndWriteAndReadPath), StandardOpenOption.APPEND);
            ByteBuffer appendBuffer = ByteBuffer.allocate(JAVA_NIO.length());
            appendBuffer.put(JAVA_NIO.getBytes("UTF-8"));
            appendBuffer.flip();
            int writed2 = appendChannel.write(appendBuffer);
            System.out.println("appendChannel writed in " + writed2 + " bytes");
   
            ByteBuffer readBuffer1 = ByteBuffer.allocate(writed2);
            int readed2 = readChannel.read(readBuffer1, readed1);
            readBuffer1.flip();
            System.out.println("readChannel readed " + readed2 + " bytes:" +
                    new String(readBytesFrromBuffer(readBuffer1)));
            appendChannel.close();

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
        }
    }
    private static byte[] readBytesFrromBuffer(ByteBuffer byteBuffer) {
        byte[] result = new byte[byteBuffer.limit()];
        byteBuffer.get(result);
        return result;
    }
}

結果は次のとおりです.
createAndWriteChannel write in 6 bytes
readChannel read 6 bytes:hello,
appendChannel writed in 8 bytes
readChannel readed 8 bytes:java NIO

FileInputStream##getChannel()
FileInputStreamを呼び出すgetChannelは、このファイル入力ストリームに関連付けられた一意のファイルチャネルを返します.一意であることを覚えておいてください.関連付けられたファイルチャネルが作成されている場合は、作成されたファイルチャネルを直接返します.このメソッドが返すFileChannelは読み取り専用で、readを呼び出すと例外が放出されます
demoを極度に快適にしてください
public class FileInputStreamGetChannel {
    static String filePath = "F:\\idea_two\\demo\\createAndWriteAndReadPath.txt";
    public static void main(String[] args) {
        try {
            FileInputStream fileInputStream = new FileInputStream(filePath);
            FileChannel channel = fileInputStream.getChannel();
            ByteBuffer readBuffer = ByteBuffer.allocate((int) channel.size());
            int readed = channel.read(readBuffer);
            readBuffer.flip();
            System.out.println(" fileInputStream get Channel read from " + readed + " bytes:" +
                    new String(readBytesFrromBuffer(readBuffer)));
            //                parent       ,         parent     ,   parent   FileInputStream  
            channel.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
        }
    }

    private static byte[] readBytesFrromBuffer(ByteBuffer byteBuffer) {
        byte[] result = new byte[byteBuffer.limit()];
        byteBuffer.get(result);
        return result;
    }
}

結果を見てから極度に快適に
fileInputStream get Channel read from 14 bytes:hello,java NIO

通常のファイルの読み書きに対するいくつかの特殊な方法
readメソッドリロードシリーズ
パラメータ
戻り値のタイプと説明
説明
ByteBuffer dst
int読み出しバイト数を返す
ファイルの現在位置から一連のバイトを所定のbufferに読み出し、読み出したバイト数に基づいてファイルの現在位置を更新する
ByteBuffer[] dsts, int offset, int length
longは読み出したバイト数を返す
ファイルから読み出すバイトから所定のバイトバッファ配列においてoffsetとは、バイトがバッファ配列に書き込まれる最初のバッファのオフセット量であり、負ではなくdsts以下である.length;qilengthとはoffsetからいくつかのバッファを書くことができ、負ではなくdstsより大きくはならないことを指す.length-offset
ByteBuffer[] dsts
longは読み出したバイト数を返す
このチャネルからバイトを所定のバイトバッファ配列に読み出す
コードを削除
コードリスト5-1
public class FileChannelReadTest {
   //         
    static String testFilePath = "testFileChannelReadTest.txt";

    public static void main(String[] args) {
        Path path = Paths.get(testFilePath);
        try (FileChannel fileChannel = FileChannel.open(path, StandardOpenOption.READ)) {
            //read
            ByteBuffer read1 = ByteBuffer.allocate(4);

            int readed1 = fileChannel.read(read1, 0);
            System.out.println("read ByteBuffer " + readed1 + " bytes,  :" + new String(read1.array()));

            ByteBuffer[] byteBuffersRead = new ByteBuffer[3];
            IntStream.rangeClosed(0, 2).forEach(i -> {
                byteBuffersRead[i] = ByteBuffer.allocate(5);
            });
            System.out.println("alfter read fileChannel position:" + fileChannel.position());
            //             ,      A
            fileChannel.position(readed1);
         //scatter read       channnel        buffer   
            long readed2 = fileChannel.read(byteBuffersRead, 0, byteBuffersRead.length);
            IntStream.rangeClosed(0, 2).forEach(i -> {
                byteBuffersRead[i].flip();
            });
            System.out.println(String.format("read ByteBuffers %d bytes,content:%s
%s
%s",readed2, new String(readBytesFrromBuffer(byteBuffersRead[0])), new String(readBytesFrromBuffer(byteBuffersRead[1])), new String(readBytesFrromBuffer(byteBuffersRead[2])))); } catch (IOException e) { } } private static byte[] readBytesFrromBuffer(ByteBuffer byteBuffer) { byte[] result = new byte[byteBuffer.limit()]; byteBuffer.get(result); return result; } }

結果A(エラー結果)
read ByteBuffer 4 bytes,  :hell
read ByteBuffers 15 bytes,content:helln
iha0n
iha1n

結果C(正しい結果)
read ByteBuffer 4 bytes,  :hell
alfter read fileChannel position:0
read ByteBuffers 15 bytes,content:niha0
niha1
niha2

writeメソッドリロードシリーズ
パラメータ
戻り値のタイプと説明
説明
ByteBuffer src
int書き込みバイト数、0
所与のバッファからデータを読み出してチャネルに書き込む
ByteBuffer[] srcs, int offset, int length
long書き込みバイト数、ゼロ
所与のバッファ配列からデータを読み出してチャネルに書き込む.offsetとは、バッファ配列から読み出す最初のバッファの下付き記号であり、負ではなくdsts以下である.length;lengthとはoffsetから読めるいくつかのバッファを指し、負がdstsより大きくない.length-offset
ByteBuffer[] srcs
long書き込みバイト数、ゼロ
所与のバッファ配列からチャネルに書く
秀一波操作
コードリスト5-2
public class FileChannelWriteTest {
    static String testFilePath = "testFileChannelReadTest.txt";

    public static void main(String[] args) {

        Path path = Paths.get(testFilePath);
      //  FileChannel #open       FileChannel       
        try (FileChannel fileChannel = FileChannel.open(path, StandardOpenOption.CREATE, StandardOpenOption.APPEND)) {
            ByteBuffer write1 = ByteBuffer.allocate(8);
            write1.put("hell".getBytes(Charset.forName("UTF-8")));
            //                 
            write1.flip();
            //  
            int writed1 = fileChannel.write(write1);
              
             System.out.println("write  ByteBuffer " + writed1 + " bytes");

            ByteBuffer[] byteBuffers = new ByteBuffer[3];
            IntStream.rangeClosed(0, 2).forEach(i -> {
                byteBuffers[i] = ByteBuffer.allocate(8);
                byteBuffers[i].put(("niha" + i).getBytes());
               //            
                byteBuffers[i].flip();
            });
      
          //                  
            long writed2 = fileChannel.write(byteBuffers, 0, byteBuffers.length);
            System.out.println(" write ByteBuffers "+writed2 +" bytes");
        } catch (IOException e) {
        }
    }

}

結果:
write  ByteBuffer 4 bytes
write ByteBuffers 15 bytes

force(boolean metaData)
 このチャネルファイルの更新を、そのチャネルファイルを含むストレージデバイスに書き込むように強制します.上記のwriteメソッドを呼び出すのは、システムキャッシュにデータを書き込むだけで、pdflushスレッドによってハードディスクに非同期でリフレッシュします.もちろん、期限切れの汚れたページのメモリがワークメモリに占める割合、汚れたページがワークメモリに占める割合によって、pdflushスレッドを開くかどうかを決定します.このメソッドを呼び出すと、書き込みデータがディスクに保存され、突然電源が切れたときに重要な情報が失われないようにします. この方法は、ファイルのメタデータ情報を一緒にディスクにリフレッシュするかどうかを示すパラメータmetaDataブール型を含み、ファイルのメタデータ情報はファイルのアクセス権限、ファイルの最終更新時間などを含み、オペレーティングシステムと関係がある.metaDataがtrueの場合、io操作が1つ増えるため、この値を設定することでio操作の数を制限できます.使用法:writeが呼び出されるたびにメソッドが呼び出されると、アプリケーションの応答時間とスループットが低下します.
  • 書き込みデータが電源が切れた場合に失われたくない場合は、forceを書くたびに、性能を犠牲にして書き込みデータの完全性を保証することができます.
  • 書き込みデータに対して停電時に極小部分のデータを失うことができる場合、非同期ブラシディスクのポリシーを採用することができ、あるタイミングタスクがforceをタイミング的に呼び出し、データが停電時に数秒しか失われないことを保証する.これは多くのアプリケーションで受け入れられる
  • である.
    話を多くしないで先にコードを引っ張って敬意を表します
    タイムリーにプレゼンテーションコードを書く:コードリスト5-3
    public class FileChannelForceInTime {
        static String testFilePath = "testFileChannelForceInTime.txt";
        static byte[] imageBytes;
        final static String PICTUREE_PATH = "meimei.jpg";
    
        static {
            try {
                imageBytes = Files.readAllBytes(Paths.get(PICTUREE_PATH));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        public static void main(String[] args)  {
    
            try  {
                Path path = Paths.get(testFilePath);
                FileChannel fileChannel = FileChannel.open(path, StandardOpenOption.CREATE, StandardOpenOption.APPEND);
                //      force
                ByteBuffer imageBytesBuffer = ByteBuffer.allocateDirect(imageBytes.length);
                imageBytesBuffer.put(imageBytes);
                long now = System.currentTimeMillis();
                for (int i = 0; i <= 9; i++) {
                    imageBytesBuffer.flip();
                    fileChannel.write(imageBytesBuffer);
                    fileChannel.force(false);
                    //                ,                       ,        
                    Thread.sleep( 1000);
                }
                System.out.println("write in time force cost " + ((System.currentTimeMillis() - now)-10 * 1000) + "ms");
                fileChannel.close();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
    
        }
    }
    

    出力結果
    write in time force cost 922ms
    

    タイミングタスク非同期ブラシコードのデモ:コードリスト5-4
    public class FileChannelTimelyForce {
        static String testFilePath = "testFileChannelTimelyForce.txt";
        static byte[] imageBytes;
        final static String PICTUREE_PATH = "meimei.jpg";
    
        static {
            try {
                imageBytes = Files.readAllBytes(Paths.get(PICTUREE_PATH));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        private static ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
    
        public static void main(String[] args) {
            try {
                Path path = Paths.get(testFilePath);;
                FileChannel fileChannel = FileChannel.open(path, StandardOpenOption.CREATE, StandardOpenOption.APPEND);
                executorService.scheduleAtFixedRate(() -> {
                    try {
                        if (fileChannel.isOpen()) {
                            fileChannel.force(false);
                        }
                    } catch (IOException e) {
                    }
                }, 1, 1, TimeUnit.SECONDS);
                long now = System.currentTimeMillis();
                ByteBuffer imageBytesBuffer = ByteBuffer.allocateDirect(imageBytes.length);
                imageBytesBuffer.put(imageBytes);
                for (int i = 0; i <= 9; i++) {
                    imageBytesBuffer.flip();
                    fileChannel.write(imageBytesBuffer);
                    //     s                ,        
                    Thread.sleep(1000);
                }
                System.out.println("fileChannnel write timely foce cost " + (System.currentTimeMillis() - now - 10 * 1000) + "ms");
                fileChannel.close();
            } catch (IOException e) {
            } catch (InterruptedException e) {
            }finally {
                executorService.shutdown();
            }
    
        }
    }
    

    実行結果は次のとおりです.
    fileChannnel write timely foce cost 127ms
    

    両方の使い方の時間がかかるのも明らかで、皆さんのシーンに合わせて使うことができます.もちろん、もっと良い方法があります.皆さんも以下のコメントの下で、一緒に進歩することができます.
    後記
    次のセクションでは、FileChannelについても議論します.このクラスのいくつかの高度な使い方といくつかの下位原理、そしてKafkaメッセージミドルウェアがどのように使われているのか、時間がかかるかもしれません.ブロガーにサポートしてもらい、注目を集めてください.ついでにツッコミを入れてまた仕事を探し始めて、会社は給料を出すことができなくて、衰えました.