牛客错题集5
61.下面代碼的輸出是什么?
public class Base {private String baseName = "base";public Base(){callName();}public void callName(){System. out. println(baseName);}static class Sub extends Base{private String baseName = "sub";public void callName(){System. out. println (baseName) ;}}public static void main(String[] args){Base b = new Sub();} }A.null
B.sub
C.base
答案: A
解析:
這道題也是考察類加載順序的題目.
首先,需要明白類的加載順序:
- (1) 父類靜態代碼塊(包括靜態初始化塊,靜態屬性,但不包括靜態方法)
- (2) 子類靜態代碼塊(包括靜態初始化塊,靜態屬性,但不包括靜態方法 )
- (3) 父類非靜態代碼塊( 包括非靜態初始化塊,非靜態屬性 )
- (4) 父類構造函數
- (5) 子類非靜態代碼塊 ( 包括非靜態初始化塊,非靜態屬性 )
- (6) 子類構造函數
其中:類中靜態塊按照聲明順序執行,并且(1)和(2)不需要調用new類實例的時候就執行了(意思就是在類加載到方法區的時候執行的)
其次,需要理解子類覆蓋父類方法的問題,也就是方法重寫實現多態問題。
Base b = new Sub();它為多態的一種表現形式,聲明是Base,實現是Sub類,我們可以簡單理解為 b 編譯時表現為Base類特性,運行時表現為Sub類特性
當子類覆蓋了父類的方法后,意思是父類的方法已經被重寫,題中 父類初始化調用的方法為子類實現的方法,子類實現的方法中調用的baseName為子類中的私有屬性。
62.關于volatile關鍵字,下列描述不正確的是?
A.用volatile修飾的變量,每次更新對其他線程都是立即可見的。
B.對volatile變量的操作是原子性的。
C.對volatile變量的操作不會造成阻塞。
D.不依賴其他鎖機制,多線程環境下的計數器可用volatile實現。
答案: B D
解析:
所謂 volatile的措施,就是
- 每次從內存中取值,不從緩存中什么的拿值。這就保證了用 volatile修飾的共享變量,每次的更新對于其他線程都是可見的。
- volatile保證了其他線程的立即可見性,就沒有保證原子性
- 由于有些時候對 volatile的操作,不會被保存,說明不會造成阻塞。不可用與多線程環境下的計數器。
63.方法通常存儲在進程中的哪一區()
A.堆區
B.棧區
C.全局區
D.方法區
答案:D
解析:
一條進程的棧區、堆區、數據區和代碼區在內存中的映射:
- 棧區:主要用來存放局部變量, 傳遞參數, 存放函數的返回地址。.esp 始終指向棧頂, 棧中的數據越多, esp的值越小。
- 堆區:用于存放動態分配的對象, 當你使用 malloc和new 等進行分配時,所得到的空間就在堆中。動態分配得到的內存區域附帶有分配信息, 所以能夠 free和delete它們。
- 數據區:全局,靜態和常量是分配在數據區中的,數據區包括bss(未初始化數據區)和初始化數據區。
注意:- 1)堆向高內存地址生長;
- 2)棧向低內存地址生長;
- 3)堆和棧相向而生,堆和棧之間有個臨界點,稱為stkbrk。
64.下列關于Java并發的說法中正確的是()
A.CopyOnWriteArrayList適用于寫多讀少的并發場景
B.ReadWriteLock適用于讀多寫少的并發場景
C.ConcurrentHashMap的寫操作不需要加鎖,讀操作需要加鎖
D.只要在定義int類型的成員變量i的時候加上volatile關鍵字,那么多線程并發執行i++這樣的操作的時候就是線程安全的了
答案:B
解析:
- 1.CopyOnWriteArrayList的實現原理
在使用CopyOnWriteArrayList之前,我們先閱讀其源碼了解下它是如何實現的。以下代碼是向ArrayList里添加元素,可以發現在添加的時候是需要加鎖的,否則多線程寫的時候會Copy出N個副本出來。 public boolean add(T e) {final ReentrantLock lock = this.lock;lock.lock();try {Object[] elements = getArray();int len = elements.length;// 復制出新數組Object[] newElements = Arrays.copyOf(elements, len + 1);// 把新元素添加到新數組里newElements[len] = e;// 把原數組引用指向新數組setArray(newElements);return true;} finally {lock.unlock();}}final void setArray(Object[] a) {array = a;} 讀的時候不需要加鎖,如果讀的時候有多個線程正在向ArrayList添加數據,讀還是會讀到舊的數據,因為寫的時候不會鎖住舊的ArrayList public E get(int index) {return get(getArray(), index);} CopyOnWriteArrayList適用于讀多寫少的并發場景 - 2.ConcurrentHashMap
ConcurrentHashMap采用分段鎖技術,寫操作加鎖,讀操作一般不加鎖,除非讀到的鎖是空的,才會加鎖重讀 - 3.volatite
volatite只保證線程在“加載數據階段”加載的數據是最新的,并不能保證線程安全
一個線程執行的過程有三個階段:
加載(復制)主存數據到操作棧 --> 對操作棧數據進行修改 --> 將操作棧數據寫回主存
volatite關鍵字,讓編譯器不去優化代碼使用緩存等,以保證線程在“加載數據階段”加載的數據都是最新的
對于D選項,之所以volatile不能保證i++的線程安全,除了volatile只能保證可見性之外,與 i++ 不是原子操作也有關,如果換個場合,有些人可能就會把 i++ 誤當成原子操作,其實不是的,因為i++做了三次指令操作:- 1.從內存中讀取i 變量的值到CPU的寄存器;
- 2.在寄存器中的i自增1;
- 3.將寄存器中的值寫入內存;
65.jre 判斷程序是否執行結束的標準是()
A.所有的前臺線程執行完畢
B.所有的后臺線程執行完畢
C.所有的線程執行完畢
D.和以上都無關
答案:A
解析:
- 后臺線程:指為其他線程提供服務的線程,也稱為守護線程。JVM的垃圾回收線程就是一個后臺線程。
- 前臺線程:是指接受后臺線程服務的線程
其實前臺后臺線程是聯系在一起,就像傀儡和幕后操縱者一樣的關系。傀儡是前臺線程、幕后操縱者是后臺線程。由前臺線程創建的線程默認也是前臺線程。可以通過isDaemon()和setDaemon()方法來判斷和設置一個線程是否為后臺線程。
前臺線程和后臺線程的區別和聯系:
- 1、后臺線程不會阻止進程的終止。屬于某個進程的所有前臺線程都終止后,該進程就會被終止。所有剩余的后臺線程都會停止且不會完成。
- 2、可以在任何時候將前臺線程修改為后臺線程,方式是設置Thread.IsBackground 屬性。
- 3、不管是前臺線程還是后臺線程,如果線程內出現了異常,都會導致進程的終止。
- 4、托管線程池中的線程都是后臺線程,使用new Thread方式創建的線程默認都是前臺線程。
說明:
應用程序的主線程以及使用Thread構造的線程都默認為前臺線程
使用Thread建立的線程默認情況下是前臺線程,在進程中,只要有一個前臺線程未退出,進程就不會終止。主線程就是一個前臺線程。而后臺線程不管線程是否結束,只要所有的前臺線程都退出(包括正常退出和異常退出)后,進程就會自動終止。一般后臺線程用于處理時間較短的任務,如在一個Web服務器中可以利用后臺線程來處理客戶端發過來的請求信息。而前臺線程一般用于處理需要長時間等待的任務,如在Web服務器中的監聽客戶端請求的程序,或是定時對某些系統資源進行掃描的程序
66.下列關于系列化和反序列化描述正確的是:
A.序列化是將數據轉為n個 byte序列的過程
B.反序列化是將n個 byte轉換為數據的過程
C.將類型int轉換為4 byte是反序列化過程
D.將8個字節轉換為long類型的數據為序列化過程
答案:AB
76.在Java中,關于HashMap類的描述,以下正確的是 ()
A.HashMap使用鍵/值得形式保存數據
B.HashMap 能夠保證其中元素的順序
C.HashMap允許將null用作鍵
D.HashMap允許將null用作值
答案:A C D
解析:
67.下面哪些類實現或者繼承了Collection接口?
A.HashMap
B.ArrayList
C.Vector
D.Iterator
答案:B C
解析:
68.關于Java內存區域下列說法不正確的有哪些
A.程序計數器是一塊較小的內存空間,它的作用可以看做是當前線程所執行的字節碼的信號指示器,每個線程都需要一個獨立的程序計數器.
B.Java虛擬機棧描述的是java方法執行的內存模型,每個方法被執行的時候都會創建一個棧幀,用于存儲局部變量表、類信息、動態鏈接等信息
C.Java堆是java虛擬機所管理的內存中最大的一塊,每個線程都擁有一塊內存區域,所有的對象實例以及數組都在這里分配內存。
D.方法區是各個線程共享的內存區域,它用于存儲已經被虛擬機加載的常量、即時編譯器編譯后的代碼、靜態變量等數據。
答案:B C
解析:
- A.程序計數器是一塊較小的內存空間,它的作用可以看做是當前線程所執行的字節碼的信號指示器(偏移地址),Java編譯過程中產生的字節碼有點類似編譯原理的指令,程序計數器的內存空間存儲的是當前執行的字節碼的偏移地址,每一個線程都有一個獨立的程序計數器(程序計數器的內存空間是線程私有的),因為當執行語句時,改變的是程序計數器的內存空間,因此它不會發生內存溢出 ,并且程序計數器是jvm虛擬機規范中唯一一個沒有規定 OutOfMemoryError 異常 的區域;
- B.java虛擬機棧:線程私有,生命周期和線程一致。描述的是 Java 方法執行的內存模型:每個方法在執行時都會床創建一個棧幀(Stack Frame)用于存儲局部變量表、操作數棧、動態鏈接、方法出口等信息。每一個方法從調用直至執行結束,就對應著一個棧幀從虛擬機棧中入棧到出棧的過程。 沒有類信息,類信息是在方法區中
- C.java堆:對于絕大多數應用來說,這塊區域是 JVM 所管理的內存中最大的一塊。線程共享,主要是存放對象實例和數組
- D.方法區:屬于共享內存區域,存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯后的代碼等數據。
69.以下哪個不屬于JVM堆內存中的區域()?
A.survivor區
B.常量池
C.eden區
D.old區
答案:B
解析:
jvm堆分為:新生代(一般是一個Eden區,兩個Survivor區),老年代(old區)。
常量池屬于 PermGen(方法區)
70.下面有關java hashmap的說法錯誤的是?
A.HashMap 的實例有兩個參數影響其性能:“初始容量” 和 “加載因子”
B.HashMap 的實現不是同步的,意味著它不是線程安全的
C.HashMap通過開放地址法解決哈希沖突
D.HashMap中的key-value都是存儲在Entry數組中的
答案:C
解析:
- a) HashMap實際上是一個“鏈表散列”的數據結構,即數組和鏈表的結合體。HashMap的底層結構是一個數組,數組中的每一項是一條鏈表。
- b) HashMap的實例有倆個參數影響其性能: “初始容量” 和 裝填因子。
- c) HashMap實現不同步,線程不安全。 HashTable線程安全
- d) HashMap中的key-value都是存儲在Entry中的。
- e) HashMap可以存null鍵和null值,不保證元素的順序恒久不變,它的底層使用的是數組和鏈表,通過hashCode()方法和equals方法保證鍵的唯一性
- f) 解決沖突主要有三種方法:定址法,拉鏈法,再散列法。HashMap是采用拉鏈法解決哈希沖突的。
注:
* 鏈表法是將相同hash值的對象組成一個鏈表放在hash值對應的槽位;用開放定址法解決沖突的做法是:當沖突發生時,使用某種探查(亦稱探測)技術在散列表中形成一個探查(測)序列。 沿此序列逐個單元地查找,直到找到給定 的關鍵字,或者碰到一個開放的地址(即該地址單元為空)為止(若要插入,在探查到開放的地址,則可將待插入的新結點存人該地址單元)。
* 拉鏈法解決沖突的做法是: 將所有關鍵字為同義詞的結點鏈接在同一個單鏈表中 。若選定的散列表長度為m,則可將散列表定義為一個由m個頭指針組成的指針數 組T[0…m-1]。凡是散列地址為i的結點,均插入到以T[i]為頭指針的單鏈表中。T中各分量的初值均應為空指針。在拉鏈法中,裝填因子α可以大于1,但一般均取α≤1。拉鏈法適合未規定元素的大小。
- a) 繼承不同。 public class Hashtable extends Dictionary implements Map public class HashMap extends AbstractMap implements Map
- b) Hashtable中的方法是同步的,而HashMap中的方法在缺省情況下是非同步的。在多線程并發的環境下,可以直接使用Hashtable,但是要使用HashMap的話就要自己增加同步處理了。
- c) Hashtable 中, key 和 value 都不允許出現 null 值。 在 HashMap 中, null 可以作為鍵,這樣的鍵只有一個;可以有一個或多個鍵所對應的值為 null 。當 get() 方法返回 null 值時,即可以表示 HashMap 中沒有該鍵,也可以表示該鍵所對應的值為 null 。因此,在 HashMap 中不能由 get() 方法來判斷 HashMap 中是否存在某個鍵, 而應該用 containsKey() 方法來判斷。
- d) 兩個遍歷方式的內部實現上不同。Hashtable、HashMap都使用了Iterator。而由于歷史原因,Hashtable還使用了Enumeration的方式 。
- e) 哈希值的使用不同,HashTable直接使用對象的hashCode。而HashMap重新計算hash值。
- f) Hashtable和HashMap它們兩個內部實現方式的數組的初始大小和擴容的方式。HashTable中hash數組默認大小是11,增加的方式是old*2+1。HashMap中hash數組的默認大小是16,而且一定是2的指數。
注: HashSet子類依靠hashCode()和equal()方法來區分重復元素。
HashSet內部使用Map保存數據,即將HashSet的數據作為Map的key值保存,這也是HashSet中元素不能重復的原因。而Map中保存key值的,會去判斷當前Map中是否含有該Key對象,內部是先通過key的hashCode,確定有相同的hashCode之后,再通過equals方法判斷是否相同。
71.Java是一門支持反射的語言,基于反射為Java提供了豐富的動態性支持,下面關于Java反射的描述,哪些是錯誤的:( )
A.Java反射主要涉及的類如Class, Method, Filed,等,他們都在java.lang.reflet包下
B.通過反射可以動態的實現一個接口,形成一個新的類,并可以用這個類創建對象,調用對象方法
C.通過反射,可以突破Java語言提供的對象成員、類成員的保護機制,訪問一般方式不能訪問的成員
D.Java反射機制提供了字節碼修改的技術,可以動態的修剪一個類
E.Java的反射機制會給內存帶來額外的開銷。例如對永生堆的要求比不通過反射要求的更多
F.Java反射機制一般會帶來效率問題,效率問題主要發生在查找類的方法和字段對象,因此通過緩存需要反射類的字段和方法就能達到與之間調用類的方法和訪問類的字段一樣的效率
答案:A D F
解析:
- A Class類在java.lang包
- B 動態代理技術可以動態創建一個代理對象,反射不行
- C 反射訪問私有成員時,Field調用setAccessible可解除訪問符限制
- D CGLIB實現了字節碼修改,反射不行
- E 反射會動態創建額外的對象,比如每個成員方法只有一個Method對象作為root,他不胡直接暴露給用戶。調用時會返回一個Method的包裝類
- F 反射帶來的效率問題主要是動態解析類,JVM沒法對反射代碼優化。
72.關于身份證號,以下正確的正則表達式為( )
A.isIDCard=/^[1-9]\d{7}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}$/;
B.isIDCard=/^[1-9]\d{7}((9\d)|(1[0-2]))(([0|1|2]\d)|3[9-1])\d{3}$/;
C.isIDCard=/^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{4}$/;
D.isIDCard=/^[1-9]\d{5}[1-9]\d{3}((9\d)|(1[9-2]))(([0|1|2]\d)|3[9-1])\d{4}$/;
答案: A C
解析:
- ^:起始符號,^x表示以x開頭
- $:結束符號,x$表示以x結尾
- [n-m]:表示從n到m的數字
- \d:表示數字,等同于[0-9]
- X{m}:表示由m個X字符構成,\d{4}表示4位數字
15位身份證的構成:六位出生地區碼+六位出身日期碼+三位順序碼
18位身份證的構成:六位出生地區碼+八位出生日期碼+三位順序碼+一位校驗碼
73.Java表達式"13 & 17"的結果是什么?()
A.30
B.13
C.17
D.1
答案:D
解析:
此處考察與運算,做這種題只需要2步
1.將數值轉為二進制:13(01101),17(10001)
2.將二進制數值進行相與運算,只有都為1的情況下才為1
最終的運算結果為00001即對應的10進制為1
74.關于protected 修飾的成員變量,以下說法正確的是()
A.可以被該類自身、與它在同一個包中的其它類、在其它包中的該類的子類所訪問
B.只能被該類本身和該類的所有的子類訪問
C.只能被該類自身所訪問
D.只能被同一個包中的類訪問
答案:A
解析:
| public | √ | √ | √ | √ |
| protected | √ | √ | √ | × |
| default | √ | √ | × | × |
| private | √ | × | × | × |
75.以下代碼執行的結果顯示是多少( )?
A.true,false,true
B.false,true,false
C.true,true,false
D.false,false,true
答案:D
解析:
首先我先解釋一下String的地址引用
- 1.String s = “abc”:通過字面量賦值創建字符串。則將棧中的引用直接指向該字符串,如不存在,則在常量池中生成一個字符串,再將棧中的引用指向該字符串
- 2.String s = “a”+“bc”:編譯階段會直接將“a”和“bc”結合成“abc”,這時如果方法區已存在“abc”,則將s的引用指向該字符串,如不存在,則在方法區中生成字符串“abc”對象,然后再將s的引用指向該字符串
- 3.String s = “a” + new String(“bc”):棧中先創建一個"a"字符串常量,再創建一個"bc"字符串常量,編譯階段不會進行拼接,在運行階段拼接成"abc"字符串常量并將s的引用指向它,效果相當于String s = new String(“abc”),只有’+'兩邊都是字符串常量才會在編譯階段優化。
我們再回到題目,其中i3和i4分別屬于1,3的情況,i3屬于字符串常量,在棧中(常量池中),i4屬于拼接,只有在編譯階段才能知道,所以在堆中,地址則肯定不同。
接下來我接著解釋一下整型的地址引用
Integer i1=128 表示Integer i1=Integer.valueOf(128)
Interger 賦予的int數值在-128 - 127的時候,直接從IntegerCache中獲取,這些IntegerCache引用對Integer對象地址是不變的,但是不在這個范圍內的數字,則new Integer(i) 這個地址是新的地址,不可能一樣的。
總結
- 上一篇: Pr:导出设置之多路复用器与常规
- 下一篇: 支配树与Lengauer-Tarjan算