<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      文件IO:實現(xiàn)高效正確的文件讀寫

      背景

      本篇將會講一些文件讀寫的推薦使用姿勢以及編碼時的注意事項,便于新手更好地理解如何高效地進行大文件讀寫,比如利用好緩沖區(qū)避免出現(xiàn)OOM,或者及時地釋放資源以保證資源被及時地關閉,避免資源泄露。

      處理中文時讀取到亂碼

      大家都知道,中文的編碼和英文的編碼使用的字符集是不一樣的,字符集不匹配的時候讀取中文很容易出現(xiàn)亂碼問題。下面我舉個例子,說明一下讀取中文時如何解決亂碼問題。
      1、使用下面代碼先創(chuàng)建一個hello.txt文件,編碼格式為GBK;文件內(nèi)容是“你好hi”

              Files.deleteIfExists(Paths.get("hello.txt"));
              Files.write(Paths.get("hello.txt"), "你好hi".getBytes(Charset.forName("GBK")));
              log.info("bytes:{}", Hex.encodeHexString(Files.readAllBytes(Paths.get("hello.txt"))));
      

      2、使用下面代碼讀取這個hello文件中的中文并打印

      
              char[] chars = new char[10];
              String content = "";
              try (FileReader fileReader = new FileReader("hello.txt")) {
                  int count;
                  while ((count = fileReader.read(chars)) != -1) {
                      content += new String(chars, 0, count);
                  }
              }
      

      3、打印結果。

      12:33:42.976 [main] INFO com.example.demo3.commonpitfalls.FileIOTest - bytes:c4e3bac36869
      12:33:42.993 [main] INFO com.example.demo3.commonpitfalls.FileIOTest - result:???hi
      

      可以發(fā)現(xiàn)"你好hi"沒有正確顯示,而是出現(xiàn)亂碼。
      4、分析
      出現(xiàn)亂碼的原因是我們在對你好hi進行編碼的時候,使用的是GBK, 但是讀取時使用FileReader,這邊想說明的是,FileReader 是以當前機器的默認字符集來讀取文件的
      也就是說,默認使用IDEA默認的機器碼來解碼,默認的字符集是UTF-8。所以,當前機器默認字符集是 UTF-8,自然無法讀取 GBK 編碼的漢字,因而出現(xiàn)了亂碼。
      解決這個問題也很簡單,就是我們在編碼的時候就使用UTF_8, 解碼的時候本來默認的就是UTF_8, 這樣就不會有亂碼問題了。
      修復代碼如下:

       Files.deleteIfExists(Paths.get("hello3.txt"));
              Files.write(Paths.get("hello3.txt"), "你好hi".getBytes(Charset.forName("UTF-8")));
              log.info("bytes:{}", Hex.encodeHexString(Files.readAllBytes(Paths.get("hello3.txt"))));
      
      
              char[] chars = new char[10];
              String content = "";
              try (FileReader fileReader = new FileReader("hello3.txt")) {
                  int count;
                  while ((count = fileReader.read(chars)) != -1) {
                      content += new String(chars, 0, count);
                  }
              }
      
               log.info("result:{}", content);
      

      打印結果:

      12:41:10.105 [main] INFO com.example.demo3.commonpitfalls.FileIOTest - bytes:e4bda0e5a5bd6869
      12:41:10.112 [main] INFO com.example.demo3.commonpitfalls.FileIOTest - result:你好hi
      

      這個時候有人又會問了,如果我就要使用GBK來編碼hello.txt文件,如何能解碼成功不會亂碼呢?當然也是有方法的。可直接使用 FileInputStream 拿文件流,
      然后使用 InputStreamReader 讀取字符流,并指定字符集為 GBK!

      // 使用FileInputStream, InputStreamReader
              char[] chars = new char[10];
              String content = "";
              // 使用try-with-resources來釋放資源,語句中打開的資源會在代碼塊執(zhí)行完畢后自動關閉,無需手動調(diào)用關閉方法,避免了資源泄漏。
              // 無需使用finally 手動釋放!
              try( FileInputStream fileInputStream = new FileInputStream("hello.txt");
                      InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream, Charset.forName("GBK"));) {
                      int count;
                      while ((count = inputStreamReader.read(chars)) != -1) {
                          content += new String(chars, 0, count);
                      }
                      log.info("result: {}", content);
              } catch (IOException ex){
                  ex.printStackTrace();
              }
      

      5、總結

      String text = "你好,世界!";
      byte[] gbkBytes = text.getBytes(Charset.forName("GBK"));
      String decodedText = new String(gbkBytes, Charset.forName("GBK"));
      System.out.println(decodedText);
      

      以上是使用GBK對中文編碼解碼的一個簡單的例子,這個例子是可以正常打印“你好世界!”說到這, 就有人想問問GBK和UTF-8的區(qū)別了。
      GBK和UTF-8都是字符編碼方案,用于在計算機中表示和存儲文本數(shù)據(jù)。對于中文的表示,這二者都可以使用。只不過在使用的時候有一些區(qū)別,我們一般會使用UTf-8比較多一點。這是因為:
      對字節(jié)長度而言,
      GBK 是雙字節(jié)編碼,即一個漢字通常占用兩個字節(jié),但有些生僻字可能需要三個或四個字節(jié)來表示。
      UTF-8 是一種變長編碼方案,一個字符的長度可以是1到4個字節(jié)不等,常見的英文字符只占用一個字節(jié),常見的漢字占用三個字節(jié)。
      所以,UTF-8可以表示更多的漢字。
      從用途上來說,
      GBK主要用于中文字符編碼,包括簡體中文中的常用漢字、符號等。
      UTF-8 是一種全球通用的編碼方案,能夠表示幾乎所有的字符,包括世界上所有語言的文字、符號和表情符號。
      從存儲上來看,GBK一般使用2個字節(jié)來存儲漢字,但是UTF-8會使用3個字節(jié)來保存漢字。所以,使用GBK編碼的漢字,用UTF-8來解碼,必然不會成功了。

      Files 類的readAllLines

      使用前文提到的FileInputStream, InputStreamReader, 看起來會比較繁瑣。Files 類的readAllLines是一種比較易用的方法,該方法是JDK7推出的,它可以很方便地用一行代碼完成整個文件內(nèi)容的讀取。如下所示:

       log.info("result: {}", Files.readAllLines(Paths.get("hello.txt"), Charset.forName("UTF-8")));
      

      打開readAllLines()的源碼可以發(fā)現(xiàn),讀取的字符都會被放在這個List中,List雖然是動態(tài)增長的,但是如果內(nèi)存無法存儲這個增長到很大容量的List, 必然會拋出這個OOM。

       public static List<String> readAllLines(Path path, Charset cs) throws IOException {
              try (BufferedReader reader = newBufferedReader(path, cs)) {
                  List<String> result = new ArrayList<>();
                  for (;;) {
                      String line = reader.readLine();
                      if (line == null)
                          break;
                      result.add(line);
                  }
                  return result;
              }
          }
      

      所以,readAllLines的缺點就是,如果文件非常大的時候,讀取超出內(nèi)存大小的大文件時會出現(xiàn)OOM。

      Files類的lines()

      上文提到的readAllLine()是一次性讀取內(nèi)容到內(nèi)存中,其實某些場景,比如下載大文件,我們可以一次只讀取一部分數(shù)據(jù)到內(nèi)存中,然后再進行數(shù)據(jù)的處理。
      lines()方法就是這樣的實現(xiàn)。接下來,我們說說使用 lines 方法時需要注意的一些問題。
      與 readAllLines 方法返回 List 不同,lines 方法返回的是 Stream,這使得我們在需要時可以不斷讀取、使用文件中的內(nèi)容。

      // 總共讀取2000行
       log.info("lines {}", Files.lines(Paths.get("hello3.txt")).limit(2000).collect(java.util.stream.Collectors.joining("\n")));
      

      但是我們可以想一下這兩種方式的差異在哪里,一次讀取,只需要一次IO即可,但是多次讀取,需要多次打開磁盤的文件,多次IO。雖然不會帶來OOM, 但是會頻繁的IO。
      那么,我們每次讀取一小部分數(shù)據(jù)的時候,就不宜讀取地太少,而是按需讀取一定大小(如2000)的數(shù)據(jù),每次讀的數(shù)據(jù)相對比較大的話,那么IO的次數(shù)也就比較少了。
      另外,這樣處理時,雖然不再有OOM,但是其實也有問題,即讀取完文件后沒有關閉!
      我們通常會認為靜態(tài)方法的調(diào)用不涉及資源釋放,因為方法調(diào)用結束自然代表資源使用完成,由 API 釋放資源,但對于 Files 類的一些返回 Stream 的方法并不是這樣。這是一個很容易被忽略的嚴重問題。
      以下例子是模擬 Files.lines 方法分批讀取大文件。
      首先,我們創(chuàng)建一個demo.txt,寫入10行數(shù)據(jù)。

       String filename = "demo.txt";
              try {
                  StringBuilder content = new StringBuilder();
                  IntStream.rangeClosed(1, 10)
                          .forEach(i -> content.append("Line ").append(i).append(": This is some sample data.\n"));
      
                  Files.write(Paths.get(filename), content.toString().getBytes(), CREATE, TRUNCATE_EXISTING);
                  System.out.println("寫入成功!");
              } catch (IOException e) {
                  System.err.println("寫入文件時出現(xiàn)異常:" + e.getMessage());
              }
      

      然后使用Files.lines 方法讀取這個文件 100 萬次,每讀取一行計數(shù)器 +1:

      // 讀取這個文件 100 萬次,每讀取一行計數(shù)器 +1:
              LongAdder longAdder = new LongAdder();
              IntStream.rangeClosed(1, 1000000).forEach(i -> {
                   try {
                      Files.lines(Paths.get("demo.txt")).forEach(line -> longAdder.increment());
                  } catch (IOException e) {
                      throw new RuntimeException(e);
                  }
          
              });
              log.info("total : {}", longAdder.longValue());
      

      然后就發(fā)現(xiàn),可能會報這樣的錯誤,java.nio.file.FileSystemException: demo.txt: Too many open files
      其實,在JDK 文檔中有提到,注意使用 try-with-resources 方式來配合,確保流的 close 方法可以調(diào)用釋放資源。如果報錯無法運行,那么請使用try-with-resources!
      這也很容易理解,使用流式處理,如果不顯式地告訴程序什么時候用完了流,程序又如何知道呢,它也不能幫我們做主何時關閉文件。
      修復方式很簡單,必須使用 try 來包裹 Stream !

        try (Stream<String> lines = Files.lines(Paths.get("demo.txt"))) {
                      lines.forEach(line -> longAdder.increment());
                  } catch (IOException e) {
                      e.printStackTrace();
                  }
      

      查看 lines 方法源碼可以發(fā)現(xiàn),Stream 的 close 注冊了一個回調(diào),來關閉 BufferedReader 進行資源釋放:

      public static Stream<String> lines(Path path, Charset cs) throws IOException {
              BufferedReader br = Files.newBufferedReader(path, cs);
              try {
                  return br.lines().onClose(asUncheckedRunnable(br));
              } catch (Error|RuntimeException e) {
                  try {
                      br.close();
                  } catch (IOException ex) {
                      try {
                          e.addSuppressed(ex);
                      } catch (Throwable ignore) {}
                  }
                  throw e;
              }
          }
      
      private static Runnable asUncheckedRunnable(Closeable c) {
             return () -> {
                 try {
                     c.close();
                 } catch (IOException e) {
                     throw new UncheckedIOException(e);
                 }
             };
         }
      

      注意讀寫文件要考慮設置緩沖區(qū)

      從上述命名上可以看出,使用了BufferedReader 進行字符流讀取時,用到了緩沖。這里緩沖 Buffer 的意思是,使用一塊內(nèi)存區(qū)域作為直接操作的中轉。
      比如,讀取文件操作就是一次性讀取一大塊數(shù)據(jù)(比如 8KB)到緩沖區(qū),后續(xù)的讀取可以直接從緩沖區(qū)返回數(shù)據(jù),而不是每次都直接對應文件 IO。寫操作也是類似。如果每次寫幾
      十字節(jié)到文件都對應一次 IO 操作,那么寫一個幾百兆的大文件可能就需要千萬次的 IO 操作,耗時會非常久。
      就比如之前說的Files.lines()分批讀取數(shù)據(jù),讀取的數(shù)據(jù)先放在一個獨立buffer中,buffer相當于個一個中轉站。和直接讀數(shù)據(jù)加載到內(nèi)存的區(qū)別是,放在buffer中的話有更多的好處。就比如我現(xiàn)在既需要對這部分數(shù)據(jù)讀取再進行其他處理,或者將這部分數(shù)據(jù)保存在其他文件,這只是舉個例子啊。這個時候,我只需要讀取一次放入buffer, 后續(xù)對數(shù)據(jù)的其他操作都直接從buffer中拿就好了。

        private static void bufferOperationWith100Buffer() throws IOException {
              try (FileInputStream fileInputStream = new FileInputStream("src.txt");
                   FileOutputStream fileOutputStream = new FileOutputStream("dest.txt")) {
                  byte[] buffer = new byte[100];
                  int len = 0;
                  while ((len = fileInputStream.read(buffer)) != -1) {
                      fileOutputStream.write(buffer, 0, len);
                  }
              }
          }
      

      上述代碼我們使用了一個byte[] 緩沖區(qū),極大提高了數(shù)據(jù)讀取性能。建議進行文件 IO 處理的時候,使用合適的緩沖區(qū)!
      你可能會說,實現(xiàn)文件讀寫還要自己 new一個緩沖區(qū)出來,太麻煩了,不是有BufferedInputStream 和 BufferedOutputStream 可以實現(xiàn)輸入輸出流的緩沖處理嗎?
      是的,它們在內(nèi)部實現(xiàn)了一個默認 8KB 大小的緩沖區(qū)。但是,在使用 BufferedInputStream 和 BufferedOutputStream 時,它們實現(xiàn)了內(nèi)部緩沖進行逐字節(jié)的操作。
      接下來,我寫一段代碼比較下使用下面三種方式讀寫一個文件的性能:

      1. 直接使用 BufferedInputStream 和 BufferedOutputStream;
      2. 額外使用一個 8KB 緩沖,使用 BufferedInputStream 和 BufferedOutputStream;
      3. 直接使用 FileInputStream 和 FileOutputStream,再使用一個 8KB 的緩沖。
            //使用BufferedInputStream和BufferedOutputStream
              private static void bufferedStreamByteOperation() throws IOException {
                  try (BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream("src.txt"));
                          BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream("dest.txt"))) {
                      int i;
                      while ((i = bufferedInputStream.read()) != -1) {
                          bufferedOutputStream.write(i);
                      }
                  }
              }
              //額外使用一個8KB緩沖,再使用BufferedInputStream和BufferedOutputStream
              private static void bufferedStreamBufferOperation() throws IOException {
                  try (BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream("src.txt"));
                          BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream("dest.txt"))) {
                       byte[] buffer = new byte[8192]; // 8KB
                       int len = 0;
                       while ((len = bufferedInputStream.read(buffer)) != -1) {
                          bufferedOutputStream.write(buffer, 0, len);
                       }
                  }
              }
              //直接使用FileInputStream和FileOutputStream,再使用一個8KB的緩沖
              private static void largerBufferOperation() throws IOException {
                  try (FileInputStream fileInputStream = new FileInputStream("src.txt");
                       FileOutputStream fileOutputStream = new FileOutputStream("dest.txt")) {
                      byte[] buffer = new byte[8192];
                      int len = 0;
                      while ((len = fileInputStream.read(buffer)) != -1) {
                          fileOutputStream.write(buffer, 0, len);
                      }
                  }
              }
      

      性能:
      --------------------------------------------
      ns
      %
      Task name--------------------------------------------
      1424649223 086% bufferedStreamByteOperation
      117807808 007% bufferedStreamBufferOperation
      112153174 007% largerBufferOperation

      可以看到,第一種方式雖然使用了緩沖流,但逐字節(jié)的操作因為方法調(diào)用次數(shù)實在太多還是慢;后面兩種方式的性能差不多。雖然第三種方式?jīng)]有使用緩沖流,但使用了 8KB 大小的緩沖區(qū),和緩沖流默認的緩沖區(qū)大小相同。

      BufferedInputStream 和 BufferedOutputStream 的意義

      在實際代碼中每次需要讀取的字節(jié)數(shù)很可能不是固定的,有的時候讀取幾個字節(jié),有的時候讀取幾百字節(jié),這個時候
      有一個固定大小較大的緩沖,也就是使用 BufferedInputStream 和 BufferedOutputStream 做為后備的穩(wěn)定的二次緩沖,就非常有意義了。
      最后我要補充說明的是,對于類似的文件復制操作,如果希望有更高性能,可以使用
      FileChannel 的 transfreTo 方法進行流的復制。在一些操作系統(tǒng)(比如高版本的 Linux 和 UNIX)上可以實現(xiàn) DMA(直接內(nèi)存訪問),也就是數(shù)據(jù)從磁盤經(jīng)過總線直接發(fā)送到目標文件,無需經(jīng)過內(nèi)存和 CPU 進行數(shù)據(jù)中轉:

      private static void fileChannelOperation() throws IOException {
               FileChannel in = FileChannel.open(Paths.get("src.txt"), StandardOpenOption
               FileChannel out = FileChannel.open(Paths.get("dest.txt"), CREATE, WRITE);
               in.transferTo(0, in.size(), out);
       }
      

      總結

      本文分享了文件讀寫操作中最重要的幾個方面。
      第一,如果需要讀寫字符流,那么需要確保文件中字符的字符集和字符流的字符集是一致的,否則可能產(chǎn)生亂碼。
      第二,使用 Files 類的一些流式處理操作,注意使用 try-with-resources 包裝 Stream,確保底層文件資源可以釋放,避免產(chǎn)生 too many open files 的問題。
      第三,進行文件字節(jié)流操作的時候,一般情況下不考慮進行逐字節(jié)操作,使用緩沖區(qū)進行批量讀寫減少 IO 次數(shù),性能會好很多。一般可以考慮直接使用緩沖輸入輸出流BufferedXXXStream,追求極限性能的話可以考慮使用 FileChannel 進行流轉發(fā)。
      最后我要強調(diào)的是,文件操作因為涉及操作系統(tǒng)和文件系統(tǒng)的實現(xiàn),JDK 并不能確保所有 IO API 在所有平臺的邏輯一致性,代碼遷移到新的操作系統(tǒng)或文件系統(tǒng)時,要重新進行功
      能測試和性能測試。

      posted @ 2024-09-24 15:53  heyhy  Views(248)  Comments(0)    收藏  舉報
      Title
      主站蜘蛛池模板: 无码成人精品区在线观看| 9lporm自拍视频区| 国产不卡一区二区在线| 亚洲伊人成无码综合网| 又大又粗又爽的少妇免费视频| 九九热在线精品视频九九| 国产jizzjizz视频| 国产一区二区午夜福利久久| 国产极品粉嫩学生一线天| 免费看的一级毛片| 国产尤物精品自在拍视频首页| ww污污污网站在线看com| 精品无码国产日韩制服丝袜| 国产97色在线 | 免费| 色综合国产一区二区三区| 午夜成人无码免费看网站| 国产精品久久久一区二区| 国内少妇人妻偷人精品视频| 亚洲老妇女亚洲老熟女久| 中文字幕人成无码免费视频| 最新亚洲av日韩av二区| 真实单亲乱l仑对白视频| 久久国产福利播放| 晋中市| 亚洲另类在线制服丝袜国产| 在线视频中文字幕二区| 在线精品视频一区二区三四| 色综合久久婷婷88| 精品国产亚洲一区二区三区| 亚洲国产综合自在线另类| 精品中文字幕人妻一二| 妺妺窝人体色www聚色窝仙踪| 成在线人视频免费视频| 91中文字幕在线一区| 在线精品另类自拍视频| 国产一区在线播放av| 亚洲国产精品久久一线不卡| 日韩中文字幕亚洲精品| 福利一区二区在线观看| 欧美性插b在线视频网站| 国产精品自拍自在线播放|