1、set集合從原理上如何保證不重復(fù)
1)在往set中添加元素時(shí),如果指定元素不存在,則添加成功。也就是說(shuō),如果set中不存在(e==null ? e1==null : e.queals(e1))的元素e1,則e1能添加到set中。
2)具體來(lái)講:當(dāng)向HashSet中添加元素的時(shí)候,首先計(jì)算元素的hashcode值,然后用這個(gè)(元素的hashcode)%(HashMap集合的大小)+1計(jì)算出這個(gè)元素的存儲(chǔ)位置,如果這個(gè)位置位空,就將元素添加進(jìn)去;如果不為空,則用equals方法比較元素是否相等,相等就不添加,否則找一個(gè)空位添加。
2、HashMap和HashTable的主要區(qū)別是什么?,兩者底層實(shí)現(xiàn)的數(shù)據(jù)結(jié)構(gòu)是什么?
HashMap:
HashMap是非線程安全的,只是用于單線程環(huán)境下,多線程環(huán)境下可以采用concurrent并發(fā)包下的concurrentHashMap。
HashMap?實(shí)現(xiàn)了Serializable接口,因此它支持序列化,實(shí)現(xiàn)了Cloneable接口,能被克隆。
HashMap存數(shù)據(jù)的過(guò)程是:
HashMap內(nèi)部維護(hù)了一個(gè)存儲(chǔ)數(shù)據(jù)的Entry數(shù)組,HashMap采用鏈表解決沖突,每一個(gè)Entry本質(zhì)上是一個(gè)單向鏈表。當(dāng)準(zhǔn)備添加一個(gè)key-value對(duì)時(shí),首先通過(guò)hash(key)方法計(jì)算hash值,然后通過(guò)indexFor(hash,length)求該key-value對(duì)的存儲(chǔ)位置,計(jì)算方法是先用hash(key)位運(yùn)算length,這就保證每一個(gè)key-value對(duì)都能存入HashMap中,當(dāng)計(jì)算出的位置相同時(shí),由于存入位置是一個(gè)鏈表,則把這個(gè)key-value對(duì)插入鏈表頭。
HashMap中key和value都允許為null。key為null的鍵值對(duì)永遠(yuǎn)都放在以table[0]為頭結(jié)點(diǎn)的鏈表中。HashMap內(nèi)存儲(chǔ)數(shù)據(jù)的Entry數(shù)組默認(rèn)是16.HashMap內(nèi)部擴(kuò)容機(jī)制:
變量size,它記錄HashMap的底層數(shù)組中已用槽的數(shù)量;
變量threshold,它是HashMap的閾值,用于判斷是否需要調(diào)整HashMap的容量(threshold?=?容量*加載因子)
變量DEFAULT_LOAD_FACTOR=?0.75f,默認(rèn)加載因子為0.75
HashMap擴(kuò)容的條件是:當(dāng)size大于threshold時(shí),對(duì)HashMap進(jìn)行擴(kuò)容
擴(kuò)容是是新建了一個(gè)HashMap的底層數(shù)組,而后調(diào)用transfer方法,將就HashMap的全部元素添加到新的HashMap中(要重新計(jì)算元素在新的數(shù)組中的索引位置)。?很明顯,擴(kuò)容是一個(gè)相當(dāng)耗時(shí)的操作,因?yàn)樗枰匦掠?jì)算這些元素在新的數(shù)組中的位置并進(jìn)行復(fù)制處理。因此,我們?cè)谟肏ashMap的時(shí),最好能提前預(yù)估下HashMap中元素的個(gè)數(shù),這樣有助于提高HashMap的性能。擴(kuò)容時(shí)可能會(huì)出現(xiàn)無(wú)限循環(huán)問(wèn)題。
HashMap共有四個(gè)構(gòu)造方法。構(gòu)造方法中提到了兩個(gè)很重要的參數(shù):初始容量和加載因子。這兩個(gè)參數(shù)是影響HashMap性能的重要參數(shù),其中容量表示哈希表中槽的數(shù)量(即哈希數(shù)組的長(zhǎng)度),初始容量是創(chuàng)建哈希表時(shí)的容量(從構(gòu)造函數(shù)中可以看出,如果不指明,則默認(rèn)為16),加載因子是哈希表在其容量自動(dòng)增加之前可以達(dá)到多滿的一種尺度,當(dāng)哈希表中的條目數(shù)超出了加載因子與當(dāng)前容量的乘積時(shí),則要對(duì)該哈希表進(jìn)行 resize 操作(即擴(kuò)容)。
HashTable:
Hashtable同樣是基于哈希表實(shí)現(xiàn)的,同樣每個(gè)元素是一個(gè)key-value對(duì),其內(nèi)部也是通過(guò)單鏈表解決沖突問(wèn)題,容量不足(超過(guò)了閥值)時(shí),同樣會(huì)自動(dòng)增長(zhǎng)。
Hashtable也是JDK1.0引入的類(lèi),是線程安全的,能用于多線程環(huán)境中。
Hashtable同樣實(shí)現(xiàn)了Serializable接口,它支持序列化,實(shí)現(xiàn)了Cloneable接口,能被克隆。
HashMap和HashTable的區(qū)別:
二者都實(shí)現(xiàn)了Map 接口,是將惟一鍵映射到特定的值上;主要區(qū)別在于:
1)HashMap 沒(méi)有排序,允許一個(gè)null 鍵和多個(gè)null 值,而Hashtable 不允許,若有會(huì)報(bào)空指針異常;
2)HashMap 把Hashtable 的contains 方法去掉了,改成containsvalue 和
containsKey,因?yàn)閏ontains 方法容易讓人引起誤解;
3)Hashtable繼承自Dictionary類(lèi),而HashMap繼承自AbstractMap類(lèi)。但二者都實(shí)現(xiàn)了Map接口。
4)Hashtable 的方法是Synchronize 的,而HashMap 不是,在多個(gè)線程訪問(wèn)Hashtable 時(shí),不需要自己為它的方法實(shí)現(xiàn)同步,而HashMap 就必須為之提供外同步。Hashtable 和HashMap 采用的hash/rehash 算法大致一樣,所以性能不會(huì)有很大的差異。
hashmap線程不安全可能:put方法、addEntry方法、刪除方法
5)Hashtable、HashMap都使用了 Iterator。而由于歷史原因,Hashtable還使用了Enumeration的方式 。
6)HashMap和哈希值的使用不同,HashTable直接使用對(duì)象的hashCode。而HashMap重新計(jì)算hash值。
hashCode是jdk根據(jù)對(duì)象的地址或者字符串或者數(shù)字算出來(lái)的int類(lèi)型的數(shù)值。
Hashtable計(jì)算hash值,直接用key的hashCode(),而HashMap重新計(jì)算了key的hash值,Hashtable在求hash值對(duì)應(yīng)的位置索引時(shí),用取模運(yùn)算,而HashMap在求位置索引時(shí),則用與運(yùn)算,且這里一般先用hash&0x7FFFFFFF后,再對(duì)length取模,&0x7FFFFFFF的目的是為了將負(fù)的hash值轉(zhuǎn)化為正值,因?yàn)閔ash值有可能為負(fù)數(shù),而&0x7FFFFFFF后,只有符號(hào)外改變,而后面的位都不變。
8)HashTable在不指定容量的情況下的默認(rèn)容量為11,而HashMap為16,Hashtable不要求底層數(shù)組的容量一定要為2的整數(shù)次冪,而HashMap則要求一定為2的整數(shù)次冪。
Hashtable擴(kuò)容時(shí),將容量變?yōu)樵瓉?lái)的2倍加1,而HashMap擴(kuò)容時(shí),將容量變?yōu)樵瓉?lái)的2倍。
Hashtable和HashMap它們兩個(gè)內(nèi)部實(shí)現(xiàn)方式的數(shù)組的初始大小和擴(kuò)容的方式。HashTable中hash數(shù)組默認(rèn)大小是11,增加的方式是 old*2+1。
HashTable的底層實(shí)現(xiàn)數(shù)據(jù)結(jié)構(gòu):
HashMap和Hashtable的底層實(shí)現(xiàn)都是數(shù)組+鏈表結(jié)構(gòu)實(shí)現(xiàn)的。
JDK1.8中,HashMap采用數(shù)組+鏈表+紅黑樹(shù)實(shí)現(xiàn),當(dāng)鏈表長(zhǎng)度超過(guò)閾值(8)時(shí),將鏈表轉(zhuǎn)換為紅黑樹(shù),這樣大大減少了查找時(shí)間。
3、HashMap何時(shí)擴(kuò)容,擴(kuò)容的算法是什么?
HashMap何時(shí)擴(kuò)容:
當(dāng)向容器添加元素的時(shí)候,會(huì)判斷當(dāng)前容器的元素個(gè)數(shù),如果大于等于閾值---即當(dāng)前數(shù)組的長(zhǎng)度乘以加載因子的值的時(shí)候,就要自動(dòng)擴(kuò)容
擴(kuò)容的算法是什么:
擴(kuò)容(resize)就是重新計(jì)算容量,向HashMap對(duì)象里不停的添加元素,而HashMap對(duì)象內(nèi)部的數(shù)組無(wú)法裝載更多的元素時(shí),對(duì)象就需要擴(kuò)大數(shù)組的長(zhǎng)度,以便能裝入更多的元素。當(dāng)然Java里的數(shù)組是無(wú)法自動(dòng)擴(kuò)容的,方法是使用一個(gè)新的數(shù)組代替已有的容量小的數(shù)組


4、Java的虛擬機(jī)JVM的兩個(gè)內(nèi)存:棧內(nèi)存和堆內(nèi)存的區(qū)別是什么?
Java把內(nèi)存劃分成兩種:一種是棧內(nèi)存,一種是堆內(nèi)存。兩者的區(qū)別是:
1)棧內(nèi)存:在函數(shù)中定義的一些基本類(lèi)型的變量和對(duì)象的引用變量都在函數(shù)的棧內(nèi)存中分配。 當(dāng)在一段代碼塊定義一個(gè)變量時(shí),Java就在棧中為這個(gè)變量分配內(nèi)存空間,當(dāng)超過(guò)變量的作用域后,Java會(huì)自動(dòng)釋放掉為該變量所分配的內(nèi)存空間,該內(nèi)存空間可以立即被另作他用。
2)堆內(nèi)存:堆內(nèi)存用來(lái)存放由new創(chuàng)建的對(duì)象和數(shù)組。在堆中分配的內(nèi)存,由Java虛擬機(jī)的自動(dòng)垃圾回收器來(lái)管理。
5、Java中對(duì)異常是如何進(jìn)行分類(lèi)的?
Throwable分為兩個(gè)分支:Error和Exception
一、Error
1、Error類(lèi)層次描述了Java運(yùn)行時(shí)系統(tǒng)的內(nèi)部錯(cuò)誤和資源耗盡錯(cuò)誤。對(duì)于這類(lèi)錯(cuò)誤是無(wú)法難通過(guò)程序來(lái)解決的,所以程序不應(yīng)該拋出這種類(lèi)型的對(duì)象。如果出現(xiàn)了這樣的內(nèi)部錯(cuò)誤,除了通知給用戶,并盡力使程序安全地終止。當(dāng)然這類(lèi)情況是很少出現(xiàn)的
二、Exception
在設(shè)計(jì)Java程序時(shí),Exception是我們需要重點(diǎn)關(guān)注的錯(cuò)誤,在Java程序中Exception又可以分為兩類(lèi):Runtime Exception、其它異常如:IOException。
1、派生于Runtime Exception 的異常主要包含以下幾種情況
錯(cuò)誤的類(lèi)型轉(zhuǎn)換
數(shù)組訪問(wèn)越界
訪問(wèn)空指針
2、不是派生于Runtime Exception的異常包含
試圖在文件尾部讀取數(shù)據(jù)
試圖打開(kāi)一個(gè)錯(cuò)誤格式的URL
試圖根據(jù)給定的字符串查找Class對(duì)象,而這個(gè)字符串表示的類(lèi)并不存在
運(yùn)行時(shí)異常和檢查性異常
1、在Java語(yǔ)言中將派生于Error或RuntimeException類(lèi)的所有異常稱為運(yùn)行時(shí)異常,這類(lèi)異常我們可以不處理,當(dāng)出現(xiàn)這個(gè)的異常時(shí),總是由虛擬機(jī)接管,比如:我們從來(lái)沒(méi)有人去處理過(guò)NullPointerException異常,它就是運(yùn)行時(shí)異常,并且這種異常還是最常見(jiàn)的異常之一。運(yùn)行時(shí)異常我們常見(jiàn)的有ClassCastException(類(lèi)轉(zhuǎn)換異常)、IndexOutOfBoundsException(數(shù)組越界)、NullPointerException(空指針)、ArrayStoreException(數(shù)據(jù)存儲(chǔ)異常,操作數(shù)組時(shí)類(lèi)型不一致)、還有IO操作的BufferOverflowException異常;
2、除了運(yùn)行時(shí)異常,其它的異常都被稱為檢查性異常,我們經(jīng)常遇到的IO異常及sql異常就屬于檢查式異常。對(duì)于這種異常,Java編譯器要求我們必須對(duì)出現(xiàn)的這些異常進(jìn)行catch 所以 面對(duì)這種異常不管我們是否愿意,只能自己去寫(xiě)一堆catch來(lái)捕捉這些異常。常見(jiàn)的查檢性異常有:FileNotFoundException 文件不存在異常、SQLException SQL異常等。
一、使用throws拋出異常
二、使用try{....}catch(){....}finally{....}捕捉異常
6、數(shù)據(jù)庫(kù)設(shè)計(jì)中常講的三范式是指什么?
1)第一范式1NF(域的原子性)
如果數(shù)據(jù)庫(kù)表中的所有字段值都是不可分解的原子值,就說(shuō)明該數(shù)據(jù)庫(kù)表滿足了第一范式
2)第二范式2NF(表中除主鍵外的字段都完全依賴主鍵)
第二范式是在第一范式基礎(chǔ)上建立的。第二范式有兩個(gè)重點(diǎn):(1)表中必須有主鍵;(2)其他非主屬性必須完全依賴主鍵,不能只依賴主鍵的一部分(主要針對(duì)聯(lián)合主鍵而言)。
3)第三范式3NF(表中除主鍵外的字段都完全直接依賴,不能是傳遞依賴)
不能是傳遞依賴,即不能存在:非主鍵列 A 依賴于非主鍵列 B,非主鍵列 B 依賴于主鍵的情況。第二范式和第三范式區(qū)分的關(guān)鍵點(diǎn):2NF:非主鍵列是否完全依賴于主鍵,還是依賴于主鍵的一部分;3NF:非主鍵列是直接依賴于主鍵,還是直接依賴于非主鍵列。
7、Java中的線程池共有幾種?
Java四種線程池
第一種:newCachedThreadPool
創(chuàng)建一個(gè)可根據(jù)需要?jiǎng)?chuàng)建新線程的線程池,但是在以前構(gòu)造的線程可用時(shí)將重用它們。
第二種:newFixedThreadPool
創(chuàng)建一個(gè)指定工作線程數(shù)量的線程池
第三種:newScheduledThreadPool
創(chuàng)建一個(gè)線程池,它可安排在給定延遲后運(yùn)行命令或者定期地執(zhí)行。
第四種:newSingleThreadExecutor
創(chuàng)建一個(gè)使用單個(gè) worker 線程的 Executor,以無(wú)界隊(duì)列方式來(lái)運(yùn)行該線程。
8、volatile和synchronized區(qū)別
volatile和synchronized簡(jiǎn)介:
在Java中,為了保證多線程讀寫(xiě)數(shù)據(jù)時(shí)保證數(shù)據(jù)的一致性,可以采用兩種方式:
1)使用synchronized關(guān)鍵字
2)使用volatile關(guān)鍵字:用一句話概括volatile,它能夠使變量在值發(fā)生改變時(shí)能盡快地讓其他線程知道。
兩者的區(qū)別:
1)volatile本質(zhì)是在告訴jvm當(dāng)前變量在寄存器中的值是不確定的,需要從主存中讀取,synchronized則是鎖定當(dāng)前變量,只有當(dāng)前線程可以訪問(wèn)該變量,其他線程被阻塞住.
2)volatile僅能使用在變量級(jí)別,synchronized則可以使用在變量,方法.
3)volatile僅能實(shí)現(xiàn)變量的修改可見(jiàn)性,而synchronized則可以保證變量的修改可見(jiàn)性和原子性.
4)volatile不會(huì)造成線程的阻塞,而synchronized可能會(huì)造成線程的阻塞.
兩者原理:
1.synchronized:monitor 監(jiān)視器;monitor enter,monitor exit
2.volatile:內(nèi)存屏障,會(huì)在處理器上加一個(gè)Lock指令
9、Spring的特性
1.方便解耦,簡(jiǎn)化開(kāi)發(fā)
通過(guò)Spring提供的IoC容器,我們可以將對(duì)象之間的依賴關(guān)系交由Spring進(jìn)行控制,避免硬編碼所造成的過(guò)度程序耦合。
2.AOP編程的支持
通過(guò)Spring提供的AOP功能,方便進(jìn)行面向切面的編程。
3.聲明事物的支持
在Spring中,我們可以從單調(diào)煩悶的事務(wù)管理代碼中解脫出來(lái),通過(guò)聲明式方式靈活地進(jìn)行事務(wù)的管理,提高開(kāi)發(fā)效率和質(zhì)量。
4.方便程序的測(cè)試
可以用非容器依賴的編程方式進(jìn)行幾乎所有的測(cè)試工作。例如:Spring對(duì)Junit4支持,可以通過(guò)注解方便的測(cè)試Spring程序。
5.方便集成各種優(yōu)秀框架
Spring不排斥各種優(yōu)秀的開(kāi)源框架,相反,Spring可以降低各種框架的使用難度,Spring提供了對(duì)各種優(yōu)秀框架(如Struts,Hibernate、Hessian、Quartz)等的直接支持。
6.降低Java EE API的使用難度
Spring對(duì)很多難用的Java EE API(如JDBC,JavaMail,遠(yuǎn)程調(diào)用等)提供了一個(gè)薄薄的封裝層,通過(guò)Spring的簡(jiǎn)易封裝,這些Java EE API的使用難度大為降低。
10、spring aop的應(yīng)用場(chǎng)景:
AOP用來(lái)封裝橫切關(guān)注點(diǎn),具體可以在下面的場(chǎng)景中使用
Authentication 權(quán)限
Caching 緩存
Context passing 內(nèi)容傳遞
Error handling 錯(cuò)誤處理
Lazy loading 懶加載
Debugging 調(diào)試
logging, tracing, profiling and monitoring 記錄跟蹤 優(yōu)化 校準(zhǔn)
Performance optimization 性能優(yōu)化
Persistence 持久化
Resource pooling 資源池
Synchronization 同步
Transactions 事務(wù)
11、Mybaits中#和$區(qū)別
1)${}是Properties文件中的變量占位符,它可以用于標(biāo)簽屬性值和sql內(nèi)部,屬于靜態(tài)文本替換,比如${driver}會(huì)被靜態(tài)替換為com.mysql.jdbc.Driver。
2)#{}是sql的參數(shù)占位符,Mybatis會(huì)將sql中的#{}替換為?號(hào),在sql執(zhí)行前會(huì)使用PreparedStatement的參數(shù)設(shè)置方法,按序給sql的?號(hào)占位符設(shè)置參數(shù)值,比如ps.setInt(0, parameterValue),#{item.name}的取值方式為使用反射從參數(shù)對(duì)象中獲取item對(duì)象的name屬性值,相當(dāng)于param.getItem().getName()。
12、排序都有哪幾種方法?請(qǐng)列舉。用JAVA 實(shí)現(xiàn)一個(gè)快速排序。
排序的方法有:
插入排序(直接插入排序、希爾排序),交換排序(冒泡排序、快速排序),選擇排序(直接選擇排序、堆排序),歸并排序,分配排序(箱排序、基數(shù)排序);
快速排序的偽代碼:
//使用快速排序方法對(duì)a[ 0 :n- 1 ]排序
從a[ 0 :n- 1 ]中選擇一個(gè)元素作為middle,該元素為支點(diǎn);
把余下的元素分割為兩段left 和right,使得left 中的元素都小于等于支點(diǎn),
而right 中的元素都大于等于支點(diǎn);
遞歸地使用快速排序方法對(duì)left 進(jìn)行排序;
遞歸地使用快速排序方法對(duì)right 進(jìn)行排序;
所得結(jié)果為left + middle + right。
快速排序的Java代碼實(shí)現(xiàn)如下:
