1. 程式人生 > >【面試】IO相關-這一篇全瞭解

【面試】IO相關-這一篇全瞭解

什麼是位元?什麼是位元組?什麼是字元?它們長度是多少?各有什麼區別?

解:

Bit最小的二進位制單位 ,是計算機的操作部分。取值0或者1

Byte是計算機操作資料的最小單位由8位bit組成 取值(-128-127)

Char是使用者的可讀寫的最小單位,在Java裡面由16位bit組成 取值(0-65535)

1B(Byte) = 8bit;1KB = 1024B(Byte);1MB = 1024KB(Byte);

- ASCII編碼:一個英文字母(不分大小寫)佔一個位元組的空間,一箇中文漢字佔兩個位元組的空間。

- GB2312/GBK編碼:一個漢字字元儲存需要2個位元組。

- UTF-8編碼中:一個英文字母字元儲存需要1個位元組,一個漢字字元儲存需要3到4個位元組

什麼是流?Java IO中包含哪幾種流,之間的關係是怎樣的?

解:

流是一串連續不斷的資料的集合,就象水管裡的水流,在水管的一端一點一點地供水,而在水管的另一端看到的是一股連續不斷的水流。資料寫入程式可以是一段、一段地向資料流管道中寫入資料,這些資料段會按先後順序形成一個長的資料流。對資料讀取程式來說,看不到流在寫入時的分段情況,每次可以讀取其中的任意長度的資料,但只能先讀取前面的資料後,再讀取後面的資料。

Java IO中包含位元組流、字元流。按照流向還分為輸入流,輸出流

位元組流和字元流的區別。

解:

位元組流:操作byte型別資料,主要操作類是OutputStream、InputStream的子類;不用緩衝區,直接對檔案本身操作。

字元流:操作字元型別資料,主要操作類是Reader、Writer的子類;使用緩衝區緩衝字元,不關閉流就不會輸出任何內容。

什麼是輸入流和輸出流,如何區分?

解:

輸入、輸出,有一個參照物,參照物就是儲存資料的介質。如果是把物件讀入到介質中,這就是輸入。從介質中向外讀資料,這就是輸出。

所以,輸入流是把資料寫入儲存介質的。輸出流是從儲存介質中把資料讀取出來。

位元組流和字元流之間如何相互轉換。

解:

整個IO包實際上分為位元組流和字元流,但是除了這兩個流之外,還存在一組位元組流-字元流的轉換類。

OutputStreamWriter:是Writer的子類,將輸出的字元流變為位元組流,即將一個字元流的輸出物件變為位元組流輸出物件。 InputStreamReader:是Reader的子類,將輸入的位元組流變為字元流,即將一個位元組流的輸入物件變為字元流的輸入物件。

什麼是NIO?

解:

在瞭解 NIO 之前,我們首先有必要回憶一下 IO 知識。

1.什麼是IO? 它是指計算機與外部世界或者一個程式與計算機的其餘部分的之間的介面。它對於任何計算機系統都非常關鍵,因而所有 I/O 的主體實際上是內建在作業系統中的。單獨的程式一般是讓系統為它們完成大部分的工作。

在 Java 程式設計中,直到最近一直使用 流 的方式完成 I/O。所有 I/O 都被視為單個的位元組的移動,通過一個稱為 Stream 的物件一次移動一個位元組。流 I/O 用於與外部世界接觸。它也在內部使用,用於將物件轉換為位元組,然後再轉換回物件。

2.什麼是NIO? NIO 與原來的 I/O 有同樣的作用和目的, 他們之間最重要的區別是資料打包和傳輸的方式。原來的 I/O 以流的方式處理資料,而 NIO 以塊的方式處理資料。

面向流 的 I/O 系統一次一個位元組地處理資料。一個輸入流產生一個位元組的資料,一個輸出流消費一個位元組的資料。為流式資料建立過濾器非常容易。連結幾個過濾器,以便每個過濾器只負責單個複雜處理機制的一部分,這樣也是相對簡單的。不利的一面是,面向流的 I/O 通常相當慢。

一個 面向塊 的 I/O 系統以塊的形式處理資料。每一個操作都在一步中產生或者消費一個數據塊。按塊處理資料比按(流式的)位元組處理資料要快得多。但是面向塊的 I/O 缺少一些面向流的 I/O 所具有的優雅性和簡單性。

什麼是AIO?

解:

Java AIO即Async非阻塞,是非同步非阻塞的IO。

https://www.cnblogs.com/94cool/p/5952903.html

什麼是BIO?

解:

Java BIO即Block I/O , 同步並阻塞的IO。

BIO就是傳統的java.io包下面的程式碼實現。

bio: 同步阻塞

nio: 同步非阻塞

aio: 非同步非阻塞

(非阻塞 io 只是換地方阻塞了)

關鍵是理解同步/非同步,阻塞/非阻塞的概念

IO流需不需要關閉,如果關閉的話應該如何關閉。需要注意什麼。

解:

IO流一定要記得關閉,不然可能會記憶體洩露,進而導致記憶體溢位。

在Java程式碼中,IO流的關閉一般要在finally塊中進行,這樣可以保證一定會被執行。還要注意的是,流關閉的程式碼也可能會拋異常,也要對異常進行捕獲。

public class CloseIOStream {
   public static void main(String[] args) {
       InputStream is = null;
       OutputStream os = null;
       try { // ...
       } finally {
           if (is != null)
              try{
                 is.close();
              } catch (IOException e) {
          }  
           if (os != null)
               try{
                   os.close();
               } catch (IOException e){
           }
       }
    }
}

Java 7 中關閉IO的更優雅的方式是什麼?

解:

在Java 7 中新定義了一個AutoCloseable介面,他位於java.lang包下。主要在try-with-resources語句中會被自動呼叫,用於自動釋放資源。

try-with-resources語句是JDK 1.7中一個新的異常處理機制,更方便簡潔的關閉在try-catch語句塊中使用的資源。

BIO、NIO及AIO三者之間的區別和聯絡有哪些?

解: BIO (Blocking I/O):同步阻塞I/O模式,資料的讀取寫入必須阻塞在一個執行緒內等待其完成。這裡假設一個燒開水的場景,有一排水壺在燒開水,BIO的工作模式就是, 叫一個執行緒停留在一個水壺那,直到這個水壺燒開,才去處理下一個水壺。但是實際上執行緒在等待水壺燒開的時間段什麼都沒有做。

NIO (New I/O):同時支援阻塞與非阻塞模式,但這裡我們以其同步非阻塞I/O模式來說明,那麼什麼叫做同步非阻塞?如果還拿燒開水來說,NIO的做法是叫一個執行緒不斷的輪詢每個水壺的狀態,看看是否有水壺的狀態發生了改變,從而進行下一步的操作。

AIO ( Asynchronous I/O):非同步非阻塞I/O模型。非同步非阻塞與同步非阻塞的區別在哪裡?非同步非阻塞無需一個執行緒去輪詢所有IO操作的狀態改變,在相應的狀態改變後,系統會通知對應的執行緒來處理。對應到燒開水中就是,為每個水壺上面裝了一個開關,水燒開之後,水壺會自動通知我水燒開了。

請使用BIO實現檔案的讀取和寫入。

解:

//Initializes The Object
User1 user = new User1();
user.setName("hollis");
user.setAge(23);
System.out.println(user);
//Write Obj to File Object
OutputStream oos = null;
try {
     oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
      oos.writeObject(user);
} catch (IOException e) {
       e.printStackTrace();
} finally {
        IOUtils.closeQuietly(oos);
}
//Read Obj from File 
File file = new File("tempFile");
ObjectInputStream ois = null;
try {
       ois = new ObjectInputStream(new FileInputStream(file));
       User1 newUser = (User1) ois.readObject();
        System.out.println(newUser);
} catch (IOException e) {
        e.printStackTrace();
} catch (ClassNotFoundException e) {
        e.printStackTrace();
} finally {
        IOUtils.closeQuietly(ois);
        try {
             FileUtils.forceDelete(file);
       } catch (IOException e) {
             e.printStackTrace();
      }
}

請使用NIO實現檔案的讀取和寫入。

解:

static void readNIO() {
    String pathname = "C:\\Users\\adew\\Desktop\\jd-gui.cfg";
    FileInputStream fin = null;
    try {
        fin = new FileInputStream(new File(pathname));
        FileChannel channel = fin.getChannel();
        int capacity = 100; // 位元組 ByteBuffer bf = ByteBuffer.allocate(capacity);
        System.out.println("限制是:" + bf.limit() + "容量是:" + bf.capacity() + "位置是:" + bf.position());
        int length = -1;
        while ((length = channel.read(bf)) != -1) {
            /* * 注意,讀取後,將位置置為0,將limit置為容量, 以備下次讀入到位元組緩衝中,從0開始儲存 */
            bf.clear();
            byte[] bytes = bf.array();
            System.out.write(bytes, 0, length);
            System.out.println();
            System.out.println("限制是:" + bf.limit() + "容量是:" + bf.capacity() + "位置是:" + bf.position());
        }
        channel.close();
    } catch(FileNotFoundException e) {
        e.printStackTrace();
    } catch(IOException e) {
        e.printStackTrace();
    } finally {
        if (fin != null) {
            try {
                fin.close();
            } catch(IOException e) {
                e.printStackTrace();
            }
        }
    }
}

static void writeNIO() {
    String filename = "out.txt";
    FileOutputStream fos = null;
    try {
        fos = new FileOutputStream(new File(filename));
        FileChannel channel = fos.getChannel();
        ByteBuffer src = Charset.forName("utf8").encode("你好你好你好你好你好");
        // 位元組緩衝的容量和limit會隨著資料長度變化,不是固定不變的
        System.out.println("初始化容量和limit:" + src.capacity() + "," + src.limit());
        int length = 0;
        while ((length = channel.write(src)) != 0) {
            /* * 注意,這裡不需要clear,將緩衝中的資料寫入到通道中後 第二次接著上一次的順序往下讀 */
            System.out.println("寫入長度:" + length);
        }
    } catch(FileNotFoundException e) {
        e.printStackTrace();
    } catch(IOException e) {
        e.printStackTrace();
    } finally {
        if (fos != null) {
            try {
                fos.close();
            } catch(IOException e) {
                e.printStackTrace();
            }
        }
    }
}

請使用AIO實現檔案的讀取和寫入。

解:

public class ReadFromFile {
    public static void main(String[] args) throws Exception {
        Path file = Paths.get("/usr/a.txt");
        AsynchronousFileChannel channel = AsynchronousFileChannel.open(file);
        ByteBuffer buffer = ByteBuffer.allocate(100_000);
        Future < Integer > result = channel.read(buffer, 0);
        while (!result.isDone()) {
            ProfitCalculator.calculateTax();
        }
        Integer bytesRead = result.get();
        System.out.println("Bytes read [" + bytesRead + "]");
    }
}
class ProfitCalculator {
    public ProfitCalculator() {}
    public static void calculateTax() {}
}

public class WriteToFile {
    public static void main(String[] args) throws Exception {
        AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(Paths.get("/asynchronous.txt"), StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE);
        CompletionHandler < Integer,
        Object > handler = new CompletionHandler < Integer,
        Object > () {@Override public void completed(Integer result, Object attachment) {
                System.out.println("Attachment: " + attachment + " " + result + " bytes written");
                System.out.println("CompletionHandler Thread ID: " + Thread.currentThread().getId());
            }@Override public void failed(Throwable e, Object attachment) {
                System.err.println("Attachment: " + attachment + " failed with:");
                e.printStackTrace();
            }
        };
        System.out.println("Main Thread ID: " + Thread.currentThread().getId());
        fileChannel.write(ByteBuffer.wrap("Sample".getBytes()), 0, "First Write", handler);
        fileChannel.write(ByteBuffer.wrap("Box".getBytes()), 0, "Second Write", handler);
    }
}

請將以下程式碼,改成使用try-with-resources的形式。

//Initializes The Object
User1 user = new User1();
user.setName("hollis");
user.setAge(23);
System.out.println(user);
//Write Obj to File
ObjectOutputStream oos = null;
try {
    oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
    oos.writeObject(user);
} catch(IOException e) {
    e.printStackTrace();
} finally {
    IOUtils.closeQuietly(oos);
}
//Read Obj from File
File file = new File("tempFile");
ObjectInputStream ois = null;
try {
    ois = new ObjectInputStream(new FileInputStream(file));
    User1 newUser = (User1) ois.readObject();
    System.out.println(newUser);
} catch(IOException e) {
    e.printStackTrace();
} catch(ClassNotFoundException e) {
    e.printStackTrace();
} finally {
    IOUtils.closeQuietly(ois);
    try {
        FileUtils.forceDelete(file);
    } catch(IOException e) {
        e.printStackTrace();
    }
}
User user = new User();
            user.setName("hollis");
            user.setAge(23);
            System.out.println(user);
            try(ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile"))) {
                oos.writeObject(user);
            } catch (IOException e) {
                e.printStackTrace();
        }

        //Read Obj from File
        File file = new File("tempFile");
        try(ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file))) {
            User newUser = (User) ois.readObject();
            System.out.println(newUser);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

Java中BIO、NIO、AIO分別適用哪些場景?

解:

BIO方式適用於連線數目比較小且固定的架構,這種方式對伺服器資源要求比較高,併發侷限於應用中,JDK1.4以前的唯一選擇,但程式直觀簡單易理解。

NIO方式適用於連線數目多且連線比較短(輕操作)的架構,比如聊天伺服器,併發侷限於應用中,程式設計比較複雜,JDK1.4開始支援。

AIO方式適用於連線數目多且連線比較長(重操作)的架構,比如相簿伺服器,充分呼叫OS參與併發操作,程式設計比較複雜,JDK7開始支援。

什麼是同步?什麼是非同步?

解:

同步與非同步描述的是被呼叫者的。 A呼叫B。 如果是同步,B在接到A的呼叫後,會立即執行要做的事。A的本次呼叫可以得到結果。

如果是非同步,B在接到A的呼叫後,不保證會立即執行要做的事,但是保證會去做,B在做好了之後會通知A。A的本次呼叫得不到結果,但是B執行完之後會通知A。

什麼是阻塞?什麼是非阻塞?

解:

阻塞與非阻塞描述的是呼叫者的。

A呼叫B。

如果是阻塞,A在發出呼叫後,要一直等待,等著B返回結果。

如果是非阻塞,A在發出呼叫後,不需要等待,可以去做自己的事情。

同步,非同步 和 阻塞,非阻塞之間的區別?

同步,非同步,是描述被呼叫方的。

阻塞,非阻塞,是描述呼叫方的。

同步不一定阻塞,非同步也不一定非阻塞。沒有必然關係。

舉個簡單的例子,老張燒水。

1 老張把水壺放到火上,一直在水壺旁等著水開。(同步阻塞)

2 老張把水壺放到火上,去客廳看電視,時不時去廚房看看水開沒有。(同步非阻塞)

3 老張把響水壺放到火上,一直在水壺旁等著水開。(非同步阻塞)

4 老張把響水壺放到火上,去客廳看電視,水壺響之前不再去看它了,響了再去拿壺。(非同步非阻塞)

1和2的區別是,呼叫方在得到返回之前所做的事情不一行。

1和3的區別是,被呼叫方對於燒水的處理不一樣。

IO模型有哪5種?

解:

- 阻塞式IO模型

- 非阻塞IO模型

- IO複用模型

- 訊號驅動IO模型

- 非同步IO模型

請簡答介紹下阻塞IO模型。

解:

最傳統的一種IO模型,即在讀寫資料過程中會發生阻塞現象。

當用戶執行緒發出IO請求之後,核心會去檢視資料是否就緒,如果沒有就緒就會等待資料就緒,而使用者執行緒就會處於阻塞狀態,使用者執行緒交出CPU。當資料就緒之後,核心會將資料拷貝到使用者執行緒,並返回結果給使用者執行緒,使用者執行緒才解除block狀態。

典型的阻塞IO模型的例子為:

data = socket.read();

如果資料沒有就緒,就會一直阻塞在read方法。

請簡單回答下什麼是非阻塞IO模型

解:

當用戶執行緒發起一個read操作後,並不需要等待,而是馬上就得到了一個結果。如果結果是一個error時,它就知道資料還沒有準備好,於是它可以再次傳送read操作。一旦核心中的資料準備好了,並且又再次收到了使用者執行緒的請求,那麼它馬上就將資料拷貝到了使用者執行緒,然後返回。

所以事實上,在非阻塞IO模型中,使用者執行緒需要不斷地詢問核心資料是否就緒,也就說非阻塞IO不會交出CPU,而會一直佔用CPU。

典型的非阻塞IO模型一般如下:

while(true){
     data = socket.read();
     if(data!= error){
           處理資料
      break;
      }
}

但是對於非阻塞IO就有一個非常嚴重的問題,在while迴圈中需要不斷地去詢問核心資料是否就緒,這樣會導致CPU佔用率非常高,因此一般情況下很少使用while迴圈這種方式來讀取資料。

請簡單回答下什麼是多路複用IO模型?

解:

多路複用IO模型是目前使用得比較多的模型。Java NIO實際上就是多路複用IO。 在多路複用IO模型中,會有一個執行緒不斷去輪詢多個socket的狀態,只有當socket真正有讀寫事件時,才真正呼叫實際的IO讀寫操作。因為在多路複用IO模型中,只需要使用一個執行緒就可以管理多個socket,系統不需要建立新的程序或者執行緒,也不必維護這些執行緒和程序,並且只有在真正有socket讀寫事件進行時,才會使用IO資源,所以它大大減少了資源佔用。

在Java NIO中,是通過selector.select()去查詢每個通道是否有到達事件,如果沒有事件,則一直阻塞在那裡,因此這種方式會導致使用者執行緒的阻塞。

也許有朋友會說,我可以採用 多執行緒+ 阻塞IO 達到類似的效果,但是由於在多執行緒 + 阻塞IO 中,每個socket對應一個執行緒,這樣會造成很大的資源佔用,並且尤其是對於長連線來說,執行緒的資源一直不會釋放,如果後面陸續有很多連線的話,就會造成效能上的瓶頸。

而多路複用IO模式,通過一個執行緒就可以管理多個socket,只有當socket真正有讀寫事件發生才會佔用資源來進行實際的讀寫操作。因此,多路複用IO比較適合連線數比較多的情況。

另外多路複用IO為何比非阻塞IO模型的效率高是因為在非阻塞IO中,不斷地詢問socket狀態時通過使用者執行緒去進行的,而在多路複用IO中,輪詢每個socket狀態是核心在進行的,這個效率要比使用者執行緒要高的多。

不過要注意的是,多路複用IO模型是通過輪詢的方式來檢測是否有事件到達,並且對到達的事件逐一進行響應。因此對於多路複用IO模型來說,一旦事件響應體很大,那麼就會導致後續的事件遲遲得不到處理,並且會影響新的事件輪詢。

請簡答回答一下什麼是訊號驅動IO模型?

解:

在訊號驅動IO模型中,當用戶執行緒發起一個IO請求操作,會給對應的socket註冊一個訊號函式,然後使用者執行緒會繼續執行,當核心資料就緒時會發送一個訊號給使用者執行緒,使用者執行緒接收到訊號之後,便在訊號函式中呼叫IO讀寫操作來進行實際的IO請求操作。

請簡答回答一下什麼是非同步IO模型

解:

非同步IO模型是比較理想的IO模型,在非同步IO模型中,當用戶執行緒發起read操作之後,立刻就可以開始去做其它的事。而另一方面,從核心的角度,當它受到一個asynchronous read之後,它會立刻返回,說明read請求已經成功發起了,因此不會對使用者執行緒產生任何block。然後,核心會等待資料準備完成,然後將資料拷貝到使用者執行緒,當這一切都完成之後,核心會給使用者執行緒傳送一個訊號,告訴它read操作完成了。也就說使用者執行緒完全不需要實際的整個IO操作是如何進行的,只需要先發起一個請求,當接收核心返回的成功訊號時表示IO操作已經完成,可以直接去使用資料了。

也就說在非同步IO模型中,IO操作的兩個階段都不會阻塞使用者執行緒,這兩個階段都是由核心自動完成,然後傳送一個訊號告知使用者執行緒操作已完成。使用者執行緒中不需要再次呼叫IO函式進行具體的讀寫。這點是和訊號驅動模型有所不同的,在訊號驅動模型中,當用戶執行緒接收到訊號表示資料已經就緒,然後需要使用者執行緒呼叫IO函式進行實際的讀寫操作;而在非同步IO模型中,收到訊號表示IO操作已經完成,不需要再在使用者執行緒中呼叫iO函式進行實際的讀寫操作。

注意,非同步IO是需要作業系統的底層支援,在Java 7中,提供了Asynchronous IO。

前面四種IO模型實際上都屬於同步IO,只有最後一種是真正的非同步IO,因為無論是多路複用IO還是訊號驅動模型,IO操作的第2個階段都會引起使用者執行緒阻塞,也就是核心進行資料拷貝的過程都會讓使用者執行緒阻塞。