1. 程式人生 > >002java面試筆記——【java基礎篇】從團800失敗面試總結的java面試題

002java面試筆記——【java基礎篇】從團800失敗面試總結的java面試題

6、java io流

     1)java io流相關概念

輸出流:

 

輸入流:


因此輸入和輸出都是從程式的角度來說的。

位元組流:一次讀入或讀出是8位二進位制

字元流:一次讀入或讀出是16位二進位制

位元組流和字元流的原理是相同的,只不過處理的單位不同而已。字尾是Stream是位元組流,而後綴是ReaderWriter是字元流。

節點流:直接與資料來源相連,讀入或讀出。


直接使用節點流,讀寫不方便,為了更快的讀寫檔案,才有了處理流。

處理流:與節點流一塊使用,在節點流的基礎上,再套接一層,套接在節點流上的就是處理流。


     2)java io流的分類

按流向分:
輸入流: 程式可以從中讀取資料的流。
輸出流: 程式能向其中寫入資料的流。
按資料傳輸單位分:
位元組流: 以位元組為單位傳輸資料的流
字元流: 以字元為單位傳輸資料的流
按功能分:
節點流: 用於直接操作目標裝置的流
過濾流: 是對一個已存在的流的連結和封裝,通過對資料進行處理為程式提供功能強大、靈活的讀寫功能。

     3)java io類結構圖

     流是一個很形象的概念,當程式需要讀取資料的時候,就會開啟一個通向資料來源的流,這個資料來源可以是檔案,記憶體,或是網路連線。類似的,當程式需要寫入資料的時候,就會開啟一個通向目的地的流。這時候你就可以想象資料好像在這其中“流”動一樣。Java把這些不同來源和目標的資料都統一抽象為資料流,

在Java類庫中,IO部分的內容是很龐大的,因為它涉及的領域很廣泛:標準輸入輸出,檔案的操作,網路上的資料流,字串流,物件流,zip檔案流,java io 類結構如下:


[1]輸入位元組流InputStream:InputStream 是所有的輸入位元組流的父類,它是一個抽象類;ByteArrayInputStream、StringBufferInputStream、FileInputStream 是三種基本的介質流,它們分別從Byte 陣列、StringBuffer、和本地檔案中讀取資料;PipedInputStream 是從與其它執行緒共用的管道中讀取資料;ObjectInputStream 和所有FilterInputStream 的子類都是裝飾流(裝飾器模式的主角)。

[2]輸出位元組流OutputStream:OutputStream 是所有的輸出位元組流的父類,它是一個抽象類。
ByteArrayOutputStream、FileOutputStream 是兩種基本的介質流,它們分別向Byte 陣列、和本地檔案中寫入資料。PipedOutputStream 是向與其它執行緒共用的管道中寫入資料,ObjectOutputStream 和所有FilterOutputStream 的子類都是裝飾流。

[3]位元組流的輸入與輸出的對應


圖中藍色的為主要的對應部分,紅色的部分就是不對應部分。紫色的虛線部分代表這些流一般要搭配使用。從上面的圖中可以看出Java IO 中的位元組流是極其對稱的。

不對稱的類介紹如下:

1>LineNumberInputStream 主要完成從流中讀取資料時,會得到相應的行號,至於什麼時候分行、在哪裡分行是由改類主動確定的,並不是在原始中有這樣一個行號。在輸出部分沒有對應的部分,我們完全可以自己建立一個LineNumberOutputStream,在最初寫入時會有一個基準的行號,以後每次遇到換行時會在下一行新增一個行號,看起來也是可以的。好像更不入流了。
2>PushbackInputStream 的功能是檢視最後一個位元組,不滿意就放入緩衝區。主要用在編譯器的語法、詞法分析部分。輸出部分的BufferedOutputStream 幾乎實現相近的功能。
3>StringBufferInputStream 已經被Deprecated,本身就不應該出現在InputStream 部分,主要因為String 應該屬於字元流的範圍。已經被廢棄了,當然輸出部分也沒有必要需要它了!還允許它存在只是為了保持版本的向下相容而已。
4>SequenceInputStream 可以認為是一個工具類,將兩個或者多個輸入流當成一個輸入流依次讀取。完全可以從IO 包中去除,還完全不影響IO 包的結構,卻讓其更“純潔”――純潔的Decorator 模式。
5>PrintStream 也可以認為是一個輔助工具。主要可以向其他輸出流,或者FileInputStream 寫入資料,本身內部實現還是帶緩衝的。本質上是對其它流的綜合運用的一個工具而已。一樣可以踢出IO 包!System.out 和System.out 就是PrintStream 的例項!

[4]字元輸入流Reader:Reader 是所有的輸入字元流的父類,它是一個抽象類;CharReader、StringReader 是兩種基本的介質流,它們分別將Char 陣列、String中讀取資料;PipedReader 是從與其它執行緒共用的管道中讀取資料;BufferedReader 很明顯就是一個裝飾器,它和其子類負責裝飾其它Reader 物件;FilterReader 是所有自定義具體裝飾流的父類,其子類PushbackReader 對Reader 物件進行裝飾,會增加一個行號;InputStreamReader 是一個連線位元組流和字元流的橋樑,它將位元組流轉變為字元流。FileReader 可以說是一個達到此功能、常用的工具類,在其原始碼中明顯使用了將FileInputStream 轉變為Reader 的方法。我們可以從這個類中得到一定的技巧。Reader 中各個類的用途和使用方法基本和InputStream 中的類使用一致。

[5]字元輸出流Writer:Writer 是所有的輸出字元流的父類,它是一個抽象類;CharArrayWriter、StringWriter 是兩種基本的介質流,它們分別向Char 陣列、String 中寫入資料。PipedWriter 是向與其它執行緒共用的管道中寫入資料,BufferedWriter 是一個裝飾器為Writer 提供緩衝功能;PrintWriter 和PrintStream 極其類似,功能和使用也非常相似;
OutputStreamWriter 是OutputStream 到Writer 轉換的橋樑,它的子類FileWriter 其實就是一個實現此功能的具體類(具體可以研究一SourceCode)。

[6]字元流的輸入與輸出的對應


[8]字元流與位元組流轉換

轉換流的特點:
其是字元流和位元組流之間的橋樑
可對讀取到的位元組資料經過指定編碼轉換成字元
可對讀取到的字元資料經過指定編碼轉換成位元組
何時使用轉換流?
當位元組和字元之間有轉換動作時;
流操作的資料需要編碼或解碼時。
具體的物件體現:
InputStreamReader:位元組到字元的橋樑
OutputStreamWriter:字元到位元組的橋樑
這兩個流物件是字元體系中的成員,它們有轉換作用,本身又是字元流,所以在構造的時候需要傳入位元組流物件進來。

     4)java 管道通訊

     Java提供管道功能,實現管道通訊的類有兩組:PipedInputStream和PipedOutputStream或者是PipedReader和PipedWriter。管道通訊主要用於不同執行緒間的通訊
一個PipedInputStream例項物件必須和一個PipedOutputStream例項物件進行連線而產生一個通訊管道。PipedOutputStream向管道中寫入資料,PipedIntputStream讀取PipedOutputStream向管道中寫入的資料。一個執行緒的PipedInputStream物件能夠從另外一個執行緒的PipedOutputStream物件中讀取資料。

 //Sender類 
package pipeCommu;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
public class Sender extendsThread{
   private PipedOutputStream out=new PipedOutputStream();//傳送者建立PipedOutputStream向外寫資料; 
   public PipedOutputStream getOut(){
       return out;
   }
   public void run(){
       String strInfo="hello,receiver";
       try{
           out.write(strInfo.getBytes());//寫入資料
           out.close();
       }catch(Exception e){
           e.printStackTrace();
       }
   }
}  
 //Reader類,負責接收資料 
package pipeCommu;
import java.io.PipedInputStream; 
public class Reader extends Thread{
   private PipedInputStream in=new PipedInputStream();//傳送者建立PipedOutputStream向外寫資料
   public PipedInputStream getIn(){
       returnin;
   }
   public void run(){
       byte[] buf=new byte[1024];//宣告位元組陣列
       try{
           int len=in.read(buf);//讀取資料,並返回實際讀到的位元組數
           System.out.println("receive from sender:"+newString(buf,0,len));
           in.close();
       }catch(Exception e){
           e.printStackTrace();
       }
   }
}
package pipeCommu; 
import java.io.*;
public class PipedStream {
    public static void main(String[] args) throws Exception{
       Sender send=new Sender();
       Reader read=new Reader();
       PipedOutputStream out=send.getOut();
       PipedInputStream in=read.getIn();
       out.connect(in);//或者也可以用in.connect(out);
       send.start();
       read.start();
   }  
}  
package pipeCommu; 
import java.io.*;
public class PipedCommu {
    public static void main(String[] args) {
        // TODOAuto-generatedmethodstub
        try{
             PipedReader reader=new PipedReader();
             PipedWriter writer=new PipedWriter(reader);
             Thread a=new Send(writer);
             Thread b=new Read(reader);
             a.start();
             Thread.sleep(1000);
             b.start();
        }catch(IOException e){
             e.printStackTrace();
             
        }catch(InterruptedException e1){
             e1.printStackTrace();           
        }
    }
}
    class Send extends Thread{
        PipedWriter writer;
        public Send(PipedWriter writer){
             this.writer=writer;
        }
        public void run(){
             try{
                  writer.write("this is a.hello world".toCharArray());
                  writer.close();
             }catch(IOException e){
                  e.printStackTrace();
             }            
        }
    }
    class Read extends Thread{
        PipedReader reader;
        public Read(PipedReader reader){
             this.reader=reader;
        }
        public void run(){
             System.out.println("this is B");
             try{
                  char[] buf=new char[1000];
                  reader.read(buf,0,100);
                  System.out.println(new String(buf));
             }catch(Exception e){
                  e.printStackTrace();
             }
        }
    } 

     5)java 物件序列化

     對於一個存在Java虛擬機器中的物件來說,其內部的狀態只是儲存在記憶體中。JVM退出之後,記憶體資源也就被釋放,Java物件的內部狀態也就丟失了。而在很多情況下,物件內部狀態是需要被持久化的,將執行中的物件狀態儲存下來(最直接的方式就是儲存到檔案系統中),在需要的時候可以還原,即使是在Java虛擬機器退出的情況下。 
     物件序列化機制是Java內建的一種物件持久化方式,可以很容易實現在JVM中的活動物件與位元組陣列(流)之間進行轉換,使用得Java物件可以被儲存,可以被網路傳輸,在網路的一端將物件序列化成位元組流,經過網路傳輸到網路的另一端,可以從位元組流重新還原為Java虛擬機器中的執行狀態中的物件。 

     對於任何需要被序列化的物件,都必須要實現介面Serializable,它只是一個標識介面,本身沒有任何成員,只是用來標識說明當前的實現類的物件可以被序列化。

     如果在類中的一些屬性,希望在物件序列化過程中不被序列化,使用關鍵字transient標註修飾就可以。當物件被序列化時,標註為transient的成員屬性將會自動跳過。

注:

      [1].當一個物件被序列化時,只儲存物件的非靜態成員變數,不能儲存任何的成員方法,靜態的成員變數和transient標註的成員變數。 
   [2].如果一個物件的成員變數是一個物件,那麼這個物件的資料成員也會被儲存還原,而且會是遞迴的方式。
   [3].如果一個可序列化的物件包含對某個不可序列化的物件的引用,那麼整個序列化操作將會失敗,並且會丟擲一個NotSerializableException。可以將這個引用標記transient,那麼物件仍然可以序列化。

       java物件序列化示例程式碼:

實體類:

class Student implements Serializable{        
    private String name;  
    private transient int age;  
    private Course course;        
    public void setCourse(Course course){  
        this.course = course;  
    }      
    public Course getCourse(){  
        return course;  
    }        
    public Student(String name, int age){  
        this.name = name;  
        this.age = age;  
    }   
    public String  toString(){  
        return "Student Object name:"+this.name+" age:"+this.age;  
    }  
}  
  
class Course implements Serializable{        
    private static String courseName;  
    private int credit;        
    public Course(String courseName, int credit){  
        this.courseName  = courseName;  
        this.credit = credit;  
    }        
    public String toString(){            
        return "Course Object courseName:"+courseName  
               +" credit:"+credit;  
    }  
}  
將物件寫入檔案序列化
public class TestWriteObject{    
    public static void main(String[] args) {    
        String filePath = "C://obj.txt";  
        ObjectOutputStream objOutput = null;  
        Course c1 = new Course("C language", 3);  
        Course c2 = new Course("OS", 4);           
        Student s1 = new Student("king", 25);  
        s1.setCourse(c1);            
        Student s2 = new Student("jason", 23);  
        s2.setCourse(c2);   
        try {  
            objOutput = new ObjectOutputStream(new FileOutputStream(filePath));  
            objOutput.writeObject(s1);  
            objOutput.writeObject(s2);  
            objOutput.writeInt(123);  
        } catch (FileNotFoundException e) {  
            e.printStackTrace();  
        } catch (IOException e) {  
            e.printStackTrace();  
        }finally{  
            try {  
                objOutput.close();  
            } catch (IOException e) {  
                e.printStackTrace();  
            }  
        }            
        System.out.println("Info:物件被寫入"+filePath);  
    } 
從檔案中讀取物件,反序列化
public class TestReadObject  {  
      
    public static void main(String[] args) {  
          
        String filePath = "C://obj.txt";  
        ObjectInputStream objInput = null;  
        Student s1 = null,s2 = null;  
        int intVal = 0;  
      
        try {  
            objInput = new ObjectInputStream(new FileInputStream(filePath));  
            s1 = (Student)objInput.readObject();  
            s2 = (Student)objInput.readObject();  
            intVal = objInput.readInt();  
              
        } catch (FileNotFoundException e) {  
            e.printStackTrace();  
        } catch (IOException e) {  
            e.printStackTrace();  
        }catch(ClassNotFoundException cnfe){  
            cnfe.printStackTrace();  
        }finally{  
              
            try {  
                objInput.close();  
            } catch (IOException e) {  
                e.printStackTrace();  
            }  
        }            
        System.out.println("Info:檔案"+filePath+"中讀取物件");  
        System.out.println(s1);  
        System.out.println(s1.getCourse());  
        System.out.println(s2);  
        System.out.println(s2.getCourse());  
        System.out.println(intVal);  
    }  
}  

     7、java  nio

    1)java nio簡介

     nio 是 java New IO 的簡稱,在 jdk1.4 裡提供的新 api 。 Sun 官方標榜的特性如有:為所有的原始型別提供 (Buffer) 快取支援;字符集編碼解碼解決方案;Channel :一個新的原始 I/O 抽象;支援鎖和記憶體對映檔案的檔案訪問介面;提供多路 (non-bloking) 非阻塞式的高伸縮性網路 I/O 。

    2)java nio非阻塞原理

     一個常見的網路 IO 通訊流程如下 :


     從該網路通訊過程來理解一下何為阻塞 :在以上過程中若連線還沒到來,那麼 accept 會阻塞 , 程式執行到這裡不得不掛起, CPU 轉而執行其他執行緒。在以上過程中若資料還沒準備好, read 會一樣也會阻塞。阻塞式網路 IO 的特點:多執行緒處理多個連線。每個執行緒擁有自己的棧空間並且佔用一些 CPU 時間。每個執行緒遇到外部未準備好的時候,都會阻塞掉。阻塞的結果就是會帶來大量的程序上下文切換。且大部分程序上下文切換可能是無意義的。比如假設一個執行緒監聽一個埠,一天只會有幾次請求進來,但是該 cpu 不得不為該執行緒不斷做上下文切換嘗試,大部分的切換以阻塞告終。

     何為非阻塞?
     下面有個隱喻:
      一輛從 A 開往 B 的公共汽車上,路上有很多點可能會有人下車。司機不知道哪些點會有哪些人會下車,對於需要下車的人,如何處理更好?
      1. 司機過程中定時詢問每個乘客是否到達目的地,若有人說到了,那麼司機停車,乘客下車。 ( 類似阻塞式 )
      2. 每個人告訴售票員自己的目的地,然後睡覺,司機只和售票員互動,到了某個點由售票員通知乘客下車。 ( 類似非阻塞 )
     很顯然,每個人要到達某個目的地可以認為是一個執行緒,司機可以認為是 CPU 。在阻塞式裡面,每個執行緒需要不斷的輪詢,上下文切換,以達到找到目的地的結果。而在非阻塞方式裡,每個乘客 ( 執行緒 ) 都在睡覺 ( 休眠 ) ,只在真正外部環境準備好了才喚醒,這樣的喚醒肯定不會阻塞。
     非阻塞的原理
     把整個過程切換成小的任務,通過任務間協作完成。由一個專門的執行緒來處理所有的 IO 事件,並負責分發。事件驅動機制:事件到的時候觸發,而不是同步的去監視事件。執行緒通訊:執行緒之間通過 wait,notify 等方式通訊。保證每次上下文切換都是有意義的。減少無謂的程序切換

     以下是非同步 IO 的結構:


    Reactor 就是上面隱喻的售票員角色。每個執行緒的處理流程大概都是讀取資料、解碼、計算處理、編碼、傳送響應。
非同步 IO 核心 API:

    Selector:非同步 IO 的核心類,它能檢測一個或多個通道 (channel) 上的事件,並將事件分發出去。使用一個 select 執行緒就能監聽多個通道上的事件,並基於事件驅動觸發相應的響應。而不需要為每個 channel 去分配一個執行緒。
    SelectionKey:包含了事件的狀態資訊和時間對應的通道的繫結。

    3)Buffer結構、主要方法

     Buffer內部結構如圖:


    一個 buffer 主要由 position,limit,capacity 三個變數來控制讀寫的過程。此三個變數的含義見如下表格:

引數

寫模式    

讀模式

position

當前寫入的單位資料數量。

當前讀取的單位資料位置。

limit

代表最多能寫多少單位資料和容量是一樣的。

代表最多能讀多少單位資料,和之前寫入的單位資料量一致。

capacity

buffer 容量

buffer 容量

    Buffer 常見方法:
    flip(): 寫模式轉換成讀模式
    rewind() :將 position 重置為 0 ,一般用於重複讀。
    clear() :清空 buffer ,準備再次被寫入 (position 變成 0 , limit 變成 capacity) 。
    compact(): 將未讀取的資料拷貝到 buffer 的頭部位。
    mark() 、 reset():mark 可以標記一個位置, reset 可以重置到該位置。
    Buffer 常見型別: ByteBuffer 、 MappedByteBuffer 、 CharBuffer 、 DoubleBuffer 、 FloatBuffer 、 IntBuffer 、 LongBuffer 、ShortBuffer 。
    channel 常見型別 :FileChannel 、 DatagramChannel(UDP) 、 SocketChannel(TCP) 、 ServerSocketChannel(TCP)

     4)Buffer、Chanel

     Channel 和 buffer 是 NIO 是兩個最基本的資料型別抽象。Buffer:是一塊連續的記憶體塊,是 NIO 資料讀或寫的中轉地;Channel:資料的源頭或者資料的目的地,用於向 buffer 提供資料或者讀取 buffer 資料,buffer 物件的唯一介面,支援非同步 I/O 。chanel與Buffer關係如下:


    Buffer、Chanel使用例子:CopyFile.java

package sample;  
  
import java.io.FileInputStream;  
import java.io.FileOutputStream;  
import java.nio.ByteBuffer;  
import java.nio.channels.FileChannel;  
  
public class CopyFile {  
    public static void main(String[] args) throws Exception {  
        String infile = "C:\\copy.sql";  
        String outfile = "C:\\copy.txt";  
        // 獲取原始檔和目標檔案的輸入輸出流  
        FileInputStream fin = new FileInputStream(infile);  
        FileOutputStream fout = new FileOutputStream(outfile);  
        // 獲取輸入輸出通道  
        FileChannel fcin = fin.getChannel();  
        FileChannel fcout = fout.getChannel();  
        // 建立緩衝區  
        ByteBuffer buffer = ByteBuffer.allocate(1024);  
        while (true) {  
            // clear方法重設緩衝區,使它可以接受讀入的資料  
            buffer.clear();  
            // 從輸入通道中將資料讀到緩衝區  
            int r = fcin.read(buffer);  
            // read方法返回讀取的位元組數,可能為零,如果該通道已到達流的末尾,則返回-1  
            if (r == -1) {  
                break;  
            }  
            // flip方法讓緩衝區可以將新讀入的資料寫入另一個通道  
            buffer.flip();  
            // 從輸出通道中將資料寫入緩衝區  
            fcout.write(buffer);  
        }  
    }  
}  
     5)nio.charset

     字元編碼解碼 : 位元組碼本身只是一些數字,放到正確的上下文中被正確被解析。向 ByteBuffer 中存放資料時需要考慮字符集的編碼方式,讀取展示 ByteBuffer 資料時涉及對字符集解碼。Java.nio.charset 提供了編碼解碼一套解決方案。
     以我們最常見的 http 請求為例,在請求的時候必須對請求進行正確的編碼。在得到響應時必須對響應進行正確的解碼。
     以下程式碼向 baidu 發一次請求,並獲取結果進行顯示。例子演示到了 charset 的使用。

package nio.readpage;  
  
import java.nio.ByteBuffer;  
import java.nio.channels.SocketChannel;  
import java.nio.charset.Charset;  
import java.net.InetSocketAddress;  
import java.io.IOException;  
public class BaiduReader {  
    private Charset charset = Charset.forName("GBK");// 建立GBK字符集  
    private SocketChannel channel;  
    public void readHTMLContent() {  
        try {  
            InetSocketAddress socketAddress = new InetSocketAddress(  
"www.baidu.com", 80);  
//step1:開啟連線  
            channel = SocketChannel.open(socketAddress);  
        //step2:傳送請求,使用GBK編碼  
            channel.write(charset.encode("GET " + "/ HTTP/1.1" + "\r\n\r\n"));  
            //step3:讀取資料  
            ByteBuffer buffer = ByteBuffer.allocate(1024);// 建立1024位元組的緩衝  
            while (channel.read(buffer) != -1) {  
                buffer.flip();// flip方法在讀緩衝區位元組操作之前呼叫。  
                System.out.println(charset.decode(buffer));  
                // 使用Charset.decode方法將位元組轉換為字串  
                buffer.clear();// 清空緩衝  
            }  
        } catch (IOException e) {  
            System.err.println(e.toString());  
        } finally {  
            if (channel != null) {  
                try {  
                    channel.close();  
                } catch (IOException e) {  
                }  
            }  
        }  
    }  
    public static void main(String[] args) {  
        new BaiduReader().readHTMLContent();  
    }  
}