基礎篇:一文讓你讀懂JAVA.IO、字符編碼、URL和Spring.Resource

2020-08-28 java架構葉子

1 JAVA.IO字節流

inputstream.png

  • LineNumberInputStream和StringBufferInputStream官方建議不再使用,推薦使用LineNumberReader和StringReader代替
  • ByteArrayInputStream和ByteArrayOutputStream 字節數組處理流,在內存中建立一個緩衝區作為流使用,從緩存區讀取數據比從存儲介質(如磁碟)的速率快

//用ByteArrayOutputStream暫時緩存來自其他渠道的數據ByteArrayOutputStream data = new ByteArrayOutputStream(1024); //1024位元組大小的緩存區data.write(System.in.read()); // 暫存用戶輸入數據//將data轉為ByteArrayInputStreamByteArrayInputStream in = new ByteArrayInputStream(data.toByteArray());

  • FileInputStream和FileOutputStream 訪問文件,把文件作為InputStream,實現對文件的讀寫操作
  • ObjectInputStream和ObjectOutputStream 對象流,構造函數需要傳入一個流,實現對JAVA對象的讀寫功能;可用於序列化,而對象需要實現Serializable接口

//java對象的寫入FileOutputStream fileStream = new FileOutputStream(&34;);ObjectOutputStream out = new ObjectOutputStream(fileStream);Example example = new Example();out.writeObject(example);//java對象的讀取FileInputStream fileStream = new FileInputStream(&34;);ObjectInputStream in = new ObjectInputStream(fileStream);Example = (Example) in.readObject();

  • PipedInputStream和PipedOutputStream 管道流,適用在兩個線程中傳輸數據,一個線程通過管道輸出流發送數據,另一個線程通過管道輸入流讀取數據,實現兩個線程間的數據通信

// 創建一個發送者對象Sender sender = new Sender(); // 創建一個接收者對象Receiver receiver = new Receiver(); // 獲取輸出管道流// 獲取輸入輸出管道流PipedOutputStream outputStream = sender.getOutputStream(); PipedInputStream inputStream = receiver.getInputStream();// 連結兩個管道,這一步很重要,把輸入流和輸出流聯通起來 outputStream.connect(inputStream);sender.start();// 啟動發送者線程receiver.start();// 啟動接收者線程

  • SequenceInputStream 把多個InputStream合併為一個InputStream,允許應用程式把幾個輸入流連續地合併起來

InputStream in1 = new FileInputStream(&34;);InputStream in2 = new FileInputStream(&34;);SequenceInputStream sequenceInputStream = new SequenceInputStream(in1, in2);//數據讀取int data = sequenceInputStream.read();

  • FilterInputStream和FilterOutputStream 使用了裝飾者模式來增加流的額外功能,子類構造參數需要一個InputStream/OutputStream

ByteArrayOutputStream out = new ByteArrayOutputStream(2014);//數據寫入,使用DataOutputStream裝飾一個InputStream//使用InputStream具有對基本數據的處理能力DataOutputStream dataOut = new DataOutputStream(out);dataOut.writeDouble(1.0);//數據讀取ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());DataInputStream dataIn = new DataInputStream(in);Double data = dataIn.readDouble();

  • DataInputStream和DataOutputStream (Filter流的子類) 為其他流附加處理各種基本類型數據的能力,如byte、int、String
  • BufferedInputStream和BufferedOutputStream (Filter流的子類) 為其他流增加緩衝功能
  • PushBackInputStream (FilterInputStream子類) 推回輸入流,可以把讀取進來的某些數據重新回退到輸入流的緩衝區之中
  • PrintStream (FilterOutputStream子類) 列印流,功能類似System.out.print

2 JAVA.IO字符流

21.png

  • 從字節流和字符流的導向圖來,它們之間是相互對應的,比如CharArrayReader和ByteArrayInputStream
  • 字節流和字符流的轉化:InputStreamReader可以將InputStream轉為Reader,OutputStreamReader可以將OutputStream轉為Writer

//InputStream轉為ReaderInputStream inputStream = new ByteArrayInputStream(&34;.getBytes());InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);//OutputStream轉為WriterOutputStream out = new FileOutputStream(&34;);OutputStreamWriter writer = new OutputStreamWriter(out);//以字符為單位讀寫writer.write(reader.read(new char[2]));

  • 區別:字節流讀取單位是字節,字符流讀取單位是字符;一個字符由字節組成,如變字長編碼UTF-8是由1~4個字節表示

3 亂碼問題和字符流

  • 字符以不同的編碼表示,它的字節長度(字長)是不一樣的。如「程」的utf-8編碼格式,由[-25][-88][-117]組成。而ISO_8859_1編碼則是單個字節[63]
  • 平時工作對資源的操作都是面向字節流的,然而數據資源根據不同的字節編碼轉為字節時,它們的內容是不一樣,容易造成亂碼問題
  • 兩種出現亂碼場景 encode和decode使用的字符編碼不一致:資源使用UTF-8編碼,而在代碼裡卻使用GBK解碼打開使用字節流讀取字節數不符合字符規定字長:字符是由字節組成的,比如「程」的utf-8格式是三個字節;如果在InputStream裡以每兩個字節讀取流,再轉為String(java默認編碼是utf-8),此時會出現亂碼(半個中文,你猜是什麼)

ByteArrayInputStream in = new ByteArrayInputStream(&34;.getBytes());byte[] buf = new byte[2]; //讀取流的兩個字節in.read(buf); //讀取數據System.out.println(new String(buf)); //亂碼---result---- � //亂碼

  • 亂碼場景1,知道資源的字符編碼,就可以使用對應的字符編碼來解碼解決
  • 亂碼場景2,可以一次性讀取所有字節,再一次性編碼處理。但是對於大文件流,這是不現實的,因此有了字符流的出現
  • 字節流使用InputStreamReader、OutputStreamReader轉化為字符流,其中可以指定字符編碼,再以字符為單位來處理,可解決亂碼

InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);

4 字符集和字符編碼的概念區分

  • 字符集和字符編碼的關係,字符集是規範,字符編碼是規範的具體實現;字符集規定了符號和二進位代碼值的唯一對應關係,但是沒有指定具體的存儲方式;
  • unicode、ASCII、GB2312、GBK都是字符集;其中ASCII、GB2312、GBK既是字符集也是字符編碼;注意不混淆這兩者區別;而unicode的具體實現有UTF-8,UTF-16,UTF-32
  • 最早出現的ASCII碼是使用一個字節(8bit)來規定字符和二進位映射關係,標準ASCII編碼規定了128個字符,在英文的世界,是夠用的。但是中文,日文等其他文字符號怎麼映射呢?因此其他更大的字符集出現了
  • unicode(統一字符集),早期時它使用2個byte表示1個字符,整個字符集可以容納65536個字符。然而仍然不夠用,於是擴展到4個byte表示一個字符,現支持範圍是U+010000~U+10FFFF
  • unicode是兩個字節的說法是錯誤的;UTF-8是變字長的,需要用1~4個字節存儲;UTF-16一般是兩個字節(U+0000~U+FFFF範圍),如果遇到兩個字節存不下,則用4個字節;而UTF-32是固定四個字節
  • unicode表示的字符,會用「U+」開頭,後面跟著十六進位的數字,如「字」的編碼就是U+5B57
  • UTF-8 編碼和unicode字符集

範圍 Unicode(Binary) UTF-8編碼(Binary) UTF-8編碼byte長度 U+0000~U+007F 00000000 00000000 00000000 0XXXXXXX 0XXXXXX 1 U+0080~U+07FF 00000000 00000000 00000YYY YYXXXXXX 110YYYYY 10XXXXXX 2 U+0800~U+FFFF 00000000 00000000 ZZZZYYYY YYXXXXXX 1110ZZZZ 10YYYYYY 10XXXXXX 3 U+010000~U+10FFFF 00000000 000AAAZZ ZZZZYYYY YYXXXXXX 11110AAA 10ZZZZZZ 10YYYYYY 10XXXXXX 4

  • 程序是分內碼和外碼,java的默認編碼是UTF-8,其實指的是外碼;內碼傾向於使用定長碼,和內存對齊一個原理,便於處理。外碼傾向於使用變長碼,變長碼將常用字符編為短編碼,罕見字符編為長編碼,節省存儲空間與傳輸帶寬
  • JDK8的字符串,是使用char[]來存儲字符的,char是兩個字節大小,其中使用的是UTF-16編碼(內碼)。而unicode規定的中文字符在U+0000~U+FFFF內,因此使用char(UTF-16編碼)存儲中文是不會出現亂碼的
  • JDK9後,字符串則使用byte[]數組來存儲,因為有一些字符一個char已經存不了,如emoji表情字符,使用字節存儲字符串更容易拓展
  • JDK9,如果字符串的內容都是ISO-8859-1/Latin-1字符(1個字符1位元組),則使用ISO-8859-1/Latin-1編碼存儲字符串,否則使用UTF-16編碼存儲數組(2或4個字節)

System.out.println(Charset.defaultCharset()); //輸出java默認編碼for (byte item : &34;.getBytes(StandardCharsets.UTF_16)) { System.out.print(&34; + item + &34;);}System.out.println(&34;);for (byte item : &34;.getBytes(StandardCharsets.UTF_8)) { System.out.print(&34; + item + &34;);}----result----UTF-8 //java默認編碼UTF-8[-2][-1][122][11][94][-113] //UTF_16:6個字節?[-25][-88][-117][-27][-70][-113] //UTF_8:6個字節 正常

  • 「程序」的UTF-16編碼竟是輸出6個字節,多出了兩個字節,這是什麼情況?再試試一個字符的輸出

for (byte item : &34;.getBytes(StandardCharsets.UTF_16)) { System.out.print(&34; + item + &34;);}---result--[-2][-1][122][11]

  • 可以看出UTF-16編碼的字節是多了[-2][-1]兩個字節,十六進位是0xFEFF。而它用來標識編碼順序是Big endian還是Little endian。以字符&39;為例,它的unicode十六進位是4E2D,存儲時4E在前,2D在後,就是Big endian;2D在前,4E在後,就是Little endian。FEFF表示存儲採用Big endian,FFFE表示使用Little endian
  • 為什麼UTF-8沒有字節序的問題呢?個人看法,因為UTF-8是變長的,由第一個字節的頭部的0、110、1110、11110判斷是否需後續幾個字節組成字符,使用Big endian易讀取處理,反過來不好處理,因此強制用Big endian
  • 其實感覺UTF-16可以強制規定用Big endian;但這其中歷史問題。。。

5 URI概念的簡單介紹

  • 既然有了java.io來操作資源流;但是對於網絡的資源,該怎麼打開,怎麼定位呢?答URI-URL
  • URI全稱是Uniform Resource Identifier 統一資源標識符
  • 通俗說,就是一個類似身份證號碼的字符串,只不過它是用來標識資源(如:郵件地址,主機名,文件等)
  • URI 具有特定的規則: [scheme]:[scheme-specific-part][fragment],其中模式特定部分為authority和path、query;而authority可以看做域名,如www.baidu.com終極細分則是[scheme]:[//host:port][/path][?query][片段[protocol]:[//host:port][/path][?query][34;D:/example.txt&34;example.txt&34;test.txt&34;classpath:&34;classpath*:&34;classpath*:&34;com/smart/**/*.xml&34;file:///res.txt&34;https://my.cn/res.txt&34;res.properties&34;username&34;res.yml&34;name"); // chen//url: http://www.my.com

    11 優雅地關閉資源,try-with-resource語法和lombok@Cleanup

    • 資源的打開就需要對應的關閉,但我們常會忘記關閉資源,或在多處代碼關閉資源感到雜亂,有沒有簡潔的關閉方法呢?
    • 自動關閉資源類需實現AutoCloseable接口和配合try-with-resource語法糖使用

    public class YSOAPConnection implements AutoCloseable { private SOAPConnection connection; public static YSOAPConnection open(SOAPConnectionFactory soapConnectionFactory) throws SOAPException { YSOAPConnection ySoapConnection = new YSOAPConnection(); SOAPConnection connection = soapConnectionFactory.createConnection(); ySoapConnection.setConnection(connection); return ySoapConnection; } public SOAPMessage call(SOAPMessage request, Object to) throws SOAPException { return connection.call(request, to); } @Override public void close() throws SOAPException { if (connection != null) { connection.close(); } }}

    //自動關閉的資源類使用示例try (YSOAPConnection soapConnection=YSOAPConnection.open(soapConnectionFactory)){ SOAPMessage soapResponse = soapConnection.call(request, endpoint); ...//數據操作} catch (Exception e) { log.error(e.getMessage(), e); ...}

    • lombok註解@Cleanup,對象生命周期結束時會調用public void close();對象需實現AutoCloseable接口

    import lombok.Cleanup;@Cleanup // @Cleanup的使用YSOAPConnection soapConnection=YSOAPConnection.open(soapConnectionFactory)

    12 資源不關閉,會導致什麼最壞的結果

    • JDK的原生資源類不關閉,它也不會永遠存在。JVM會藉助finalize自動關閉它,例如FileInputStream

    //FileInputStream.java - JDK8//jdk8的FileInputStream重寫了finalize,保證對象回收前開啟的資源被關閉protected void finalize () throws IOException { if (guard != null) { guard.warnIfOpen(); } if ((fd != null) && (fd != FileDescriptor.in)) { close(); }}

    • 在JDK9後,用Cleaner機制代替了finalize機制;Cleaner機制自動回收的對象同樣需要實現AutoCloseable接口;Cleaner是基於PhantomReference實現的;對實現細節感興趣的同學,可自行查閱下相關文檔
    • 但是使用JDK的提供的資源關閉機制的,那麼資源的關閉比手動關閉時要延後很長時間的。據測試,使用try-with-resources關閉資源,並讓垃圾回收器回收它的時間在12納秒。而使用finalizer機制,時間增加到550納秒
    • 不及時關閉資源,就會佔用資源,影響其他線程的執行;比如linux的文件資源,linux進程默認能打開的最大文件數是1024(有的是2048,此數值是可配置的);如果一個線程持有十幾個文件資源,還要等550納秒用finalizer機制釋放資源,同進程的其他線程都等到花謝了


    作者:clswcl
    連結:https://juejin.im/post/6856266775022174222
    來源:掘金

相關焦點

  • 一文讓你讀懂JAVA.IO、字符編碼、
    而ISO_8859_1編碼則是單個字節[63]平時工作對資源的操作都是面向字節流的,然而數據資源根據不同的字節編碼轉為字節時,它們的內容是不一樣,容易造成亂碼問題兩種出現亂碼場景 encode和decode使用的字符編碼不一致:資源使用UTF-8編碼,而在代碼裡卻使用GBK解碼打開使用字節流讀取字節數不符合字符規定字長:字符是由字節組成的,比如「程」的utf-8格式是三個字節
  • 使用Spring框架可以使用Spring的哪些常用工具類呢?
    處理註解org.springframework.core.io.support.PathMatchingResourcePatternResolver 用 於處理 ant 匹配風格(com/.jsp, com//.jsp),找出所有的資源, 結合上面的resource的概念一起使用,對於遍歷文件很有用.
  • java中的IO流(字符流和字節流)
    3、Java程序如何訪問文件的屬性來自Java.io.File 類,不能操作內容。操作內容是通過流來讀或寫。4、File類訪問文件屬性File類位於Java.io包下,用於管理本地的文件和文件夾,通過File類在程序中操作硬碟上的文件和目錄。如創建、刪除文件和文件夾,File類不用來對文件的內容進行讀寫。
  • 「JAVA」字節流、字符流、緩衝流、轉換流、內存流、字符編碼
    字符輸入流,FileReader,字符輸入流的使用案例代碼如下:import java.io.File;import java.io.FileReader;import java.io.Reader;/** * 文件的字符輸入流案例 */public class FileReaderDemo { public static
  • 一文讀懂resource.arsc文件結構
    表示字符串是經過排序的,而 UTF8_FLAG 位等於1表示字符串是使用 UTF8 編碼的,否則就是 UTF16 編碼的stringsStart : 字符串內容與常量池頭部起始點之間的偏移距離 : 資源項字符串相對頭部的偏移位置lastPublicKey : 一資源項名稱字符串資源池的大小
  • JAVA IO Stream流總結
    我們把這種數據的傳輸,可以看做是一種數據的流動,按照流動的方向,以內存為基準,分為 輸入input 和 輸出 output ,即流向內存是輸入流,流出內存的輸出流。Java中I/O操作主要是指使用 java.io 包下的內容,進行輸入、輸出操作。輸入也叫做讀取數據,輸出也叫做作寫 出數據。一.java.io.File 類1.
  • 一文解開java中字符串編碼的小秘密
    簡介在本文中你將了解到Unicode和UTF-8,UTF-16,UTF-32的關係,同時你還會了解變種UTF-8,並且探討一下UTF-8和變種UTF-8在java中的應用。一起來看看吧。接下來的1,920個字符需要兩個字節進行編碼,涵蓋了幾乎所有拉丁字母字母表的其餘部分,以及希臘語,西裡爾字母,科普特語,亞美尼亞語,希伯來語,阿拉伯語,敘利亞語,Thaana和N』Ko字母,以及組合變音符號標記。BMP中的其餘部分中的字符需要三個字節,其中幾乎包含了所有常用字符,包括大多數中文,日文和韓文字符。
  • Spring中資源的加載原來是這麼一回事啊!
    Resource 接口spring 中的 Resource 接口目的在於成為一種功能更加強大的接口,用於抽象化對具體資源的訪問,它繼承了 org.springframework.core.io.InputStreamSource 接口,作為資源定義的頂級接口, Resource 內部定義了通用的方法,並且有它的子類 AbstractResource 來提供統一的默認實現,
  • 你確定會?Spring集成MyBatis | Spring系列第53篇
    1、本文內容【文末送書】本文主要介紹mybatis和spring集成的兩種方式,對MyBatis不熟悉的,建議先看一下MyBatis高手系列目前註解的方式我們用的比較多,所以主要介紹註解的方式,xml的方式這裡就暫時不介紹了。
  • SpringCloud alibaba:從基礎配置到打包帶走
    >1.8</java.version> </properties> <dependencies> <!> <dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-httpclient</artifactId> </dependency> <!
  • K8S官方java客戶端之六:OpenAPI基本操作
    客戶端》系列的第六篇,以下提到的java客戶端都是指client-jar.jar;前文《 》已經提到,java客戶端的基本功能由兩個主要脈絡組成,第一個是proto,主要功能是使用ProtoClient類提供的增刪改查接口,這些接口用到的入參和返回對象所涉及到的java類,都是通過
  • Java程式設計師必看 使用ssm框架開發web項目時遇到的那些坑!
    計算機中數據類型的概念有兩種為位bits和bytes字節,一個字節有8位。二進位的計算機系統則只能讀懂0和1,人類只能讀懂characters 文字,而在現實生活中我們需要藉助計算機來顯示文字信息,而不同國家的人使用的文字時不一樣的,於是人們為了讓每個國家的人都能夠通過計算機讀懂自己想表達的意思,就需要將文字轉換成計算機認識的數據類型,字節。
  • spring-boot-dependencies引入spring boot
    兩種方式快速創建一個spring boot的maven工程一、使用<parent>標籤繼承父模塊 <parentStarter Parent</name> <description>Parent pom providing dependency and plugin management for applications built with Maven</description> <url>https://projects.spring.io
  • Spring Batch 原始碼進行編譯的時候提示倉庫錯誤
    在對 Spring Batch 原始碼進行編譯的時候,提示倉庫配置錯誤錯誤:org.gradle.api.resources.ResourceException: Could not get resource 'http://repo.spring.io
  • 我的Java Web之路58 - Spring整合ORM(MyBatis)2
    本系列文章旨在記錄和總結自己在Java Web開發之路上的知識點、經驗、問題和思考,希望能幫助更多(Java)碼農和想成為(Java)碼農的人。package houserenter.config;import java.io.IOException;import java.io.InputStream;import org.apache.ibatis.io.Resources;import org.apache.ibatis.session.SqlSessionFactory;import org.apache.ibatis.session.SqlSessionFactoryBuilder
  • Spring中Spring Java Format插件的作用
    如果我告訴你有一個工具可以使你的Java項目中的「Spring」風格更加一致,那該怎麼辦?那就對了!Spring創建了一組可應用於任何Java項目的插件,並承諾提供一致的「Spring」樣式。讓我們仔細看看。
  • 基於Spring Boot 2.2.6實現Rest風格的文件上傳&下載APIs-附源碼
    service/FileStorageService.javapackage com.ramostear.springboot.uploadfile.service;import org.springframework.core.io.Resource;
  • IO,對象傳輸的基石(一)
    ;import java.io.BufferedOutputStream;import java.io.File;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.InputStream;import java.io.OutputStream;import java.io.Writer; //用緩衝流和緩衝數組提高讀寫效率
  • Spring Boot 教程:文件處理
    本文學習如何使用 web 服務進行文件上傳和下載。;import java.io.FileOutputStream;import java.io.IOException;import org.springframework.http.MediaType;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestMethod
  • Java中IO流的通俗講義
    此文章適合於和我一樣還在學習java基礎的小白,若正準備接觸IO流,那這篇文章正適合,因為在前期會用IO流便好,所以沒有太多的深究其底層原理,注意:我們只需要重點理解輸入流和輸出流的區別,還要在面對不同的傳輸數據特性來選擇正確的流進行使用。