04.JavaIO流問題

目錄介紹

  • 4.0.0.1 說一下Java IO裏面的常見類,字節流,字符流、接口、實現類、方法阻塞?
  • 4.0.0.2 什麼是比特(Bit),什麼是字節(Byte),什麼是字符(Char),它們長度是多少,各有什麼區別?
  • 4.0.0.3 字符流和字節流有什麼區別?如何選擇字節流或者字符流?什麼是緩衝區,有什麼做用?
  • 4.0.0.4 IO流中用到哪些模式?談一談IO流中用到的適配器模式和裝飾者模式的做用優點?
  • 4.0.0.5 說一下對NIO的理解?NIO和IO的主要區別?NIO和IO如何影響應用程序的設計?
  • 4.0.0.9 如何實現對象克隆?克隆有哪些方式?深克隆和淺克隆有何區別?深克隆和淺克隆分別說的是什麼意思?
  • 4.0.1.0 淺拷貝會建立新的對象嗎?對於基本類型拷貝的是什麼?怎麼樣實現淺拷貝,淺拷貝先後對象地址值會同樣嗎?
  • 4.0.1.1 對字節流進行大量的從硬盤讀取,要用那個流,爲何?有什麼須要注意的問題?

好消息

  • 博客筆記大彙總【15年10月到至今】,包括Java基礎及深刻知識點,Android技術博客,Python學習筆記等等,還包括平時開發中遇到的bug彙總,固然也在工做之餘收集了大量的面試題,長期更新維護而且修正,持續完善……開源的文件是markdown格式的!同時也開源了生活博客,從12年起,積累共計500篇[近100萬字],將會陸續發表到網上,轉載請註明出處,謝謝!
  • 連接地址:github.com/yangchong21…
  • 若是以爲好,能夠star一下,謝謝!固然也歡迎提出建議,萬事起於忽微,量變引發質變!全部博客將陸續開源到GitHub!

4.0.0.1 說一下Java IO裏面的常見類,字節流,字符流、接口、實現類、方法阻塞?

  • 輸出流和輸入流
    • 輸入流就是從外部文件輸入到內存,輸出流主要是從內存輸出到文件。
  • IO裏面常見的類
    • IO流中有不少類,IO流主要分爲字符流和字節流。字節流中有抽象類InputStream和OutputStream,它們的子類FileInputStream,FileOutputStream,BufferedOutputStream等。字符流BufferedReader和Writer等。都實現了Closeable, Flushable, Appendable這些接口。程序中的輸入輸出都是以流的形式保存的,流中保存的實際上全都是字節文件。
  • IO流中方法阻塞
    • java中的阻塞式方法是指在程序調用改方法時,必須等待輸入數據可用或者檢測到輸入結束或者拋出異常,不然程序會一直停留在該語句上,不會執行下面的語句。好比read()和readLine()方法。
    • 技術博客大總結

4.0.0.2 什麼是比特(Bit),什麼是字節(Byte),什麼是字符(Char),它們長度是多少,各有什麼區別?

  • 什麼是比特(Bit)?
    • Bit最小的二進制單位 ,是計算機的操做部分 取值0或者1
  • 什麼是字節
    • Byte是計算機操做數據的最小單位由8位bit組成 取值(-128-127)
  • 什麼是字符
    • Char是用戶的可讀寫的最小單位,在Java裏面由16位bit組成 取值(0-65535)
  • 各有什麼區別
    • Bit 是最小單位 計算機 只能認識 0或者1

4.0.0.3 字符流和字節流有什麼區別?如何選擇字節流或者字符流?什麼是緩衝區,有什麼做用?

  • 字符流和字節流區別
    • 把二進制數據數據逐一輸出到某個設備中,或者從某個設備中逐一讀取一片二進制數據,無論輸入輸出設備是什麼,咱們要用統一的方式來完成這些操做,用一種抽象的方式進行描述,這個抽象描述方式起名爲IO流,對應的抽象類爲OutputStream和InputStream ,不一樣的實現類就表明不一樣的輸入和輸出設備,它們都是針對字節進行操做的。
    • 在應用中,常常要徹底是字符的一段文本輸出去或讀進來,用字節流能夠嗎?計算機中的一切最終都是二進制的字節形式存在。對於「中國」這些字符,首先要獲得其對應的字節,而後將字節寫入到輸出流。讀取時,首先讀到的是字節,但是咱們要把它顯示爲字符,咱們須要將字節轉換成字符。因爲這樣的需求很普遍,人家專門提供了字符流的包裝類。
    • 底層設備永遠只接受字節數據,有時候要寫字符串到底層設備,須要將字符串轉成字節再進行寫入。字符流是字節流的包裝,字符流則是直接接受字符串,它內部將串轉成字節,再寫入底層設備,這爲咱們向IO設別寫入或讀取字符串提供了一點點方便。
    • 技術博客大總結
    • 字符流和字節流的使用很是類似,可是實際上字節流的操做不會通過緩衝區(內存)而是直接操做文本自己的,而字符流的操做會先通過緩衝區(內存)而後經過緩衝區再操做文件。
  • 如何選擇字節流或者字符流?
    • 字符流是由Java虛擬機將字節轉化爲2個字節的Unicode字符爲單位的字符而成的
    • 若是是音頻文件、圖片、歌曲,就用字節流好點(避免數據丟失)
    • 若是是關係到中文(文本)的,用字符流好點)
  • 什麼是緩衝區,有什麼做用?
    • 緩衝區就是一段特殊的內存區域,不少狀況下當程序須要頻繁地操做一個資源(如文件或數據庫)則性能會很低,因此爲了提高性能就能夠將一部分數據暫時讀寫到緩存區,之後直接今後區域中讀寫數據便可,這樣就顯著提高了性能。
    • 對於 Java 字符流的操做都是在緩衝區操做的,因此若是咱們想在字符流操做中主動將緩衝區刷新到文件則可使用 flush() 方法操做。

4.0.0.4 IO流中用到哪些模式?談一談IO流中用到的適配器模式和裝飾者模式的做用優點?

  • IO流中用到哪些模式
    • 大概有裝飾者模式和適配器模式!
    • 要知道裝飾者模式和適配器模式的做用;其次,能夠本身舉個例子把它的做用生動形象地講出來;最後,簡要說一下要完成這樣的功能須要什麼樣的條件。
  • 談一談IO流中用到的適配器模式和裝飾者模式的做用優點
    • 裝飾器模式:就是動態地給一個對象添加一些額外的職責(對於原有功能的擴展)。
      //把InputStreamReader裝飾成BufferedReader來成爲具有緩衝能力的Reader。
      BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
      複製代碼
      • 1.它必須持有一個被裝飾的對象(做爲成員變量)。
      • 2.它必須擁有與被裝飾對象相同的接口(多態調用、擴展須要)。
      • 3.它能夠給被裝飾對象添加額外的功能。
      • 好比,在io流中,FilterInputStream類就是裝飾角色,它實現了InputStream類的全部接口,並持有InputStream的對象實例的引用,BufferedInputStream是具體的裝飾器實現者,這個裝飾器類的做用就是使得InputStream讀取的數據保存在內存中,而提升讀取的性能。
    • 適配器模式:將一個類的接口轉換成客戶指望的另外一個接口,讓本來不兼容的接口能夠合做無間。
      //把FileInputStream文件字節流適配成InputStreamReader字符流來操做文件字符串。
      FileInputStream fileInput = new FileInputStream(file); 
      InputStreamReader inputStreamReader = new InputStreamReader(fileInput);
      複製代碼
      • 1.適配器對象實現原有接口
      • 2.適配器對象組合一個實現新接口的對象
      • 3.對適配器原有接口方法的調用被委託給新接口的實例的特定方法(重寫舊接口方法來調用新接口功能。)
      • 好比,在io流中,InputStreamReader類繼承了Reader接口,但要建立它必須在構造函數中傳入一個InputStream的實例,InputStreamReader的做用也就是將InputStream適配到Reader。InputStreamReader實現了Reader接口,而且持有了InputStream的引用。這裏,適配器就是InputStreamReader類,而源角色就是InputStream表明的實例對象,目標接口就是Reader類。
      • 適配器模式主要在於將一個接口轉變成另外一個接口,它的目的是經過改變接口來達到重複使用的目的;而裝飾器模式不是要改變被裝飾對象的接口,而是保持原有的接口,可是加強原有對象的功能,或改變原有對象的方法而提升性能。
  • 用到設計模式優點
    • 裝飾者模式就是給一個對象增長一些新的功能,並且是動態的,要求裝飾對象和被裝飾對象實現同一個接口,裝飾對象持有被裝飾對象的實例(各類字符流間裝飾,各類字節流間裝飾)。
    • 技術博客大總結
    • 適配器模式就是將某個類的接口轉換成咱們指望的另外一個接口表示,目的是消除因爲接口不匹配所形成的類的兼容性問題(字符流與字節流間互相適配)。

4.0.0.5 說一下對NIO的理解?NIO和IO的主要區別?NIO和IO如何影響應用程序的設計?

  • 說一下對NIO的理解?
    • 傳統的IO流是阻塞式的,會一直監聽一個ServerSocket,在調用read等方法時,它會一直等到數據到來或者緩衝區已滿時才返回。調用accept也是一直阻塞到有客戶端鏈接纔會返回。每一個客戶端鏈接過來後,服務端都會啓動一個線程去處理該客戶端的請求。而且多線程處理多個鏈接。每一個線程擁有本身的棧空間而且佔用一些CPU時間。每一個線程遇到外部未準備好的時候,都會阻塞掉。阻塞的結果就是會帶來大量的進程上下文切換。
    • 對於NIO,它是非阻塞式,核心類:
      • 1.Buffer爲全部的原始類型提供 (Buffer)緩存支持。
      • 2.Charset字符集編碼解碼解決方案
      • 3.Channel一個新的原始I/O抽象,用於讀寫Buffer類型,通道能夠認爲是一種鏈接,能夠是到特定設備,程序或者是網絡的鏈接。
  • NIO和IO的主要區別?
    • 主要區別
      IO 	NIO
      面向流 	面向緩衝
      阻塞IO 	非阻塞IO
      無 	選擇器
      複製代碼
    • 面向流與面向緩衝
      • Java IO和NIO之間第一個最大的區別是,IO是面向流的,NIO是面向緩衝區的。JavaIO面向流意味着每次從流中讀一個或多個字節,直至讀取全部字節,它們沒有被緩存在任何地方。此外,它不能先後移動流中的數據。若是須要先後移動從流中讀取的數據,須要先將它緩存到一個緩衝區。JavaNIO的緩衝導向方法略有不一樣。數據讀取到一個它稍後處理的緩衝區,須要時可在緩衝區中先後移動。這就增長了處理過程當中的靈活性。可是,還須要檢查是否該緩衝區中包含全部您須要處理的數據。並且,需確保當更多的數據讀入緩衝區時,不要覆蓋緩衝區裏還沒有處理的數據。
    • 阻塞與非阻塞IO
      • Java IO的各類流是阻塞的。這意味着,當一個線程調用read()或write()時,該線程被阻塞,直到有一些數據被讀取,或數據徹底寫入。該線程在此期間不能再幹任何事情了。JavaNIO的非阻塞模式,使一個線程從某通道發送請求讀取數據,可是它僅能獲得目前可用的數據,若是目前沒有數據可用時,就什麼都不會獲取,而不是保持線程阻塞,因此直至數據變的能夠讀取以前,該線程能夠繼續作其餘的事情。非阻塞寫也是如此。一個線程請求寫入一些數據到某通道,但不須要等待它徹底寫入,這個線程同時能夠去作別的事情。線程一般將非阻塞IO的空閒時間用於在其它通道上執行IO操做,因此一個單獨的線程如今能夠管理多個輸入和輸出通道(channel)。
    • 選擇器
      • Java NIO的選擇器容許一個單獨的線程來監視多個輸入通道,你能夠註冊多個通道使用一個選擇器,而後使用一個單獨的線程來「選擇」通道:這些通道里已經有能夠處理的輸入,或者選擇已準備寫入的通道。這種選擇機制,使得一個單獨的線程很容易來管理多個通道。
  • NIO和IO如何影響應用程序的設計?
    • 不管您選擇IO或NIO工具箱,可能會影響您應用程序設計的如下幾個方面:技術博客大總結
      • 1.對NIO或IO類的API調用。
      • 2.數據處理。
      • 3.用來處理數據的線程數。
    • API調用
      • 固然,使用NIO的API調用時看起來與使用IO時有所不一樣,但這並不意外,由於並非僅從一個InputStream逐字節讀取,而是數據必須先讀入緩衝區再處理。
    • 數據處理
      • 使用純粹的NIO設計相較IO設計,數據處理也受到影響。在IO設計中,咱們從InputStream或 Reader逐字節讀取數據。
      • 請注意處理狀態由程序執行多久決定。換句話說,一旦reader.readLine()方法返回,你就知道確定文本行就已讀完, readline()阻塞直到整行讀完,這就是緣由。你也知道此行包含名稱;一樣,第二個readline()調用返回的時候,你知道這行包含年齡等。正如你能夠看到,該處理程序僅在有新數據讀入時運行,並知道每步的數據是什麼。一旦正在運行的線程已處理過讀入的某些數據,該線程不會再回退數據(大多如此)。

4.0.0.9 如何實現對象克隆?克隆有哪些方式?深克隆和淺克隆有何區別?深克隆和淺克隆分別說的是什麼意思?

  • 如何實現對象克隆,有兩種方式:
    • 1.實現Cloneable接口並重寫Object類中的clone()方法;
    • 2.實現Serializable接口,經過對象的序列化和反序列化實現克隆,能夠實現真正的深度克隆,代碼以下。
    • 注意:若是不知足上面兩個條件之一,那麼就不能進行克隆的!
  • 克隆有哪些方式?
    • 克隆(複製)在Java中是一種常見的操做,目的是快速獲取一個對象副本。克隆分爲深克隆和淺克隆。
    • 淺克隆:建立一個新對象,新對象的屬性和原來對象徹底相同,對於非基本類型屬性,仍指向原有屬性所指向的對象的內存地址。
    • 深克隆:建立一個新對象,屬性中引用的其餘對象也會被克隆,再也不指向原有對象地址。
  • 深克隆和淺克隆有何區別?技術博客大總結
    • 深淺克隆都會在堆中新分配一塊區域,區別在於對象屬性引用的對象是否須要進行克隆(遞歸性的)。
  • Java的clone()方法
    • 1.clone方法將對象複製了一份並返回給調用者。通常而言,clone()方法知足
    • 2.對任何的對象x,都有x.clone() !=x 由於克隆對象與原對象不是同一個對象
    • 3.對任何的對象x,都有x.clone().getClass()= =x.getClass()//克隆對象與原對象的類型同樣
    • 4.若是對象x的equals()方法定義恰當,那麼x.clone().equals(x)應該成立
  • 注意問題:
    • 基於序列化和反序列化實現的克隆不只僅是深度克隆,更重要的是經過泛型限定,能夠檢查出要克隆的對象是否支持序列化,這項檢查是編譯器完成的,不是在運行時拋出異常,這種是方案明顯優於使用Object類的clone方法克隆對象。
  • 代碼以下所示
    • 若是對象實現Cloneable並重寫clone方法不進行任何操做時,調用clone是進行的淺克隆。而使用對象流將對象寫入流而後再讀出是進行的深克隆。
    public class MyUtil {  
      
        private MyUtil() {  
            throw new AssertionError();  
        }  
      
        public static <T> T clone(T obj) throws Exception {  
            ByteArrayOutputStream bout = new ByteArrayOutputStream();  
            ObjectOutputStream oos = new ObjectOutputStream(bout);  
            oos.writeObject(obj);  
      
            ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());  
            ObjectInputStream ois = new ObjectInputStream(bin);  
            return (T) ois.readObject();  
              
            // 說明:調用ByteArrayInputStream或ByteArrayOutputStream對象的close方法沒有任何意義  
            // 這兩個基於內存的流只要垃圾回收器清理對象就可以釋放資源  
        }  
    } 
    
    
    class CloneTest {  
        public static void main(String[] args) {  
            try {  
                Person p1 = new Person("Hao LUO", 33, new Car("Benz", 300));  
                p1.clone();//淺克隆
                Person p2 = MyUtil.clone(p1);   // 深度克隆  
                p2.getCar().setBrand("BYD");  
                // 修改克隆的Person對象p2關聯的汽車對象的品牌屬性  
                // 原來的Person對象p1關聯的汽車不會受到任何影響  
                // 由於在克隆Person對象時其關聯的汽車對象也被克隆了  
                System.out.println(p1);  
            } catch (Exception e) {  
                e.printStackTrace();  
            }  
        }  
    }
    複製代碼

4.0.1.0 淺拷貝會建立新的對象嗎?對於基本類型拷貝的是什麼?怎麼樣實現淺拷貝,淺拷貝先後對象地址值會同樣嗎?

  • 淺拷貝會建立新的對象嗎?php

    • 會建立新對象。淺拷貝是按位拷貝對象,它會建立一個新對象,這個對象有着原始對象屬性值的一份精確拷貝。
  • 對於基本類型拷貝的是什麼?java

    • 若是屬性是基本類型,拷貝的就是基本類型的值;若是屬性是內存地址(引用類型),拷貝的就是內存地址 ,所以若是其中一個對象改變了這個地址,就會影響到另外一個對象。
    • image
    • 在上圖中,SourceObject有一個int類型的屬性 "field1"和一個引用類型屬性"refObj"(引用ContainedObject類型的對象)。當對SourceObject作淺拷貝時,建立了CopiedObject,它有一個包含"field1"拷貝值的屬性"field2"以及仍指向refObj自己的引用。因爲"field1"是基本類型,因此只是將它的值拷貝給"field2",可是因爲"refObj"是一個引用類型, 因此CopiedObject指向"refObj"相同的地址。所以對SourceObject中的"refObj"所作的任何改變都會影響到CopiedObject。
  • 怎麼樣實現淺拷貝,淺拷貝先後對象地址值會同樣嗎?git

    • 下面來看一看實現淺拷貝的一個例子
    public class Subject {
     
       private String name; 
       public Subject(String s) { 
          name = s; 
       } 
    
       public String getName() { 
          return name; 
       } 
    
       public void setName(String s) { 
          name = s; 
       } 
    }
    複製代碼
    public class Student implements Cloneable { 
     
       // 對象引用 
       private Subject subj; 
       private String name; 
     
       public Student(String s, String sub) { 
          name = s; 
          subj = new Subject(sub); 
       } 
     
       public Subject getSubj() { 
          return subj; 
       } 
     
       public String getName() { 
          return name; 
       } 
     
       public void setName(String s) { 
          name = s; 
       } 
     
       /** 
        *  重寫clone()方法 
        * @return 
        */ 
       public Object clone() { 
          //淺拷貝 
          try { 
             // 直接調用父類的clone()方法
             return super.clone(); 
          } catch (CloneNotSupportedException e) { 
             return null; 
          } 
       } 
    }
    複製代碼
    private void test1(){
        // 原始對象
        Student stud = new Student("楊充", "瀟湘劍雨");
        System.out.println("原始對象: " + stud.getName() + " - " + stud.getSubj().getName());
    
        // 拷貝對象
        Student clonedStud = (Student) stud.clone();
        System.out.println("拷貝對象: " + clonedStud.getName() + " - " + clonedStud.getSubj().getName());
    
        // 原始對象和拷貝對象是否同樣:
        System.out.println("原始對象和拷貝對象是否同樣: " + (stud == clonedStud));
        // 原始對象和拷貝對象的name屬性是否同樣
        System.out.println("原始對象和拷貝對象的name屬性是否同樣: " + (stud.getName() == clonedStud.getName()));
        // 原始對象和拷貝對象的subj屬性是否同樣
        System.out.println("原始對象和拷貝對象的subj屬性是否同樣: " + (stud.getSubj() == clonedStud.getSubj()));
    
        stud.setName("小楊逗比");
        stud.getSubj().setName("瀟湘劍雨大俠");
        System.out.println("更新後的原始對象: " + stud.getName() + " - " + stud.getSubj().getName());
        System.out.println("更新原始對象後的克隆對象: " + clonedStud.getName() + " - " + clonedStud.getSubj().getName());
    }
    複製代碼
    • 輸出結果以下:
    2019-03-23 13:50:57.518 24704-24704/com.ycbjie.other I/System.out: 原始對象: 楊充 - 瀟湘劍雨
    2019-03-23 13:50:57.519 24704-24704/com.ycbjie.other I/System.out: 拷貝對象: 楊充 - 瀟湘劍雨
    2019-03-23 13:50:57.519 24704-24704/com.ycbjie.other I/System.out: 原始對象和拷貝對象是否同樣: false
    2019-03-23 13:50:57.519 24704-24704/com.ycbjie.other I/System.out: 原始對象和拷貝對象的name屬性是否同樣: true
    2019-03-23 13:50:57.519 24704-24704/com.ycbjie.other I/System.out: 原始對象和拷貝對象的subj屬性是否同樣: true
    2019-03-23 13:50:57.519 24704-24704/com.ycbjie.other I/System.out: 更新後的原始對象: 小楊逗比 - 瀟湘劍雨大俠
    2019-03-23 13:50:57.519 24704-24704/com.ycbjie.other I/System.out: 更新原始對象後的克隆對象: 楊充 - 瀟湘劍雨大俠
    複製代碼
    • 能夠得出的結論
      • 在這個例子中,讓要拷貝的類Student實現了Clonable接口並重寫Object類的clone()方法,而後在方法內部調用super.clone()方法。從輸出結果中咱們能夠看到,對原始對象stud的"name"屬性所作的改變並無影響到拷貝對象clonedStud,可是對引用對象subj的"name"屬性所作的改變影響到了拷貝對象clonedStud。
    • 能夠經過案例得出:原始對象和拷貝對象是不同的,也就是地址值發生了變化,可是裏面的屬性仍是同樣的。

4.0.1.1 對字節流進行大量的從硬盤讀取,要用那個流,爲何?有什麼須要注意的問題?

  • 對字節流進行大量的從硬盤讀取,要用那個流,爲何?
    • 由於明確說了是對字節流的讀取,因此確定是inputstream或者他的子類,又由於要大量讀取,確定要考慮到高效的問題,天然想到緩衝流。技術博客大總結
    • 用BufferedInputStream,緣由:BufferedInputStream是InputStream的緩衝流,使用它能夠防止每次讀取數據時進行實際的寫操做,表明着使用緩衝區。不帶緩衝的操做,每讀一個字節就要寫入一個字節,因爲涉及磁盤的IO操做相比內存的操做要慢不少,因此不帶緩衝的流效率很低。帶緩衝的流,能夠一次讀不少字節,但不向磁盤中寫入,只是先放到內存裏。等湊夠了緩衝區大小的時候一次性寫入磁盤,這種方式能夠減小磁盤操做次數,速度就會提升不少!而且也能夠減小對磁盤的損傷。

其餘介紹

01.關於博客彙總連接

02.關於個人博客

相關文章
相關標籤/搜索