- Java程序員面試筆試寶典(第2版)
- 何昊等編著
- 3025字
- 2022-06-17 16:00:53
2.1 輸入輸出流
從外部設備流向中央處理器的數(shù)據(jù)流被稱為“輸入流”,反之被稱為“輸出流”。由此可見,只要涉及文件的讀寫或者網(wǎng)絡數(shù)據(jù)的收發(fā),都會涉及輸入、輸出流。
2.1.1 Java IO流的實現(xiàn)機制
在Java語言中,輸入和輸出都被稱為抽象的流,流可以被看作一組有序的字節(jié)集合,即數(shù)據(jù)在兩個設備之間的傳輸。
流的本質(zhì)是數(shù)據(jù)傳輸,根據(jù)處理數(shù)據(jù)類型的不同,流可以分為兩大類:字節(jié)流和字符流。其中字節(jié)流以字節(jié)(8bit)為單位,讀到一個字節(jié)就返回一個字節(jié),包含兩個抽象類:InputStream(輸入流)和OutputStream(輸出流)。而字符流使用了字節(jié)流讀到一個或多個字節(jié)(中文對應的字節(jié)數(shù)是兩個,在UTF-8碼表中是3個字節(jié))時,先去查指定的編碼表,將查到的字符返回,它包含兩個抽象類:Reader(輸入流)和Writer(輸出流)。其中字節(jié)流和字符流最主要的區(qū)別為:字節(jié)流在處理輸入輸出的時候不會用到緩存,而字符流用到了緩存。每個抽象類都有很多具體的實現(xiàn)類,在這里就不詳細介紹了。圖2-1主要介紹Java中IO的設計理念。Java IO類在設計的時候采用了Decorator(裝飾者)設計模式,以InputStream為例,介紹Decorator設計模式在IO類中的使用如下。

圖2-1 IO設計類圖
其中ByteArrayInputStream、StringBufferInputStream、FileInputStream和PipedInputStream是Java提供的最基本的對流進行處理的類,F(xiàn)ilterInputStream為一個包裝類的基類,可以對基本的IO類進行包裝,通過調(diào)用這些類提供的基本的流操作方法來實現(xiàn)更復雜的流操作。
使用這種設計模式的好處是,可以在運行時動態(tài)地給對象添加一些額外的職責,與使用繼承的設計方法相比,該方法具有很好的靈活性。
假如現(xiàn)在要設計一個輸入流的類,該類的作用為在讀文件的時候把文件中的大寫字母轉(zhuǎn)換成小寫字母,把小寫字母轉(zhuǎn)換為大寫字母。在設計的時候,可以通過繼承抽象裝飾者類(FilterInputStream)來實現(xiàn)一個裝飾類,通過調(diào)用InputStream類或其子類提供的一些方法再加上邏輯判斷代碼從而可以很簡單地實現(xiàn)這個功能,示例代碼如下:

當文件test.txt中的內(nèi)容為aaaBBBcccDDD123時,程序輸出為:

Java10中給InputStream新增加了一個方法transferTo:用來把數(shù)據(jù)從InputStream中直接傳輸?shù)絆utputStream,示例代碼如下:

2.1.2 管理文件和目錄的類
對文件或目錄進行管理與操作在編程中有著非常重要的作用,Java提供了一個非常重要的類(File)來管理文件和文件夾,通過File類不僅能夠查看文件或目錄的屬性,而且還可以實現(xiàn)對文件或目錄的創(chuàng)建、刪除與重命名等操作。下面主要介紹File類中常用的幾個方法。見表2-1。
表2-1 File類常用的方法

常見筆試題:
如何列出某個目錄下的所有目錄和文件?
假設目錄“C:\\testDir1”下有兩個文件夾(dir1和dir2)和一個文件file1.txt。實現(xiàn)代碼如下:


程序運行結果為:

2.1.3 Java Socket
網(wǎng)絡上的兩個程序通過一個雙向的通信連接實現(xiàn)數(shù)據(jù)的交換,這個雙向鏈路的一端稱為一個Socket。Socket也稱為套接字,可以用來實現(xiàn)不同虛擬機或不同計算機之間的通信。在Java語言中,Socket可以分為兩種類型:面向連接的Socket(TCP,Transmission Control Protocol,傳輸控制協(xié)議)通信協(xié)議和面向無連接的Socket(UDP,User Datagram Protocol,用戶數(shù)據(jù)報協(xié)議)通信協(xié)議。任何一個Socket都是由IP地址和端口號唯一確定的。如圖2-2所示。

圖2-2 Socket原理圖
基于TCP協(xié)議的通信過程如下:首先,Server端Listen(監(jiān)聽)指定的某個端口(建議使用大于1024的端口)是否有連接請求,然后Client端向Server端發(fā)出Connect(連接)請求,緊接著Server端向Client端發(fā)回Accept(接收)消息。一個連接就建立起來了,會話隨即產(chǎn)生。Server端和Client端都可以通過Send、Write等方法與對方通信。
Socket的生命周期可以分為三個階段:打開Socket、使用Socket收發(fā)數(shù)據(jù)和關閉Socket。在Java語言中,可以使用ServerSocket作為服務端,Socket作為客戶端來實現(xiàn)網(wǎng)絡通信。
2.1.4 Java序列化
Java提供了兩種對象持久化的方式,分別為序列化和外部序列化。
(1)序列化(Serialization)
在分布式環(huán)境下,當進行遠程通信時,無論是何種類型的數(shù)據(jù),都會以二進制序列的形式在網(wǎng)絡上傳送。序列化是一種將對象以一連串字節(jié)描述的過程,用于解決在對對象流進行讀寫操作時所引發(fā)的問題。序列化可以將對象的狀態(tài)寫在流里進行網(wǎng)絡傳輸,或者保存到文件、數(shù)據(jù)庫等系統(tǒng)里,并在需要的時候把該流讀取出來重新構造一個相同的對象。
如何實現(xiàn)序列化呢?其實,所有要實現(xiàn)序列化的類都必須實現(xiàn)Serializable接口,Serializable接口位于java.lang包中,它里面沒有包含任何方法。實現(xiàn)序列化的方法為:使用一個輸出流(例如FileOutputStream)來構造一個ObjectOutputStream(對象流)對象,緊接著,使用該對象的writeObject(Object obj)方法就可以將obj對象寫出(即保存其狀態(tài)),要恢復的時候可以使用其對應的輸入流。
序列化有如下幾個特點:
1)如果一個類能被序列化,那么它的子類也能夠被序列化。
2)由于static(靜態(tài))代表類的成員,transient(Java語言關鍵字,如果用transient聲明一個實例變量,當對象存儲時,它的值不需要維持)代表對象的臨時數(shù)據(jù),因此被聲明為這兩種類型的數(shù)據(jù)成員是不能夠被序列化的。
3)Java提供了多個對象序列化的接口:ObjectOutput、ObjectInput、ObjectOutputStream、ObjectInputStream。
下面給出一個序列化的具體實例:


上面程序的運行結果為:

由于序列化的使用會影響系統(tǒng)的性能,因此如果不是必須要使用序列化,盡可能不要使用序列化。那么在什么情況下會使用該序列化呢?
1)需要通過網(wǎng)絡來發(fā)送對象,或?qū)ο蟮臓顟B(tài)需要被持久化到數(shù)據(jù)庫或文件中。
2)序列化能實現(xiàn)深拷貝,即可以拷貝引用的對象。
與序列化相對的是反序列化,它將流轉(zhuǎn)換為對象。在序列化與反序列化的過程中,serialVersionUID起著非常重要的作用,每個類都有一個特定的serialVersionUID,在反序列化的過程中通過serialVersionUID來判定類的兼容性。如果待序列化的對象與目標對象的serialVersionUID不同,那么在反序列化的時候就會拋出InvalidClassException異常。作為一個好的編程習慣,最好在被序列化的類中顯式地聲明serialVersionUID(該字段必須定義為static final)。自定義serialVersionUID主要有如下三個優(yōu)點。
1)提高程序的運行效率。如果在類中未顯式聲明serialVersionUID,那么在序列化的時候會通過計算得到一個serialVersionUID值。通過顯式聲明serialVersionUID的方式省去了計算的過程,因此提高了程序的運行效率。
2)提高程序在不同平臺上的兼容性。由于各個平臺的編譯器在計算serialVersionUID的時候完全有可能會采用不同的計算方式,這就會導致在一個平臺上序列化的對象在另外一個平臺上將無法實現(xiàn)反序列化的操作。通過顯式聲明serialVersionUID的方法完全可以避免該問題的發(fā)生。
3)增強程序各個版本的可兼容性。在默認情況下,每個類都有唯一的serialVersionUID,因此當后期對類進行修改的時候(例如加入新的屬性),類的serialVersionUID值將會發(fā)生變化,這將會導致類在修改前對象序列化的文件在修改后無法進行反序列化操作。同樣通過顯式聲明serialVersionUID也會解決這個問題。
(2)外部序列化
此外,Java語言還提供了另外一種方式來實現(xiàn)對象持久化,即外部序列化。其接口如下:

外部序列化與序列化主要的區(qū)別在于序列化是內(nèi)置的API,只需要實現(xiàn)Serializable接口,開發(fā)人員不需要編寫任何代碼就可以實現(xiàn)對象的序列化,而使用外部序列化時,Externalizable接口中的讀寫方法必須由開發(fā)人員來實現(xiàn)。因此與實現(xiàn)Serializable接口的方法相比,使用Externalizable編寫程序的難度更大,但是由于把控制權交給了開發(fā)人員,在編程的時候有更多的靈活性,對需要持久化的那些屬性進行控制,可能會提高性能。
引申:在用接口Serializable實現(xiàn)序列化的時候,這個類中的所有屬性都會被序列化。怎樣才能實現(xiàn)只序列化部分屬性呢?
一種方法為實現(xiàn)Externalizable接口,開發(fā)人員可以根據(jù)實際需求來實現(xiàn)readExternal與writeExternal方法,從而控制序列化與反序列化所使用的屬性,這種方法的缺點為增加了編程的難度。
另外一種方法為使用關鍵字transient來控制序列化的屬性。被transient修飾的屬性是臨時的,不會被序列化。因此,可以通過把不需要被序列化的屬性用transient來修飾來實現(xiàn)。
常見筆試題:

創(chuàng)建一個如下方式的DataObject:DataObject object=new DataObject();object.setWord(“123”);object.setI(2);將此對象序列化文件,并在另一個JVM中讀取文件,進行反序列化,請問此時讀出的DataObject對象中的word和i的值分別是( )
A.“”,0
B.“”,2
C.“123”,2
D.“123”,0
答案:D。Java在序列化的時候不會實例化static變量,因此上述代碼只實例化了word,而沒有實例化i。在反序列化的時候只能讀取到word的值,i為默認值。
- JSP網(wǎng)絡編程(學習筆記)
- Java入門很輕松(微課超值版)
- HTML5 移動Web開發(fā)從入門到精通(微課精編版)
- Python自動化運維快速入門
- Java高手真經(jīng)(高級編程卷):Java Web高級開發(fā)技術
- Java開發(fā)入行真功夫
- Django Design Patterns and Best Practices
- MariaDB High Performance
- KnockoutJS Starter
- Java Web從入門到精通(第3版)
- Vue.js 3應用開發(fā)與核心源碼解析
- C# 7.0本質(zhì)論
- Beginning PHP
- C++面向?qū)ο蟪绦蛟O計教程
- Python深度學習入門:從零構建CNN和RNN