【JAVA面試】java面試題整理(3)

                                     java面試題整理(3)

JAVA常考點3

目錄

1. 講下JAVA的運行時區域 1

2、簡單說下垃圾回收機制 2

3、TCP和UDP的區別 7

4、項目是怎樣預防sql注入的 7

5、 MySQL存儲引擎中的MyISAM和InnoDB區別 7

6、B樹與B+樹簡明扼要的區別 11

6、 解決哈希衝突的三種方法(拉鍊法、開放地址法、再散列法) 14

7、forward(轉發)和redirect(重定向)有什麼區別 16

8、set、list、map 16

9、 HashMap、Hashtable、ConcurrentHashMap的原理與區別 19

 

 

 

 

 

1. 講下JAVA的運行時區域

 

回答:運行時數據區整體分爲兩類 線程私有和線程共享。

 

線程私有的包括:

 

(1)程序計數器

 

若正在執行的是java方法,則計數器記錄的是正在執行的字節碼指令的地址

若正在執行的是native方法,則計數器爲空

該區域是唯一一個不會導致outofmemoryError的區域

 

(2)虛擬機棧

 

描述的是Java方法執行的內存模型:每個方法都會創建一個棧幀用於存儲局部變量表,操作數棧,動態鏈接,方法出口等信息

 

局部變量表存放了編譯期可知的基本數據類型,對象引用,和returnAddress類型(指向一條字節碼指令地址),局部變量表的內存空間在編譯器確定,在運行期不變

可導致兩種異常:線程請求的棧深度大於虛擬機允許的深度-StackOverflowError;虛擬機無法申請到足夠的內存-OutOfMemoryError

 

(3)本地方法棧

和虛擬機棧類似,但它是爲Native方法服務的

 

 

線程共享的包括:

 

(1)堆

 

java堆是被所有線程共享的內存區域,在虛擬機啓動時創建,用來分配對象實例和數組

堆是垃圾回收器主要管理的區域,堆可分爲新生代和老年代

從內存分配角度看,堆可劃分出多個線程私有的分配緩衝區(TLAB)

大小可通過 -Xmx 和 -Xms 控制

 

(2)方法區

 

用來存放虛擬機加載的類信息,常量,靜態變量,即時編譯器編譯後的代碼等信息

GC會回收該區域的常量池和進行類型的卸載 *運行時常量池

♣ Class文件的常量池用於存放編譯期生成的各種字面量和符號引用,這部分內容將在類加載後存放在運行時常量池中

♣ 還把翻譯出來的直接引用也放在運行時常量池中,運行時產生的常量也放在裏面

 

 

 

2、簡單說下垃圾回收機制

大致思路: 要進行垃圾回收,首先要判斷一個對象是否活着,這就引出了兩種方法…

 

引用計數法和可達性分析法

 

gc roots 類型

 

引用類型

 

兩次標記過程

 

垃圾回收算法

 

內存分配策略

 

觸發垃圾回收

 

垃圾回收器

 

也會回收方法區

 

 

回答:要進行垃圾回收,首先要判斷對象是否存活,引出了兩個方法:

 

  •  

引用計數法

    • 思想:給對象設置引用計數器,沒引用該對象一次,計數器就+1,引用失效時,計數器就-1,當任意時候引用計數器的值都爲0時,則該對象可被回收
    • Java不適用原因:無法解決對象互相循環引用的問題
  •  

可達性分析法

    • 以GC Roots爲起點,從這些起點開始向下搜索,經過的路徑稱爲引用鏈。若一個對象到GC Roots之間沒有任何引用鏈,則該對象是不可達的。
    • 那麼可作爲GC Roots的對象有
      • 虛擬機棧(棧幀中的局部變量表)中引用的對象
      • 方法區中類靜態屬性引用的對象
      • 方法區中常量引用的對象
      • 本地方法棧中JNI(Native方法)引用的對象
  •  

在可達性分析過程中,對象引用類型會對對象的生命週期產生影響,JAVA中有這幾種類型的引用:

    • 強引用:只要該引用還有效,GC就不會回收
    • 軟引用:內存空間足夠時不進行回收,在內存溢出發生前進行回收、用SoftReference類實現
    • 弱引用:弱引用關聯的對象只能存活到下一次Gc收集、用WeakReference類實現
    • 虛引用:無法通過虛引用獲得對象實例,也不會對對象的生存時間產生影響、唯一目的:當該對象被Gc收集時,收到一個系統通知。用PhantomReference類實現
    •  

一個對象真正不可用,要經歷兩次標記過程:

  • 首先進行可達性分析,篩選出與GC Roots沒用引用鏈的對象,進行第一次標記
  • 第一次標記後,再進行一次篩選,篩選條件是是否有必要執行finalize()方法。若對象有沒有重寫finalize()方法,或者finalize()是否已被jvm調用過,則沒必要執行,GC會回收該對象
  • 若有必要執行,則該對象會被放入F-Queue中,由jvm開啓一個低優先級的線程去執行它(但不一定等待finalize執行完畢)。
  • Finalize()是對象最後一次自救的機會,若對象在finalize()中重新加入到引用鏈中,則它會被移出要回收的對象的集合。其他對象則會被第二次標記,進行回收

JAVA中的垃圾回收算法有:

  • 標記-清除(Mark-Sweep)
    • 兩個階段:標記, 清除
    • 缺點:兩個階段的效率都不高;容易產生大量的內存碎片
  • 複製(Copying)
    • 把內存分成大小相同的兩塊,當一塊的內存用完了,就把可用對象複製到另一塊上,將使用過的一塊一次性清理掉
    • 缺點:浪費了一半內存
  • 標記-整理(Mark-Compact)
    • 標記後,讓所有存活的對象移到一端,然後直接清理掉端邊界以外的內存
  • 分代收集
    • 把堆分爲新生代和老年代
    • 新生代使用複製算法
    • 將新生代內存分爲一塊大的Eden區和兩塊小的Survivor;每次使用Eden和一個Survivor,回收時將Eden和Survivor存活的對象複製到另一個Survivor(HotSpot的比例Eden:Survivor = 8:1)
    • 老年代使用標記-清理或者標記-整理
    •  

觸發GC又涉及到了內存分配規則: (對象主要分配在Eden,若啓動了本地線程分配緩衝,將優先在TLAB上分配)

  • 對象優先在Eden分配
    • 當Eden區沒有足夠的空間時就會發起一次Minor GC
  • 大對象直接進入老年代
    • 典型的大對象是很長的字符串和數組
  • 長期存活的對象進入老年代
    • 每個對象有年齡計數器,每經過一次GC,計數器值加一,當到達一定程度時(默認15),就會進入老年代
    • 年齡的閾值可通過參數 -XX:MaxTenuringThreshold設置
  • 對象年齡的判定
    • Survivor空間中相同年齡所有對象大小的總和大於Survivor空間的一半,年齡大於等於該年齡的對象就可直接進入老年代,無須等到MaxTenuringThreshold要求的年齡
  • 空間分配擔保
    • 發生Minor GC前,jvm會檢查老年代最大可用的連續空間是否大於新生代所有對象總空間,若大於,則Minor GC是安全的
    • 若不大於,jvm會查看HandlePromotionFailure是否允許擔保失敗,若不允許,則改爲一次Full GC
    • 若允許擔保失敗,則檢查老年代最大可用的連續空間是否大於歷次晉升到老年代對象的平均大小,若大於,則嘗試進行Minor GC;若小於,則要改爲Full GC

垃圾收集器:

  •  

Serial(串行收集器)

    • 特性:單線程,stop the world,採用複製算法
    • 應用場景:jvm在Client模式下默認的新生代收集器
    • 優點:簡單高效
  •  

ParNew

    • 特點:是Serial的多線程版本,採用複製算法
    • 應用場景:在Server模式下常用的新生代收集器,可與CMS配合工作
  •  

Parallel Scavenge

    • 特點:並行的多線程收集器,採用複製算法,吞吐量優先,有自適應調節策略
    • 應用場景:需要吞吐量大的時候
  •  

SerialOld

    • 特點:Serial的老年代版本,單線程,使用標記-整理算法
  •  

Parallel Old

    • Parallel Scavenge的老年代版本,多線程,標記-整理算法
  •  

CMS

    • 特點:以最短回收停頓時間爲目標,使用標記-清除算法
    • 過程:
      • 初始標記:stop the world 標記GC Roots能直接關聯到的對象
      • 併發標記:進行GC Roots Tracing
      • 重新標記:stop the world;修正併發標記期間因用戶程序繼續運作而導致標記產生變動的 那一部分對象的標記記錄
      • 併發清除:清除對象
    • 優點:併發收集,低停頓
    • 缺點:
      • 對CPU資源敏感
      • 無法處理浮動垃圾(併發清除 時,用戶線程仍在運行,此時產生的垃圾爲浮動垃圾)
      • 產生大量的空間碎片
  •  

G1

    • 特點:面向服務端應用,將整個堆劃分爲大小相同的region。
      • 並行與併發
      • 分代收集
      • 空間整合:從整體看是基於“標記-整理”的,從局部(兩個region之間)看是基於“複製”的。
      • 可預測的停頓:使用者可明確指定在一個長度爲M毫秒的時間片段內,消耗在垃圾收集上的時間不得超過N毫秒。
    • 執行過程:
      • 初始標記:stop the world 標記GC Roots能直接關聯到的對象
      • 併發標記:可達性分析
      • 最終標記:修正在併發標記期間因用戶程序繼續運作而導致標記產生變動的那一部分標記記錄
      • 篩選回收:篩選回收階段首先對各個Region的回收價值和成本進行排序,根據用戶所期望的GC停頓時間來制定回收計劃
  •  

GC自適應調節策略 Parallel Scavenge收集器有一個參數-XX:+UseAdaptiveSizePolicy。當這個參數打開之後,就不需要手工指定新生代的大小、Eden與Survivor區的比例、晉升老年代對象年齡等細節參數了,虛擬機會根據當前系統的運行情況收集性能監控信息,動態調整這些參數以提供最合適的停頓時間或者最大的吞吐量,這種調節方式稱爲GC自適應的調節策略(GC Ergonomics)。

  •  

(垃圾回收器部分重點講CMS和G1)

最後提一下也會回收方法區:

  • 永久代中主要回收兩部分內容:廢棄常量和無用的類
  • 廢棄常量回收和對象的回收類似
  • 無用的類需滿足3個條件
    • 該類的所有實例對象已被回收
    • 加載該類的ClassLoader已被回收
    • 該類的Class對象沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法

上面的知識點在你多刷幾遍書,腦中形成相應的知識網後能很全面的說出來。

3、TCP和UDP的區別

  • TCP面向連接(如打電話要先撥號建立連接) UDP是無連接的,即發送數據之前不需要建立連接
  • TCP提供可靠的服務。也就是說,通過TCP連接傳送的數據,無差錯,不丟失,不重複,且按序到達;UDP盡最大努力交付,即不保證可靠交付
  • TCP面向字節流,實際上是TCP把數據看成一連串無結構的字節流;UDP是面向報文的
  • UDP沒有擁塞控制,因此網絡出現擁塞不會使源主機的發送速率降低(對實時應用很有用,如IP電話,實時視頻會議等)
  • 每一條TCP連接只能是點到點的;UDP支持一對一,一對多,多對一和多對多的交互通信
  • TCP首部開銷20字節;UDP的首部開銷小,只有8個字節
  • TCP的邏輯通信信道是全雙工的可靠信道,UDP則是不可靠信道

 

 

4、項目是怎樣預防sql注入的

回答:用的是mybatis,sql語句中用#{},#{}表示一個佔位符號,通過#{}可以實現preparedStatement向佔位符中設置值,jdbc有個預編譯的過程可以有效預防sql注入,儘量不用${},它是個拼接符,用來拼接sql字符串。

 

 

 

 

  1. MySQL存儲引擎中的MyISAM和InnoDB區別

在使用MySQL的過程中對MyISAM和InnoDB這兩個概念存在了些疑問,到底兩者引擎有何分別一直是存在我心中的疑問。爲了解開這個謎題,搜尋了網絡,找到了如下信息:

 

MyISAM是MySQL的默認數據庫引擎(5.5版之前),由早期的ISAM(Indexed Sequential Access Method:有索引的順序訪問方法)所改良。雖然性能極佳,但卻有一個缺點:不支持事務處理(transaction)。不過,在這幾年的發展下,MySQL也導入了InnoDB(另一種數據庫引擎),以強化參考完整性與併發違規處理機制,後來就逐漸取代MyISAM。

 

InnoDB,是MySQL的數據庫引擎之一,爲MySQL AB發佈binary的標準之一。InnoDB由Innobase Oy公司所開發,2006年五月時由甲骨文公司併購。與傳統的ISAM與MyISAM相比,InnoDB的最大特色就是支持了ACID兼容的事務(Transaction)功能,類似於PostgreSQL。目前InnoDB採用雙軌制授權,一是GPL授權,另一是專有軟件授權。

 

MyISAM與InnoDB的區別是什麼?

 

存儲結構

 

MyISAM:每個MyISAM在磁盤上存儲成三個文件。第一個文件的名字以表的名字開始,擴展名指出文件類型。.frm文件存儲表定義。數據文件的擴展名爲.MYD (MYData)。索引文件的擴展名是.MYI (MYIndex)。

InnoDB:所有的表都保存在同一個數據文件中(也可能是多個文件,或者是獨立的表空間文件),InnoDB表的大小隻受限於操作系統文件的大小,一般爲2GB。

 

存儲空間

 

MyISAM:可被壓縮,存儲空間較小。支持三種不同的存儲格式:靜態表(默認,但是注意數據末尾不能有空格,會被去掉)、動態表、壓縮表。

InnoDB:需要更多的內存和存儲,它會在主內存中建立其專用的緩衝池用於高速緩衝數據和索引。

 

可移植性、備份及恢復

 

MyISAM:數據是以文件的形式存儲,所以在跨平臺的數據轉移中會很方便。在備份和恢復時可單獨針對某個表進行操作。

InnoDB:免費的方案可以是拷貝數據文件、備份 binlog,或者用 mysqldump,在數據量達到幾十G的時候就相對痛苦了。

 

事務支持

 

MyISAM:強調的是性能,每次查詢具有原子性,其執行數度比InnoDB類型更快,但是不提供事務支持。

InnoDB:提供事務支持事務,外部鍵等高級數據庫功能。 具有事務(commit)、回滾(rollback)和崩潰修復能力(crash recovery capabilities)的事務安全(transaction-safe (ACID compliant))型表。

 

AUTO_INCREMENT

 

MyISAM:可以和其他字段一起建立聯合索引。引擎的自動增長列必須是索引,如果是組合索引,自動增長可以不是第一列,他可以根據前面幾列進行排序後遞增。

InnoDB:InnoDB中必須包含只有該字段的索引。引擎的自動增長列必須是索引,如果是組合索引也必須是組合索引的第一列。

 

表鎖差異

 

MyISAM:只支持表級鎖,用戶在操作myisam表時,select,update,delete,insert語句都會給表自動加鎖,如果加鎖以後的表滿足insert併發的情況下,可以在表的尾部插入新的數據。

InnoDB:支持事務和行級鎖,是innodb的最大特色。行鎖大幅度提高了多用戶併發操作的新能。但是InnoDB的行鎖,只是在WHERE的主鍵是有效的,非主鍵的WHERE都會鎖全表的。

 

全文索引

 

MyISAM:支持 FULLTEXT類型的全文索引

InnoDB:不支持FULLTEXT類型的全文索引,但是innodb可以使用sphinx插件支持全文索引,並且效果更好。

 

表主鍵

 

MyISAM:允許沒有任何索引和主鍵的表存在,索引都是保存行的地址。

InnoDB:如果沒有設定主鍵或者非空唯一索引,就會自動生成一個6字節的主鍵(用戶不可見),數據是主索引的一部分,附加索引保存的是主索引的值。

 

表的具體行數

 

MyISAM:保存有表的總行數,如果select count(*) from table;會直接取出出該值。

InnoDB:沒有保存表的總行數,如果使用select count(*) from table;就會遍歷整個表,消耗相當大,但是在加了wehre條件後,myisam和innodb處理的方式都一樣。

 

CURD操作

 

MyISAM:如果執行大量的SELECT,MyISAM是更好的選擇。

InnoDB:如果你的數據執行大量的INSERT或UPDATE,出於性能方面的考慮,應該使用InnoDB表。DELETE 從性能上InnoDB更優,但DELETE FROM table時,InnoDB不會重新建立表,而是一行一行的刪除,在innodb上如果要清空保存有大量數據的表,最好使用truncate table這個命令。

 

外鍵

 

MyISAM:不支持

InnoDB:支持

通過上述的分析,基本上可以考慮使用InnoDB來替代MyISAM引擎了,原因是InnoDB自身很多良好的特點,比如事務支持、存儲 過程、視圖、行級鎖定等等,在併發很多的情況下,相信InnoDB的表現肯定要比MyISAM強很多。另外,任何一種表都不是萬能的,只用恰當的針對業務類型來選擇合適的表類型,才能最大的發揮MySQL的性能優勢。如果不是很複雜的Web應用,非關鍵應用,還是可以繼續考慮MyISAM的,這個具體情況可以自己斟酌。

 

 

在MySQL數據庫中,常用的引擎主要就是2個:Innodb和MyIASM。

 

 

 

其他的解釋:   首先:

 

(1)簡單介紹這兩種引擎,以及該如何去選擇。

(2)這兩種引擎所使用的數據結構是什麼。

 

 

a.Innodb引擎,Innodb引擎提供了對數據庫ACID事務的支持。並且還提供了行級鎖和外鍵的約束。它的設計的目標就是處理大數據容量的數據庫系統。它本身實際上是基於Mysql後臺的完整的系統。Mysql運行的時候,Innodb會在內存中建立緩衝池,用於緩衝數據和索引。但是,該引擎是不支持全文搜索的。同時,啓動也比較的慢,它是不會保存表的行數的。當進行Select count(*) from table指令的時候,需要進行掃描全表。所以當需要使用數據庫的事務時,該引擎就是首選。由於鎖的粒度小,寫操作是不會鎖定全表的。所以在併發度較高的場景下使用會提升效率的。

 

b.MyIASM引擎,它是MySql的默認引擎,但不提供事務的支持,也不支持行級鎖和外鍵。因此當執行Insert插入和Update更新語句時,即執行寫操作的時候需要鎖定這個表。所以會導致效率會降低。不過和Innodb不同的是,MyIASM引擎是保存了表的行數,於是當進行Select count(*) from table語句時,可以直接的讀取已經保存的值而不需要進行掃描全表。所以,如果表的讀操作遠遠多於寫操作時,並且不需要事務的支持的。可以將MyIASM作爲數據庫引擎的首先。

 

補充2點:

 

c.大容量的數據集時趨向於選擇Innodb。因爲它支持事務處理和故障的恢復。Innodb可以利用數據日誌來進行數據的恢復。主鍵的查詢在Innodb也是比較快的。

 

d.大批量的插入語句時(這裏是INSERT語句)在MyIASM引擎中執行的比較的快,但是UPDATE語句在Innodb下執行的會比較的快,尤其是在併發量大的時候。

 

兩種引擎所使用的索引的數據結構是什麼?

 

答案:都是B+樹!

 

MyIASM引擎,B+樹的數據結構中存儲的內容實際上是實際數據的地址值。也就是說它的索引和實際數據是分開的,只不過使用索引指向了實際數據。這種索引的模式被稱爲非聚集索引。

 

Innodb引擎的索引的數據結構也是B+樹,只不過數據結構中存儲的都是實際的數據,這種索引有被稱爲聚集索引。

 

 

 

6、B樹與B+樹簡明扼要的區別

看了很多講B樹和B+樹的文章,大多都是圍繞各自的特性講的,第一,樹中每個結點最多含有m個孩子(m>=2);第二,……我也是從這些文章里弄懂了各種樹的聯繫與區別,要真寫,我可能還不如人家寫得好。所以就在這裏簡明扼要的用幾張圖記錄一下主要區別吧。 

 

  爲了便於說明,我們先定義一條數據記錄爲一個二元組[key,data],key爲記錄的鍵值,key唯一;data爲數據記錄除key外的數據。

 

 

B樹

 

  每個節點都存儲key和data,所有節點組成這棵樹,並且葉子節點指針爲null。

 

  

 

B+樹

 

  只有葉子節點存儲data,葉子節點包含了這棵樹的所有鍵值,葉子節點不存儲指針。

  

 

  後來,在B+樹上增加了順序訪問指針,也就是每個葉子節點增加一個指向相鄰葉子節點的指針,這樣一棵樹成了數據庫系統實現索引的首選數據結構。 

 

  原因有很多,最主要的是這棵樹矮胖,呵呵。一般來說,索引很大,往往以索引文件的形式存儲的磁盤上,索引查找時產生磁盤I/O消耗,相對於內存存取,I/O存取的消耗要高几個數量級,所以評價一個數據結構作爲索引的優劣最重要的指標就是在查找過程中磁盤I/O操作次數的時間複雜度。樹高度越小,I/O次數越少。 

 

  那爲什麼是B+樹而不是B樹呢,因爲它內節點不存儲data,這樣一個節點就可以存儲更多的key。

 

 

 

  在MySQL中,最常用的兩個存儲引擎是MyISAM和InnoDB,它們對索引的實現方式是不同的。

 

 

 

MyISAM 

 

  data存的是數據地址。索引是索引,數據是數據。

InnoDB

 

  data存的是數據本身。索引也是數據。

  

 

  瞭解了數據結構再看索引,一切都不費解了,只是順着邏輯推而已。

 

 

 

  1. 解決哈希衝突的三種方法(拉鍊法、開放地址法、再散列法)

上篇博客我們說到了,什麼是哈希衝突,其實就是再採用哈希函數對輸入域進行映射到哈希表的時候,因爲哈希表的位桶的數目遠小於輸入域的關鍵字的個數,所以,對於輸入域的關鍵字來說,很可能會產生這樣一種情況,也就是,一個關鍵字會映射到同一個位桶中的情況,這種情況就就叫做哈希衝突,解決哈希衝突的有三種方案,一種叫做拉鍊法(也叫作鏈接法、鏈地址法,一個意思),另外三種分別爲開發地址法和再散列法。

 

(1)拉鍊法

 

上篇博文我們舉的例子,HashMap,HashSet其實都是採用的拉鍊法來解決哈希衝突的,就是在每個位桶實現的時候,我們採用鏈表(jdk1.8之後採用鏈表+紅黑樹)的數據結構來去存取發生哈希衝突的輸入域的關鍵字(也就是被哈希函數映射到同一個位桶上的關鍵字)。首先來看使用拉鍊法解決哈希衝突的幾個操作:

 

①插入操作:在發生哈希衝突的時候,我們輸入域的關鍵字去映射到位桶(實際上是實現位桶的這個數據結構,鏈表或者紅黑樹)中去的時候,我們先檢查帶插入元素x是否出現在表中,很明顯,這個查找所用的次數不會超過裝載因子(n/m:n爲輸入域的關鍵字個數,m爲位桶的數目),它是個常數,所以插入操作的最壞時間複雜度爲O(1)的。

 

②查詢操作:和①一樣,在發生哈希衝突的時候,我們去檢索的時間複雜度不會超過裝載因子,也就是檢索數據的時間複雜度也是O(1)的

 

③刪除操作:如果在拉鍊法中我們想要使用鏈表這種數據結構來實現位桶,那麼這個鏈表一定是雙向鏈表,因爲在刪除一個元素x的時候,需要更改x的前驅元素的next指針的屬性,把x從鏈表中刪除。這個操作的時間複雜度也是O(1)的。

 

 

 

拉鍊法的優點

 

與開放定址法相比,拉鍊法有如下幾個優點:

 

①拉鍊法處理衝突簡單,且無堆積現象,即非同義詞決不會發生衝突,因此平均查找長度較短;

 

②由於拉鍊法中各鏈表上的結點空間是動態申請的,故它更適合於造表前無法確定表長的情況;

 

③開放定址法爲減少衝突,要求裝填因子α較小,故當結點規模較大時會浪費很多空間。而拉鍊法中可取α≥1,且結點較大時,拉鍊法中增加的指針域可忽略不計,因此節省空間;

 

④在用拉鍊法構造的散列表中,刪除結點的操作易於實現。只要簡單地刪去鏈表上相應的結點即可。

 

 

 

拉鍊法的缺點

 

指針需要額外的空間,故當結點規模較小時,開放定址法較爲節省空間,而若將節省的指針空間用來擴大散列表的規模,可使裝填因子變小,這又減少了開放定址法中的衝突,從而提高平均查找速度。

 

使用例子:

 

HashMap

 

(2)開發地址法

 

開放地址法有個非常關鍵的特徵,就是所有輸入的元素全部存放在哈希表裏,也就是說,位桶的實現是不需要任何的鏈表來實現的,換句話說,也就是這個哈希表的裝載因子不會超過1。它的實現是在插入一個元素的時候,先通過哈希函數進行判斷,若是發生哈希衝突,就以當前地址爲基準,根據再尋址的方法(探查序列),去尋找下一個地址,若發生衝突再去尋找,直至找到一個爲空的地址爲止。所以這種方法又稱爲再散列法。

 

有幾種常用的探查序列的方法:

 

①線性探查

 

dii=1,2,3,…,m-1;這種方法的特點是:衝突發生時,順序查看錶中下一單元,直到找出一個空單元或查遍全表。

 

(使用例子:ThreadLocal裏面的ThreadLocalMap)

 

②二次探查

 

di=12,-12,22,-22,…,k2,-k2    ( k<=m/2 );這種方法的特點是:衝突發生時,在表的左右進行跳躍式探測,比較靈活。

 

③ 僞隨機探測

 

di=僞隨機數序列;具體實現時,應建立一個僞隨機數發生器,(如i=(i+p) % m),生成一個位隨機序列,並給定一個隨機數做起點,每次去加上這個僞隨機數++就可以了。

 

(3)再散列法

 

再散列法其實很簡單,就是再使用哈希函數去散列一個輸入的時候,輸出是同一個位置就再次散列,直至不發生衝突位置

 

缺點:每次衝突都要重新散列,計算時間增加。

 

 

7、forward(轉發)和redirect(重定向)有什麼區別

 

forward和redirect是什麼?

是servlet種的兩種主要的跳轉方式。forward又叫轉發,redirect叫做重定向。

 

forward(轉發):

1.是服務器內部的重定向,服務器直接訪問目標地址的 url網址,把裏面的東西讀取出來,但是客戶端並不知道,因此用forward的話,客戶端瀏覽器的網址是不會發生變化的。

2.關於request: 由於在整個定向的過程中用的是同一個request,因此forward會將request的信息帶到被重定向的jsp或者servlet中使用。

 

redirect(重定向):

1.是客戶端的重定向,是完全的跳轉。即服務器返回的一個url給客戶端瀏覽器,然後客戶端瀏覽器會重新發送一次請求,到新的url裏面,因此瀏覽器中顯示的url網址會發生變化。

2.因爲這種方式比forward多了一次網絡請求,因此效率會低於forward。

 

 

 

8、set、list、map

List,Set,Map是否繼承自Collection接口? 

答:List,Set是,Map不是。 Collection是最基本的集合接口,一個Collection代表一組Object,即Collection的元素。一些Collection允許相同的元素而另一些不行。一些能排序而另一些不行。Java JDK不能提供直接繼承自Collection的類,Java JDK提供的類都是繼承自Collection的"子接口",如:List和Set。 

注意:Map沒有繼承Collection接口,Map提供key到value的映射。一個Map中不能包含相同key,每個key只能映射一個value。Map接口提供3種集合的視圖,Map的內容可以被當做一組key集合,一組value集合,或者一組key-value映射。 

List按對象進入的順序保存對象,不做排序或編輯操作。Set對每個對象只接受一次,並使用自己內部的排序方法(通常,你只關心某個元素是否屬於Set,而不關心它的順序--否則應該使用List)。Map同樣對每個元素保存一份,但這是基於"鍵"的,Map也有內置的排序,因而不關心元素添加的順序。如果添加元素的順序對你很重要,應該使用 LinkedHashSet或者LinkedHashMap.

 

詳細介紹: 

List特點:元素有放入順序,元素可重複 

Map特點:元素按鍵值對存儲,無放入順序 

Set特點:元素無放入順序,元素不可重複(注意:元素雖然無放入順序,但是元素在set中的位置是有該元素的HashCode決定的,其位置其實是固定的) 

List接口有三個實現類:LinkedList,ArrayList,Vector 

LinkedList:底層基於鏈表實現,鏈表內存是散亂的,每一個元素存儲本身內存地址的同時還存儲下一個元素的地址。鏈表增刪快,查找慢 

ArrayList和Vector的區別:ArrayList是非線程安全的,效率高;Vector是基於線程安全的,效率低 

Set接口有兩個實現類:HashSet(底層由HashMap實現),LinkedHashSet 

SortedSet接口有一個實現類:TreeSet(底層由平衡二叉樹實現) 

Query接口有一個實現類:LinkList 

Map接口有三個實現類:HashMap,HashTable,LinkeHashMap 

  HashMap非線程安全,高效,支持null;HashTable線程安全,低效,不支持null 

SortedMap有一個實現類:TreeMap 

其實最主要的是,list是用來處理序列的,而set是用來處理集的。Map是知道的,存儲的是鍵值對 

set 一般無序不重複.map kv 結構 list 有序 。

 

 

List的功能方法

 

  實際上有兩種List: 一種是基本的ArrayList,其優點在於隨機訪問元素,另一種是更強大的LinkedList,它並不是爲快速隨機訪問設計的,而是具有一套更通用的方法。

 

List : 次序是List最重要的特點:它保證維護元素特定的順序。List爲Collection添加了許多方法,使得能夠向List中間插入與移除元素(這隻推薦LinkedList使用。)一個List可以生成ListIterator,使用它可以從兩個方向遍歷List,也可以從List中間插入和移除元素。

 

ArrayList : 由數組實現的List。允許對元素進行快速隨機訪問,但是向List中間插入與移除元素的速度很慢。ListIterator只應該用來由後向前遍歷ArrayList,而不是用來插入和移除元素。因爲那比LinkedList開銷要大很多。

 

LinkedList : 對順序訪問進行了優化,向List中間插入與刪除的開銷並不大。隨機訪問則相對較慢。(使用ArrayList代替。)還具有下列方法:addFirst(), addLast(), getFirst(), getLast(), removeFirst() 和 removeLast(), 這些方法 (沒有在任何接口或基類中定義過)使得LinkedList可以當作堆棧、隊列和雙向隊列使用。

 

Set的功能方法

 

Set具有與Collection完全一樣的接口,因此沒有任何額外的功能,不像前面有兩個不同的List。實際上Set就是Collection,只是行爲不同。(這是繼承與多態思想的典型應用:表現不同的行爲。)Set不保存重複的元素(至於如何判斷元素相同則較爲負責)

 

Set : 存入Set的每個元素都必須是唯一的,因爲Set不保存重複元素。加入Set的元素必須定義equals()方法以確保對象的唯一性。Set與Collection有完全一樣的接口。Set接口不保證維護元素的次序。

 

HashSet : 爲快速查找設計的Set。存入HashSet的對象必須定義hashCode()。

 

TreeSet : 保存次序的Set, 底層爲樹結構。使用它可以從Set中提取有序的序列。

 

LinkedHashSet : 具有HashSet的查詢速度,且內部使用鏈表維護元素的順序(插入的次序)。於是在使用迭代器遍歷Set時,結果會按元素插入的次序顯示。

 

Map的功能方法

 

  方法put(Object key, Object value)添加一個“值”(想要得東西)和與“值”相關聯的“鍵”(key)(使用它來查找)。方法get(Object key)返回與給定“鍵”相關聯的“值”。可以用containsKey()和containsValue()測試Map中是否包含某個“鍵”或“值”。標準的Java類庫中包含了幾種不同的Map:HashMap, TreeMap, LinkedHashMap, WeakHashMap, IdentityHashMap。它們都有同樣的基本接口Map,但是行爲、效率、排序策略、保存對象的生命週期和判定“鍵”等價的策略等各不相同。

 

  執行效率是Map的一個大問題。看看get()要做哪些事,就會明白爲什麼在ArrayList中搜索“鍵”是相當慢的。而這正是HashMap提高速度的地方。HashMap使用了特殊的值,稱爲“散列碼”(hash code),來取代對鍵的緩慢搜索。“散列碼”是“相對唯一”用以代表對象的int值,它是通過將該對象的某些信息進行轉換而生成的。所有Java對象都能產生散列碼,因爲hashCode()是定義在基類Object中的方法。

 

HashMap就是使用對象的hashCode()進行快速查詢的。此方法能夠顯著提高性能。

 

Map : 維護“鍵值對”的關聯性,使你可以通過“鍵”查找“值”

 

HashMap : Map基於散列表的實現。插入和查詢“鍵值對”的開銷是固定的。可以通過構造器設置容量capacity和負載因子load factor,以調整容器的性能。

 

LinkedHashMap : 類似於HashMap,但是迭代遍歷它時,取得“鍵值對”的順序是其插入次序,或者是最近最少使用(LRU)的次序。只比HashMap慢一點。而在迭代訪問時發而更快,因爲它使用鏈表維護內部次序。

 

TreeMap : 基於紅黑樹數據結構的實現。查看“鍵”或“鍵值對”時,它們會被排序(次序由Comparabel或Comparator決定)。TreeMap的特點在於,你得到的結果是經過排序的。TreeMap是唯一的帶有subMap()方法的Map,它可以返回一個子樹。

 

WeakHashMao : 弱鍵(weak key)Map,Map中使用的對象也被允許釋放: 這是爲解決特殊問題設計的。如果沒有map之外的引用指向某個“鍵”,則此“鍵”可以被垃圾收集器回收。

 

IdentifyHashMap : 使用==代替equals()對“鍵”作比較的hash map。專爲解決特殊問題而設計。

 

 

 

  1. HashMap、Hashtable、ConcurrentHashMap的原理與區別

如果你去面試,面試官不問你這個問題,你來找我^_^

下面直接來乾貨,先說這三個Map的區別:

HashTable

  • 底層數組+鏈表實現,無論key還是value都不能爲null,線程安全,實現線程安全的方式是在修改數據時鎖住整個HashTable,效率低,ConcurrentHashMap做了相關優化
  • 初始size爲11,擴容:newsize = olesize*2+1
  • 計算index的方法:index = (hash & 0x7FFFFFFF) % tab.length

HashMap

  • 底層數組+鏈表實現,可以存儲null鍵和null值,線程不安全
  • 初始size爲16,擴容:newsize = oldsize*2,size一定爲2的n次冪
  • 擴容針對整個Map,每次擴容時,原來數組中的元素依次重新計算存放位置,並重新插入
  • 插入元素後才判斷該不該擴容,有可能無效擴容(插入後如果擴容,如果沒有再次插入,就會產生無效擴容)
  • 當Map中元素總數超過Entry數組的75%,觸發擴容操作,爲了減少鏈表長度,元素分配更均勻
  • 計算index方法:index = hash & (tab.length – 1)

 

HashMap的初始值還要考慮加載因子:

  •  哈希衝突:若干Key的哈希值按數組大小取模後,如果落在同一個數組下標上,將組成一條Entry鏈,對Key的查找需要遍歷Entry鏈上的每個元素執行equals()比較。
  • 加載因子:爲了降低哈希衝突的概率,默認當HashMap中的鍵值對達到數組大小的75%時,即會觸發擴容。因此,如果預估容量是100,即需要設定100/0.75=134的數組大小。
  • 空間換時間:如果希望加快Key查找的時間,還可以進一步降低加載因子,加大初始大小,以降低哈希衝突的概率。

HashMap和Hashtable都是用hash算法來決定其元素的存儲,因此HashMap和Hashtable的hash表包含如下屬性:

  • 容量(capacity):hash表中桶的數量
  • 初始化容量(initial capacity):創建hash表時桶的數量,HashMap允許在構造器中指定初始化容量
  • 尺寸(size):當前hash表中記錄的數量
  • 負載因子(load factor):負載因子等於“size/capacity”。負載因子爲0,表示空的hash表,0.5表示半滿的散列表,依此類推。輕負載的散列表具有衝突少、適宜插入與查詢的特點(但是使用Iterator迭代元素時比較慢)

除此之外,hash表裏還有一個“負載極限”,“負載極限”是一個0~1的數值,“負載極限”決定了hash表的最大填滿程度。當hash表中的負載因子達到指定的“負載極限”時,hash表會自動成倍地增加容量(桶的數量),並將原有的對象重新分配,放入新的桶內,這稱爲rehashing。

HashMap和Hashtable的構造器允許指定一個負載極限,HashMap和Hashtable默認的“負載極限”爲0.75,這表明當該hash表的3/4已經被填滿時,hash表會發生rehashing。

“負載極限”的默認值(0.75)是時間和空間成本上的一種折中:

  • 較高的“負載極限”可以降低hash表所佔用的內存空間,但會增加查詢數據的時間開銷,而查詢是最頻繁的操作(HashMap的get()與put()方法都要用到查詢)
  • 較低的“負載極限”會提高查詢數據的性能,但會增加hash表所佔用的內存開銷

程序猿可以根據實際情況來調整“負載極限”值。

ConcurrentHashMap

  • 底層採用分段的數組+鏈表實現,線程安全
  • 通過把整個Map分爲N個Segment,可以提供相同的線程安全,但是效率提升N倍,默認提升16倍。(讀操作不加鎖,由於HashEntry的value變量是 volatile的,也能保證讀取到最新的值。)
  • Hashtable的synchronized是針對整張Hash表的,即每次鎖住整張表讓線程獨佔,ConcurrentHashMap允許多個修改操作併發進行,其關鍵在於使用了鎖分離技術
  • 有些方法需要跨段,比如size()和containsValue(),它們可能需要鎖定整個表而而不僅僅是某個段,這需要按順序鎖定所有段,操作完畢後,又按順序釋放所有段的鎖
  • 擴容:段內擴容(段內元素超過該段對應Entry數組長度的75%觸發擴容,不會對整個Map進行擴容),插入前檢測需不需要擴容,有效避免無效擴容

 

Hashtable和HashMap都實現了Map接口,但是Hashtable的實現是基於Dictionary抽象類的。Java5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的擴展性更好。

HashMap基於哈希思想,實現對數據的讀寫。當我們將鍵值對傳遞給put()方法時,它調用鍵對象的hashCode()方法來計算hashcode,然後找到bucket位置來存儲值對象。當獲取對象時,通過鍵對象的equals()方法找到正確的鍵值對,然後返回值對象。HashMap使用鏈表來解決碰撞問題,當發生碰撞時,對象將會儲存在鏈表的下一個節點中。HashMap在每個鏈表節點中儲存鍵值對對象。當兩個不同的鍵對象的hashcode相同時,它們會儲存在同一個bucket位置的鏈表中,可通過鍵對象的equals()方法來找到鍵值對。如果鏈表大小超過閾值(TREEIFY_THRESHOLD,8),鏈表就會被改造爲樹形結構。

在HashMap中,null可以作爲鍵,這樣的鍵只有一個,但可以有一個或多個鍵所對應的值爲null。當get()方法返回null值時,即可以表示HashMap中沒有該key,也可以表示該key所對應的value爲null。因此,在HashMap中不能由get()方法來判斷HashMap中是否存在某個key,應該用containsKey()方法來判斷。而在Hashtable中,無論是key還是value都不能爲null。

Hashtable是線程安全的,它的方法是同步的,可以直接用在多線程環境中。而HashMap則不是線程安全的,在多線程環境中,需要手動實現同步機制。

Hashtable與HashMap另一個區別是HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。所以當有其它線程改變了HashMap的結構(增加或者移除元素),將會拋出ConcurrentModificationException,但迭代器本身的remove()方法移除元素則不會拋出ConcurrentModificationException異常。但這並不是一個一定發生的行爲,要看JVM。

先看一下簡單的類圖:

  

從類圖中可以看出來在存儲結構中ConcurrentHashMap比HashMap多出了一個類Segment,而Segment是一個可重入鎖。

ConcurrentHashMap是使用了鎖分段技術來保證線程安全的。

鎖分段技術:首先將數據分成一段一段的存儲,然後給每一段數據配一把鎖,當一個線程佔用鎖訪問其中一個段數據的時候,其他段的數據也能被其他線程訪問。 

ConcurrentHashMap提供了與Hashtable和SynchronizedMap不同的鎖機制。Hashtable中採用的鎖機制是一次鎖住整個hash表,從而在同一時刻只能由一個線程對其進行操作;而ConcurrentHashMap中則是一次鎖住一個桶。

ConcurrentHashMap默認將hash表分爲16個桶,諸如get、put、remove等常用操作只鎖住當前需要用到的桶。這樣,原來只能一個線程進入,現在卻能同時有16個寫線程執行,併發性能的提升是顯而易見的。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章