webpack4.0各個擊破(3)—— Assets篇
675
2022-05-30
劍指Offer——知識點儲備-Java基礎
網址來源:
http://www.nowcoder.com/discuss/5949?type=0&order=0&pos=4&page=2
參考資料:(java方面的一些面試答案)
http://www.nowcoder.com/discuss/6890?type=0&order=0&pos=29&page=1
http://www.nowcoder.com/discuss/7342
一:java基礎
1、java 集合類問題
2、 hashMap相關問題
回答主要是三個方面:(1)hashmap的基本原理 (2)hashmap的put存源碼解讀 (3)hashmap的get取源碼解讀
hashmap是基于hash算法的key-value鍵值對,通過key可以快速的找到value值,解決了數組的增加和刪除以及鏈表的查詢效率低的問題。
public V put(K key, V value) { if (key == null)//如果key為空,調用putForNullKey()處理 return putForNullKey(value); int hash = hash(key);//通過key值獲得hash碼(看hash函數,是通過右移位,這種方式使數據散列均勻) //通過indexFor()獲得對應table中的索引 int i = indexFor(hash, table.length);//源碼采用&的方式 //取出table表中的元素,并循環單鏈表,判斷key是否存在 for (Entry
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Hashmap通過調用put函數,實現鍵值對的存儲。
(1)為了保證key的唯一性,一般選用final修飾的類型,比如基本類型的引用類型、String。
問:如果不用這些類型,別人把key都改了,取出來的值肯定就是錯的啦
(2)獲得hash值int hash =
hash(key);先得到key的hashcode值(因為每一個key的hashcode值都是唯一的),然后通過hash算法(底層是通過移位實現的),hash算法的目的就是讓hashcode值能均勻的填充table表,而不是造成大量的集中沖突。
(3)將hash值與table數組的長度進行與運算,獲得table數組下標int i = indexFor(hash,
table.length);
問題:傳統的方法是通過hashcode與table數組長度相除取余,這個使得table表中的數據分布不均勻,產生大量集中沖突。
(4)通過下標獲得元素存儲的位置,先判斷該位置上有沒有元素(hashmap定義的一個類entity,基本結構包含三個元素key、value和指向下一個entity的next),若不同的hash值算在同一個table下標下,這就產生了沖突。常用的沖突解決算法是:拉鏈法(采用頭插法)、線性探測法。
(5)若不等,調用addEntry()將新創建一個元素添加到table中。創建元素entity時,要判斷table的填充容量是否大于負載因子0.75,若大于就要擴容,容量擴充到兩倍。
(6)擴容的時候,是在內存中開辟新的內存空間,然后把原來的對象放到新的table數組中,這個過程叫做重新散列(rehashing)。但是在多線程的時候,會出現條件競爭。比如兩個線程發現hashmap需要重新調整大小,它們會同時試著調整大小。在調整大小的過程中,存儲在鏈表中的元素的次序會反過來,因為移動到新的bucket位置的時候,HashMap并不會將元素放在鏈表的尾部,而是放在頭部,這是為了避免尾部遍歷(tail
traversing)。如果條件競爭發生了,那么就死循環了。
可以參考這篇文章:http://coolshell.cn/articles/9606.html
(7)為了解決這個問題,采用了線程安全的CocurrentHashMap,它是行級鎖,而hashtable是整體加鎖。
鏈接:
http://www.360doc.com/content/13/0409/14/10384031_277138819.shtml
(1)直接定址法(或直接尋址法)
(2)數字分析法
(3)平方取中法(平方散列法)
求index是一個非常頻繁的操作,而乘法運算比除法來的省事(對CPU而言),所以把除法換成乘法和一個移位操作,公式: Index=(value*value)>>28(右移,是除以2^28. 記住:左移變大,是乘。右移變小,是除)
(4)折疊法
(5)除留余數法(這是最簡單也是最常用的構造hash函數的方法)即對關鍵字取模Index=value%16(取模)
(6)隨機數法
(1)開放定址法(線性探測再散列、二次探測再散列)(線性探測法)
(2)再哈希法(雙散列函數法)
在發生沖突的時候,再次使用另一個散列函數,計算哈希函數地址,直到沖突不再發生。
(3)鏈地址法(拉鏈法)(常用重點)
就是一個數組+鏈表(鏈表采用頭插法)
(4)建立一個公共溢出區。
(1)MD4
(2)MD5
(3)SHA-1算法
Hashmap初始容量是16,若實際填充的容量是初始容量*負載因子,若全部填滿查詢的開銷太大。因此hashmap的容量達到75%,就會擴容。擴容的大小是原來的一倍。
注意jdk1.8中若鏈表的結點數大于8,則會轉化成紅黑樹(目的提高查詢效率)
鏈接:http://www.cnblogs.com/leesf456/p/5242233.html
負載因子load factor=hashmap的數據量(entry的數量)/初始容量(table數組長度),負載因子越高,表示初始容量中容納的數據會越多,雖然在空間中減少了開銷,但是大量數據聚集,造成查詢上的大量開銷。負載因子越低,表示初始容量中容納的數據會越少,造成大量的內存空間浪費,但是查詢的效率比較高。這是一個矛盾體,為了尋求平衡點,負載因子0.75效果是最好的。
鏈接:
http://www.ibm.com/developerworks/cn/java/java-lo-concurrenthashmap/index.html
要知道ConcurrentHashMap的結構、put和get方法
1、ConcurrentHashMap類中包含兩個靜態的內部類HashEntry
和Segment.HashEntry用來封裝映射表的鍵值對;Segment 用來充當鎖的角色,每個 Segment
對象守護整個散列映射表的若干個桶。每個桶由若干個HashEntry 對象鏈接起來的鏈表。一個ConcurrentHashMap
實例中包含由若干個 Segment 對象組成的數組。
2、用分離鎖實現多個線程間的并發寫操作(put方法實現的過程)
(1)key通過hash函數得到散列碼
(2)散列碼通過segmentFor找到對應的Segment(不允許value為空)
1)將散列值右移segmentShift 個位,并在高位填充 0
2) 然后把得到的值與 segmentMask 相“與”
3)從而得到 hash 值對應的 segments 數組的下標值
4)根據下標值返回Segment對象
(3)在Segment中執行具體的put操作
1)加鎖(鎖定某個segment,而非整個ConcurrentHashMap)
2)判斷HashEntry 是否超過閥值(負載因子*數組長度),若超過要進行散列
3)沒超過,判斷鍵值對是否存在,采用頭插法加入鏈表中‘;
4)然后解鎖
2.5 HashMap與ConcurrentHashMap的關聯與區別?
2.6 HashTable的實現原理?與ConcurrentHashMap的區別
2.7 concurrent包的并發容器有哪些?
3、java多線程問題
3.1 java實現多線程的方式及三種方式的區別
(1)繼承Thread類,重寫run函數
(2)實現Runnable接口(最常用)
(3)實現Callable接口
三種方式的區別
(1)實現Runnable接口可以避免java單繼承特性帶來的局限,增強程序健壯性,代碼能夠被多個線程共享。
(2)Thread和Runnable啟動線程都是使用start方法,然后JVM將此線程放到就緒隊列中,如果有處理機可用,則執行run方法。
(3)實現Callable接口要實現call方法,并且線程執行完畢后會有返回值,其他的兩種方法都是重寫run方法,沒有返回值。Callable接口提供了一個call()方法可以作為線程執行體,但call()方法比run()方法功能更為強大:call()方法可以有返回值;call()方法可以聲明拋出異常。
3.2 線程安全
定義:
(1)某個類的行為與其規范一致;
(2)不管多個線程是怎樣的執行順序和優先級,或是wait,sleep,join等控制方式。 如果一個類在多個線程訪問下運轉一切正常,并且訪問類不需要進行額外的同步處理或協調,那么我們認為它是線程安全的。
如何保證線程安全?
(1)對變量使用volatile
(2)對程序段進行加鎖(synchronized,lock)
注意:
非線程安全的集合在多線程環境下可以使用,但并不能作為多個線程共享的屬性,可以作為某個線程獨立的屬性。例如:Vector是線程安全的,ArrayList不是線程安全的,如果每一個線程new一個ArrayList,而這個ArrayList在這個線程中使用肯定沒有問題。(若再new一個ArrayList,就會線程不安全)
3.3 多線程如何進行信息交互
1、采用object方法:wait(), notify(), notifyAll();
2、在java 1.5中采用condition,比傳統的wait,notify()更加安全高效
3、采用生產者—-消費者模式(采用隊列的形式)
生產者發布消息在隊列中,消費者從隊列中取任務去執行。
3.4多線程共用一個數據變量需要注意什么?
出現的問題:當我們在線程對象(Runnable)中定義了全局變量,run方法修改該變量時,如果有多個線程同時使用該線程對象,那么就會造成全局變量的值被同時修改,造成錯誤。
解決方法1:ThreadLocal是JDK引入的一種機制,它用于解決線程共享變量,使用ThreadLocal聲明的變量,會在每個線程內產生一個變量副本,從而解決了線程不安全。
解決方法2:volatile變量每次被線程訪問時,都強迫線程從主內存中重新取該變量的最新值到工作內存中,而當該變量發生修改變化時,也會強迫線程將最新的值刷新會到主內存中。這樣一來,不同的線程都能及時的看到該變量的最新值。
3.5什么是線程池?如果讓你設計一個動態大小的線程池,如何設計,應該有哪些方法?
什么是線程池?
線程池顧名思義就是事先創建若干個可執行的線程放入一個池(容器)中,需要的時候從池中獲取線程不用自行創建(類似于工廠設計模式),使用完畢不需要銷毀線程而是返回池中,從而減少創建和銷毀線程對象的開銷。
設計一個動態大小的線程池,如何設計,應該有哪些方法??
一個線程池包括以下四個基本單位:
(1)線程管理器(ThreadPool):用于創建并管理線程池,包括創建線程,銷毀線程池,添加新任務;
(2)工作線程(PoolWorker):線程池中線程,在沒有任務時處于等待狀態,可以循環的執行任務;
(3)任務接口(Task):每個任務必須實現的接口,以供工作線程調度任務的執行,它主要規定任務的入口,任務執行完成后的收尾工作,任務的執行狀態等。
(4)任務隊列(TaskQueue):用于存放沒有處理的任務,提供一種緩沖機制。
所包含的方法:
(1)private ThreadPool()創建線程池;
(2)Public static ThreadPool getThreadPool()獲得一個默認線程個數的線程池;
(3)Public void execute(Runnable task)執行任務,其實只是把任務加入任務隊列,什么時候執行由線程池管理器確定;
(4)Public void execute(Runnable[] task) 批量執行任務,其實只是把任務加入任務隊列,什么時候執行由線程管理器確定。
(5)Public void destroy() 銷毀線程池,該方法保證所有任務都完成的情況下才銷毀所有線程,否則等待任務完成銷毀。
(6)Public int getWorkThreadNumber() 返回工作線程的個數。
(7)Public int getFinishedTasknumber()返回已完成任務的個數,這里的已完成是指出了任務隊列的任務個數,可能該任務并沒有實際執行完成。
(8)Public void addTread() 在保證線程池中所有線程正在執行,并且要執行線程的個數大于某一值時(就是核心池大小),增加線程池中線程的個數(最大是線程池大小)。
(9)Public void reduceThread() 在保證線程池中有很大一部分線程處于空閑狀態,并且空閑狀態的線程在小于某一值時(就是核心池大小),減少線程池中線程的個數。
3.6 volatile與synchronized區別
Volatile 關鍵字的作用
(1)保證內存可見性
(2)防止指令重排序
注意:volatile并不保證原子性。
內存的可見性:
Volatile保證可見性的原理是在每次訪問變量時都會進行刷新,因此每次訪問都是在主內存中得到最新的版本。所以volatile關鍵字的作用之一就是保證變量修改的實時可見性。
當且僅當滿足以下所有條件時,才應該使用volatile變量
(1)在兩個或更多的線程需要訪問的成員變量上使用volatile.當需要訪問的變量已在synchronized代碼塊中,或者為常量,沒必要使用volatitle;
(2)由于使用volatile屏蔽了JVM中必要的代碼優化,所以在效率上比較低,因此一定在必要時才使用此關鍵字。
Volatile和synchronized區別
(1)volatile不會進行加鎖操作
Volatile變量是一種稍弱的同步機制,在訪問volatile變量時不會執行加鎖操作,因此也就不會使執行線程阻塞,因此volatile變量是一種比synchronized關鍵字更輕量級的同步機制。(其實volatitle在讀寫時,就相當于加鎖)
(2)Volatile變量作用類似于同步變量讀寫操作
從內存可見性的角度看,寫入volatile變量相當于退出同步代碼塊,而讀取volatile變量相當于進入同步代碼塊。
(3)volatile不如synchronized安全
在代碼中如果過度依賴volatile變量來控制狀態的可見性,通常會比加鎖的代碼更加脆弱,也更加難以理解。僅當volatile變量能簡化代碼的實現以及對同步策略的驗證時,才應該使用它。一般來說,用同步機制更安全些。
(4)volatile無法同時保證內存可見性和原子性
加鎖機制(即同步機制)既可以確保可見性又可以確保原子性,而volatile變量只能確保可見性,原因是聲明為volatile的簡單變量如果當前值與該變量以前的值相關,那么volatile關鍵字不起作用,也就是說以下的表達式都不是原子操作:“count++”“count=count+1”。
3.7 sleep和wait分別是哪個類的方法,有什么區別?
Sleep和wait
Sleep是Thread類的方法。Wait是object類方法(與notify(), notifyAll();連用,即等待喚醒)
兩者有什么區別
Sleep()方法(休眠)是線程類(Thread)的靜態方法,調用此方法會讓當前線程暫停執行指定的時間,將執行機會(cpu)讓給其他線程,但是對象的鎖依然保持,因此休眠時間結束后會自動回復(線程回到就緒狀態)。
Wait()是Object類的方法,調用對象的wait方法導致當前線程放棄對象的鎖(線程暫停執行),進入對象的等待池(wait poo),只有調用對象的notify方法或notifyAll時才能喚醒等待池中的線程進入等鎖池(lock pool),如果線程重新獲得對象的鎖就可以進入就緒狀態。
3.8 synchronized與lock的區別,使用場景,看過synchronized的源碼沒?
Synchronized與lock的區別
(1)(用法)synchronized(隱式鎖):在需要同步的對象中加入此控制,synchronized可以加在方法上,也可以加在特定代碼塊中。
(2)(用法)Lock(顯示鎖):需要顯示指定起始位置和終止位置,一般使用ReentrantLock類做為鎖,多個線程中必須要使用一個 ReentrantLock類做為對象才能保證鎖的生效。且在加鎖和解鎖處需要通過lock()和unlock()顯示指出。所以一般在finally塊中寫unlock以防死鎖。
(3)(性能)synchronized是托管給JVM執行的,而lock是java寫的控制鎖代碼。在java1.5中,synchronized是性能低效的。因為這是一個重量級操作,需要調用操作接口,導致有可能加鎖消耗的系統時間比加鎖以外的操作還多。相比之下使用java提供的lock對象,性能更高一些。但是到了java1.6,發生了變化。Synchronized在語義上很清晰,可以進行很多優化,有適應自旋,鎖消除,鎖粗化,輕量級鎖,偏向鎖等等。導致在java1.8上synchronized的性能并不比lock差。
(4)(機制)synchronized原始采用的是cpu悲觀鎖機制,即線程獲得是獨占鎖。獨占鎖意味著其他線程只能依靠阻塞來等待線程釋放鎖。所謂樂觀鎖就是,每次不加鎖而是假設沒有沖突而去完成某項操作,如果因為沖突失敗就重試,直到成功為止,樂觀鎖實現的機制就是CAS操作(Compare and Swap)。
3.9 synchronized底層如何實現的?用在代碼塊和方法上有什么區別?
Synchronized底層如何實現的?(看源碼)用在代碼塊和方法上有什么區別?
(1)synchronized用在代碼塊鎖的是調用該方法的對象(this),也可以選擇鎖住任何一個對象。
(2)Synchronized用在方法上鎖的是調用該方法的對象。
(3)Synchronized用在代碼塊可以減少鎖的粒度,從而提高并發性能。
(4)無論用在代碼塊上還是用在方法上,都是獲取對象的鎖。每一個對象只有一個鎖與之相關聯;實現同步需要很大的系統開銷為代價,甚至可能造成死鎖,所以盡量避免無謂的同步控制。
Synchronized與static Synchronized的區別
(1)synchronized是對類的當前實例進行加鎖,防止其他線程同時訪問該類的該實例的所有synchronized塊,同一個類的兩個不同實例就沒有這種約束(這個對象壓根就是兩個不相關的東西)。
(2)那么static synchronized恰好就是要控制類的所有實例的訪問,static synchronized是限制線程同時訪問jvm中該類的所有實例同時訪問對應的代碼塊。
3.10 java中的NIO、BIO、AIO分別是什么
BIO:
(1)同步并阻塞(互斥同步),服務器實現模式為一個連接一個線程,即客戶端有連接請求時服務器端就需要啟動一個線程進行處理,如果這個鏈接不做任何事情會造成不必要的線程開銷,當然可以通過線程池機制改善;
(2)BIO方式適用于鏈接數目比較小且固定的架構,這種方式對服務器資源要求比較高,并發局限于應用中,jdk1.4以前的唯一選擇,但程序直觀簡單易理解。
NIO:
(1)同步非阻塞,服務器實現模式為一個請求一個線程,即客戶端發送的鏈接請求都會注冊到多路復用器上,多路復用器輪詢到鏈接有I/O請求時才啟動一個線程進行處理。
(2)NIO方式適用于鏈接數目多且鏈接比較短(輕操作)的架構,比如聊天服務器,并發局限于應用中,編程比較復雜,jdk1.4開始支持。
AIO:
(1)異步非阻塞,服務器實現模式為一個有效一個線程,客戶端的I/O請求都是由OS(操作系統)先完成了在通知服務器應用去啟動線程進行處理;
(2)AIO方式使用于鏈接數目多且鏈接比較長(重操作)的架構,比如相冊服務器,充分調用os參與并發操作,編程比較復雜,jdk7開始支持。
3.11什么是java內存模型(java memory model jmm)
描述了java程序中各種變量(線程共享變量)的訪問規則,以及在JVM中將變量存儲到內存和從內存中讀取出變量這樣的低層細節。
(1)所有的變量都存儲在主內存中。
(2)每個線程都有自己獨立的工作內存,里面保存該線程使用到的變量的副本(主內存中該變量的一份拷貝)。
Java內存模型的兩條規定
(1)線程對共享變量的所有操作都必須在自己的工作內存中進行,不能直接從主內存中讀寫;
(2)不同線程無法直接訪問其他線程工作內存中的變量,線程間變量值的訪問都需要通過主內存來完成。
3.12 JVM線程死鎖,你該如何判斷是什么原因?如果用VisualVM,dump打印線程信息出來,會有哪些信息?
常常需要在隔兩分鐘后再收集一次thread dump,如果得到的輸出相同,仍然是大量thread都在等待給同一個地址上的鎖,那么肯定是死鎖了。
3.1.2 各種鎖
1、悲觀鎖
無論共享數據是否產生爭用、是否由于爭用產生沖突,都會加鎖。
2、樂觀鎖
假設沒有共享數據爭用,就執行成功。若監測出有共享數據爭用產生沖突,就進行補救措施(如:重試)。
3、可重入鎖
一個線程加鎖了,該線程中調用其他的方法,也同樣加鎖了,如遞歸;
4、讀寫鎖
對一個資源的訪問,可以分成讀鎖和寫鎖;
5、可中斷鎖
一個線程等待的時間太長,可以中斷等待的過程,去執行其他的事情;
6、公平鎖
盡量以鎖的順序來獲取鎖;
7、優化方面的鎖
1)自旋鎖(自適應鎖)
共享數據鎖定狀態比較短,對于阻塞的線程不要立馬掛起,而是自旋一下就可得到,避免線程切換的開銷。
2)鎖消除
有些數據是不會被其他線程訪問到的,這時候就不需要加同步措施,進行鎖消除。
3)鎖粗化
同步塊的作用域一般越小越好,但是對一個對象的連續操作,不停的加鎖解鎖,這樣會出現很大的性能問題。
4)輕量級鎖
為了減少獲得鎖和釋放鎖所帶來的性能消耗。Java 1.6有無鎖狀態,偏向鎖狀態,輕量級鎖狀態和重量級鎖狀態四個狀態。隨著競爭,不斷升級,不能降級。
5)偏向鎖
目的是消除數據在無競爭情況下的同步原語。進一步提升程序的運行性能。 偏向鎖就是偏心的鎖,意思是這個鎖會偏向第一個獲得他的線程。
4、異常問題
4.1 常見異常分為兩種(Exception,Error)
Throwable是java語言中所有錯誤和異常的基類,它由兩個子類:Error, Exception.
異常種類
Error: Error為錯誤,是程序無法處理的,如OutOfMemoryError,ThreadDeath等,出現這種情況你唯一能做的就是聽之任之,交由JVM來處理,不過大多數情況下會選擇終止線程。
Exception: Exception是程序可以處理的異常,它又分為兩種CheckedException(受檢異常),一種是unCheckedException(不受檢異常)
CheckedException發生在編譯階段,必須要使用try……catch(或則throws)否則編譯不通過
unCheckedException發生在運行期,具有不確定性,主要是由于程序的邏輯問題所引起的,難以排查,我們一般都需要縱觀全局才能夠發現這類的異常錯誤。
常見異常的基類:
IOException
RuntimeException
5、 加載問題
6、 關鍵字的比較
6.1 static和final的區別和用途
Static:
1)修飾變量:靜態變量隨著類加載時被完全初始化,內存中只有一個,且JVM也只會為它分配一次內存,所有類共享靜態變量。
2)修飾方法:在類加載的時候就在,不依賴任何實例;static方法必須實現,不能用abstract修飾。
3)修飾代碼塊:在類加載完成后就會執行代碼塊中的內容
4)執行的順序:父類靜態代碼塊—>子類靜態代碼塊—->父類非靜態代碼塊—->父類構造方法—–>子類非靜態代碼塊—–>子類構造方法
Final:
1)修飾變量:編譯期常量(類加載的過程完成初始化,編譯后帶入到任何計算式中,只能是基本類型) 運行時常量(基本數據類型或引用數據類型,引用不可變,但引用的對象內容可變)
2)修飾方法:不能被繼承,不能被子類修改
3)修飾類:不能被繼承
修飾形參:final形參不可變
7、字符串
7.1 String、StringBuffer、StringBuilder以及對String不變性的理解
1)都是final類,都不允許被繼承;
2)String長度是不可變動的,StringBuffer、StringBuilder長度是可變的;
3)StringBuffer是線程安全的(在StringBuilder方法上添加關鍵字synchronized),StringBuilder線程不安全
4)StringBuilder比StringBuffer擁有更好的性能;
5)如果一個String類型的字符串,在編譯時就可以確定是一個字符串常量,則編譯完成之后,字符串會自動拼接成一個常量,此時String的速度比StringBuilder和StringBuffer的性能好的多。
String不變性的理解
1)String類是被final進行修飾的,不能被繼承;
2)在用+號連接字符串的時候會創建新的字符串;
3)String s=new String(“helle world”);可能創建兩個對象也可能創建一個對象。如果靜態區中有“hello world”字符串常量對象的話,則僅僅在堆中創建一個對象。如果靜態區沒有“hello world”對象,則在堆和靜態區中都創建對象。
4)在java中,通過使用“+”符合來鏈接字符串的時候,實際底層會轉成通過StringBuilder實例的append()方法實現。
7.2 String有重寫Object的hashcode和toString嗎?如果重寫equals不重寫hashcode會出現什么問題?
有重寫這些方法。當equals方法被重寫,通常有必要重寫hashCode方法,才能保證相等。
如:object1.equal(object2)為true, object1.hashCode==object2.hashCode()為true
兩個對象內容相等,那么hashCode指向的是同一個內容,返回的哈希值是相同的;
object1.hashCode==object2.hashCode()為false時,object1.equal(object2)為false
兩個hashCode不等,那么兩個對象的內容必然不同(每個對象的哈希值是唯一的);
object1.hashCode==object2.hashCode()為true時,object1.equal(object2)不一定為true;
比如hashmap,hashCode是數組的下標,但是會產生hash沖突,比如一個數組下標后連接一個鏈表;
重寫equals不重寫hashcode會出現什么問題??
在存儲散列集合時(如set類),如果原對象.equals(新對象),但沒有對hashcode重寫,即兩個對象擁有不同的hashcode,則在集合中會存儲兩個值相同的對象,從而導致混淆,因此在重寫equals方法時,必須重寫hashcode方法。
7.3 如果你定義一個類,包括學號,姓名,分數,如何把這個對象作為key?要重寫equals和hashcode嗎?
需要重寫equals方法和hashcode,必須保證對象的屬性改變時,其hashcode不能改變。
8、java協議
8.1 java序列化、如何實現序列化和反序列化,常見的序列化協議有哪些
Java序列化定義
將哪些實現Serializable接口的對象轉換成一個字節序列,并能夠在以后將這個字節序列完全恢復為原來的對象,序列化可以彌補不同操作系統之間的差異。
Java序列化的作用
(1)Java遠程方法調用(RMI)
(2)對javaBeans進行序列化
如何實現系列化和反序列化
實現序列化
(1)實現Serializable接口
1)該接口只是一個可序列化的標志,并沒有包含實際的屬性和方法;
2)如果不在該方法中添加readObject()和writeObject()方法,則采取默認的序列化機制,如果添加了這兩個方法之后還想利用java默認的序列化機制,則在這兩個方法中分別調用defaultReadObject()和defaultWriteObject()兩個方法;
3)為了保證安全性,可以使用transient關鍵字進行修飾不必序列化的屬性。因為在反序列化時,private修飾的屬性也能查看到。
(2)實現ExternalSerializable方法
自己對要序列化的內容進行控制,控制哪些屬性被序列化,哪些不能被序列化;
實現反序列化
(1)實現Serializable接口的對象在反序列化時不需要調用對象所在類的構造方法,完全基于字節;
(2)實現ExternalSerializable接口的方法在反序列化時會調用構造方法;
注意事項
(1)被static修飾的屬性不能被序列化;
(2)對象的類名、屬性都會被序列化,方法不會被序列化;
(3)要保證序列化對象所在類的屬性也是可以序列化的;
(4)當通過網絡、文件進行序列化時,必須按照寫入的順序讀取對象;
(5)反序列化時有序列化對象的class文件;
(6)最好顯式的聲明serializableID,因為在不同的JVM之間,默認生成serializableID;可能不同,會造成反序列化失敗。
常見的序列化協議有哪些?
(1)COM
(2)CORBA
(3)XML&SOAP
(4)JSON
(5)Thrift
……
9.其他
9.1 java的四個基礎特性(抽象、封裝、繼承、多態),對多態的理解(多態的實現方式,以及在項目中用到的多態)
1)java的四個基本特性
抽象:抽象是將一類對象的共同特征總結出來構造類的過程,包括數據抽象和行為抽象兩方面,抽象只關注對象有哪些屬性和行為,并不關注這些行為的細節是什么。
繼承:繼承是從已有類得到繼承信息創建新類的過程,提供繼承信息的類稱為父類(超類、基類);得到繼承信息的類被稱為子類(派生類),繼承讓變化中的軟件系統有了一定的延續性,同時繼承也是封裝程序中可變因素的重要手段。
封裝:通常認為封裝是把數據和操作方法綁定起來,對數據的訪問只能通過已定義的接口,面向對象的本質就是將現實世界描繪成一系列完全自治、封閉的對象,我們在類中編寫的方法就是對實現細節的一種封裝;我們編寫一個類就是對數據和數據操作的封裝。可以說,封裝就是隱藏一切可隱藏的東西,只向外界提供最簡單的編程接口。
多態:是指允許不同子類型的對象對同一消息做出不同的響應。
多態的理解(多態的實現方式)
1)方法重載(overload)實現編譯時的多態性(也稱為前綁定)(1、類型不同 2、參數個數不同 3、與返回值無關)。
2)方法重寫(override)實現運行時的多態(也稱為后綁定)。(核心精髓)
3)實現多態的兩件事:
a:方法重寫(子類繼承父類并重寫父類中已有的或抽象的方法)
B:對象造型(用父類型引用子類型對象,這樣同樣的引用調用同樣的方法就會根據子類對象的不同而表現出不同的行為)
項目中多態的應用:
1)單繼承
2)接口實現
例如:在接口中寫一個爬蟲的方法
在不同的網站,爬蟲實現的方法都不同,如網易采用jsoup進行解析、百度采用RSS,今日頭條采用ajax異步傳輸,實現就要采用獲取json進行解析。
HashMap Java 任務調度
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。