目錄##
0.Set,List,Map的區(qū)別
1.Vector 與 Array 的區(qū)別
2.HashMap 與 Hashtable 的區(qū)別
3.String 和 StringBuilder 的區(qū)別
4.32 位 JVM 和 64 JVM的區(qū)別
5.transient與volatile
6.equals()和hashCode()區(qū)別
7.final
0.Set,List,Map的區(qū)別
數(shù)組是大小固定的,并且同一個數(shù)組只能存放類型一樣的數(shù)據(jù)(基本類型/引用類型)
JAVA集合可以存儲和操作數(shù)目不固定的一組數(shù)據(jù)。所有的JAVA集合都位于 java.util包中.
JAVA集合只能存放引用類型的的數(shù)據(jù),不能存放基本數(shù)據(jù)類型。
一:數(shù)組聲明了它容納的元素的類型,而集合不聲明。這是由于集合以object形式來存儲它們的元素。
二:一個數(shù)組實例具有固定的大小,不能伸縮。集合則可根據(jù)需要動態(tài)改變大小。
三:數(shù)組是一種可讀/可寫數(shù)據(jù)結(jié)構(gòu)---沒有辦法創(chuàng)建一個只讀數(shù)組。然而可以使用集合提供的ReadOnly方法,以只讀方式來使用集合。該方法將返回一個集合的只讀版本。
集合分類
--Collection:List、Set
--Map:HashMap、HashTable、TreeMap
1.Vector 與 Array 的區(qū)別
(1)Vector與數(shù)組最大區(qū)別在于,數(shù)組對象創(chuàng)建之后長度就不能改變了,而Vector的存儲空間可擴充。但注意,Vector存儲類型必須是引用類型。
(2)Vector
Vector的聲明格式一般是:Vector<類型> 變量名;
vector是通過封裝數(shù)組實現(xiàn)的,大小動態(tài)的,同時線程安全的;
訪問它比訪問ArrayList慢。
下面是Vector擴展大小源碼(Vector是默認(rèn)擴展1倍):
private void ensureCapacityHelper(int minCapacity){
int oldCapacity = elementData.length;
if (minCapacity > oldCapacity) {
Object[] oldData = elementData;
int newCapacity = (capacityIncrement > 0) ?(oldCapacity + capacityIncrement) : (oldCapacity * 2);
if (newCapacity < minCapacity) {
newCapacity = minCapacity;
}
elementData = Arrays.copyOf(elementData, newCapacity);
}
}
(3)ArrayList
內(nèi)部是通過數(shù)組實現(xiàn)的,它允許對元素進(jìn)行快速隨機訪問。下面是ArrayList擴展大小源碼:
public boolean add(E e) {
ensureCapacity(size + 1); // 增加元素,判斷是否能夠容納。不能的話就要新建數(shù)組
elementData[size++] = e;
return true;
}
public void ensureCapacity(int minCapacity) {
modCount++;
int oldCapacity = elementData.length;
if (minCapacity > oldCapacity) {
Object oldData[] = elementData; // 此行沒看出來用處,不知道開發(fā)者出于什么考慮
int newCapacity = (oldCapacity * 3)/2 + 1; // 增加新的數(shù)組的大小
if (newCapacity < minCapacity)
newCapacity = minCapacity;
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
}
(4)LinkedList
用鏈表結(jié)構(gòu)存儲數(shù)據(jù)的,很適合數(shù)據(jù)的動態(tài)插入和刪除,隨機訪問和遍歷速度比較慢。
List接口一共有三個實現(xiàn)類,分別是ArrayList、Vector和LinkedList.
2.HashMap 與 Hashtable 的區(qū)別
(1)Hashtable繼承自Dictionary類,而HashMap繼承自AbstractMap類。但二者都實現(xiàn)了Map接口。
(2)Hashtable中的方法是Synchronize的,而HashMap中的方法在缺省情況下是非Synchronize的。在多線程并發(fā)的環(huán)境下,可以直接使用Hashtable,不需要自己為它的方法實現(xiàn)同步,但使用HashMap時就必須要自己增加同步處理。
(3)HashMap把contains方法去掉了,改成containsValue和containsKey
Hashtable則保留了contains,containsValue和containsKey三個方法,其中contains和containsValue功能相同。
(4)Hashtable中,key和value都不允許出現(xiàn)null值。
HashMap中,null可以作為鍵,這樣的鍵只有一個;可以有一個或多個鍵所對應(yīng)的值為null。
(5)Hashtable、HashMap都使用了 Iterator。而由于歷史原因,Hashtable還使用了Enumeration的方式 。
(6)Hashtable和HashMap它們兩個內(nèi)部實現(xiàn)方式的數(shù)組的初始大小和擴容的方式。
HashTable中hash數(shù)組默認(rèn)大小是11,增加的方式是 old*2+1。
HashMap中hash數(shù)組的默認(rèn)大小(DEFAULT_INITIAL_CAPACITY)是16,而且一定是2的指數(shù)。
(7)哈希值的使用不同,HashTable直接使用對象的hashCode,代碼是這樣的:
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
而HashMap重新計算hash值,而且用與代替求模:
int hash = hash(k);
int i = indexFor(hash, table.length);
HashMap的部分源碼:
public V put(K key, V value)
{
if (key == null)
return putForNullKey(value);
// 根據(jù) key 的 keyCode 計算 Hash 值
int hash = hash(key.hashCode());
// 搜索指定 hash 值在對應(yīng) table 中的索引
int i = indexFor(hash, table.length);
// 如果 i 索引處的 Entry 不為 null,通過循環(huán)不斷遍歷 e 元素的下一個元素
for (Entry<K,V> e = table[i]; e != null; e = e.next)
{
Object k;
if (e.hash == hash && ((k = e.key) == key
|| key.equals(k)))
{
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
//hash
final int hash(Object k) {
int h = hashSeed;
if (0 != h && k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}
h ^= k.hashCode();
// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
static int indexFor(int h, int length)
{
return h & (length-1);
}
void addEntry(int hash, K key, V value, int bucketIndex)
{
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
// 如果 Map 中的 key-value 對的數(shù)量超過了極限
if (size++ >= threshold) //
// 把 table 對象的長度擴充到 2 倍。
resize(2 * table.length);
}
public V get(Object key)
{
// 如果 key 是 null,調(diào)用 getForNullKey 取出對應(yīng)的 value
if (key == null)
return getForNullKey();
// 根據(jù)該 key 的 hashCode 值計算它的 hash 碼
int hash = hash(key.hashCode());
// 直接取出 table 數(shù)組中指定索引處的值,
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
// 搜索該 Entry 鏈的下一個 Entr
e = e.next) // ①
{
Object k;
// 如果該 Entry 的 key 與被搜索 key 相同
if (e.hash == hash && ((k = e.key) == key
|| key.equals(k)))
return e.value;
}
return null;
}
// 以指定初始化容量、負(fù)載因子創(chuàng)建 HashMap
public HashMap(int initialCapacity, float loadFactor)
{
// 初始容量不能為負(fù)數(shù)
if (initialCapacity < 0)
throw new IllegalArgumentException(
"Illegal initial capacity: " +
initialCapacity);
// 如果初始容量大于最大容量,讓出示容量
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
// 負(fù)載因子必須大于 0 的數(shù)值
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException(
loadFactor);
// 計算出大于 initialCapacity 的最小的 2 的 n 次方值。
int capacity = 1;
while (capacity < initialCapacity)
capacity <<= 1;
this.loadFactor = loadFactor;
// 設(shè)置容量極限等于容量 * 負(fù)載因子
threshold = (int)(capacity * loadFactor);
// 初始化 table 數(shù)組
table = new Entry[capacity]; // table是一個數(shù)組
init();
}
注:
HashSet :HashSet實現(xiàn)了Set接口,它不允許集合中有重復(fù)的值. 它是基于 HashMap 實現(xiàn)的,HashSet 底層采用 HashMap 來保存所有元素
HashSet 的實現(xiàn)其實非常簡單,它只是封裝了一個 HashMap 對象來存儲所有的集合元素,所有放入 HashSet 中的集合元素實際上由 HashMap 的 key 來保存,而 HashMap 的 value 則存儲了一個 PRESENT,它是一個靜態(tài)的 Object 對象。
public boolean add(Object o)方法用來在Set中添加元素,當(dāng)元素值重復(fù)時則會立即返回false,如果成功添加的話會返回true。
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable
{
// 使用 HashMap 的 key 保存 HashSet 中所有元素
private transient HashMap<E,Object> map;
// 定義一個虛擬的 Object 對象作為 HashMap 的 value
private static final Object PRESENT = new Object();
...
// 初始化 HashSet,底層會初始化一個 HashMap
public HashSet()
{
map = new HashMap<E,Object>();
}
// 以指定的 initialCapacity、loadFactor 創(chuàng)建 HashSet
// 其實就是以相應(yīng)的參數(shù)創(chuàng)建 HashMap
public HashSet(int initialCapacity, float loadFactor)
{
map = new HashMap<E,Object>(initialCapacity, loadFactor);
}
public HashSet(int initialCapacity)
{
map = new HashMap<E,Object>(initialCapacity);
}
HashSet(int initialCapacity, float loadFactor, boolean dummy)
{
map = new LinkedHashMap<E,Object>(initialCapacity
, loadFactor);
}
// 調(diào)用 map 的 keySet 來返回所有的 key
public Iterator<E> iterator()
{
return map.keySet().iterator();
}
// 調(diào)用 HashMap 的 size() 方法返回 Entry 的數(shù)量,就得到該 Set 里元素的個數(shù)
public int size()
{
return map.size();
}
// 調(diào)用 HashMap 的 isEmpty() 判斷該 HashSet 是否為空,
// 當(dāng) HashMap 為空時,對應(yīng)的 HashSet 也為空
public boolean isEmpty()
{
return map.isEmpty();
}
// 調(diào)用 HashMap 的 containsKey 判斷是否包含指定 key
//HashSet 的所有元素就是通過 HashMap 的 key 來保存的
public boolean contains(Object o)
{
return map.containsKey(o);
}
// 將指定元素放入 HashSet 中,也就是將該元素作為 key 放入 HashMap
public boolean add(E e)
{
return map.put(e, PRESENT) == null;
}
// 調(diào)用 HashMap 的 remove 方法刪除指定 Entry,也就刪除了 HashSet 中對應(yīng)的元素
public boolean remove(Object o)
{
return map.remove(o)==PRESENT;
}
// 調(diào)用 Map 的 clear 方法清空所有 Entry,也就清空了 HashSet 中所有元素
public void clear()
{
map.clear();
}
...
}
ConcurrentHashMap
3. String 和 StringBuilder 的區(qū)別
String : 創(chuàng)建字符串常量 ,字符串長度不可變
StringBuilder : 字符串變量 ,線程非安全
StringBuffer:字符串變量,線程安全
對于三者使用的總結(jié): 1.如果要操作少量的數(shù)據(jù)用 = String ;2.單線程操作字符串緩沖區(qū) 下操作大量數(shù)據(jù) = StringBuilder;3.多線程操作字符串緩沖區(qū) 下操作大量數(shù)據(jù) = StringBuffer
不要使用String類的"+"來進(jìn)行頻繁的拼接,因為那樣的性能極差的,應(yīng)該使用StringBuffer或StringBuilder類,這在Java的優(yōu)化上是一條比較重要的原則。
4.32 位 JVM 和 64 JVM的區(qū)別
64位將會多需要30%-50%的堆內(nèi)存。
GC中斷時間更長。
JVM 32bit 和JVM 64bit的區(qū)別如下:
1.目前只有server VM支持64bit JVM,client不支持32bit JVM。
2.The Java Plug-in, AWT Robot and Java Web Start這些組件目前不支持64bit JVM
3.本地代碼的影響:對JNI的編程接口沒有影響,但是針對32-bit VM寫的代碼必須重新編譯才能在64-bit VM工作。
4.32-bit JVM堆大小最大是4G, 64-bit VMs 上, Java堆的大小受限于物理內(nèi)存和操作系統(tǒng)提供的虛擬內(nèi)存。(這里的堆并不嚴(yán)謹(jǐn))
5.線程的默認(rèn)堆棧大?。涸趙indows上32位JVM,默認(rèn)堆棧最大是320k 64-bit JVM是1024K。
6.性能影響:
(1)64bit JVM相比32bit JVM,在大量的內(nèi)存訪問的情況下,其性能損失更少,AMD64和EM64T平臺在64位模式下運行時,Java虛擬機得到了一些額外的寄存器,它可以用來生成更有效的原生指令序列。
(2)性能上,在SPARC 處理器上,當(dāng)一個java應(yīng)用程序從32bit 平臺移植到64bit平臺的64bit JVM會用大約 10-20%的性能損失,而在AMD64和 EM64T平臺上,其性能損失的范圍在0-15%.
參考 : http://java.sun.com/docs/hotspot/HotSpotFAQ.html#64bit_description
5.transient volatile
transient
transient是類型修飾符,只能用來修飾字段。在對象序列化的過程中,標(biāo)記為transient的變量不會被序列化。
當(dāng)類Test的實例對象被序列化(比如將Test類的實例對象 t 寫入硬盤的文本文件t.txt中),變量 a 的內(nèi)容不會被保存,變量 b 的內(nèi)容則會被保存。
參考:
把一個對象的表示轉(zhuǎn)化為字節(jié)流的過程稱為串行化(也稱為序列化,serialization),從字節(jié)流中把對象重建出來稱為反串行化(也稱為為反序列化,deserialization)。
transient為不應(yīng)被串行化的數(shù)據(jù)提供了一個語言級的標(biāo)記數(shù)據(jù)方法。
volatile
volatile也是變量修飾符,只能用來修飾變量。volatile修飾的成員變量在每次被線程訪問時,都強迫從共享內(nèi)存中重讀該成員變量的值。而且,當(dāng)成員變量發(fā)生變化時,強迫線程將變化值回寫到共享內(nèi)存。這樣在任何時刻,兩個不同的線程總是看到某個成員變量的同一個值。
Java語言規(guī)范中指出:為了獲得最佳速度,允許線程保存共享成員變量的私有拷貝,而且只當(dāng)線程進(jìn)入或者離開同步代碼塊時才與共享成員變量的原始值對比。
volatile關(guān)鍵字就是提示VM:對于這個成員變量不能保存它的私有拷貝,而應(yīng)直接與共享成員變量交互。
使用建議:在兩個或者更多的線程訪問的成員變量上使用volatile。當(dāng)要訪問的變量已在synchronized代碼塊中,或者為常量時,不必使用。
由于使用volatile屏蔽掉了VM中必要的代碼優(yōu)化,所以在效率上比較低,因此一定在必要時才使用此關(guān)鍵字。
《java并發(fā)編程實踐》--滿足以下所有標(biāo)準(zhǔn),才能使用volatile(2016.10.25)
1.寫入變量時并不依賴變量當(dāng)前的值;或者能夠保證只有單一的線程修改變量的值
2.變量不需要與其他狀態(tài)變量共同參與不變約束
3.訪問變量時,沒有其他的原因需要加鎖。
6.equals()和hashCode()區(qū)別?
equals():反映的是對象或變量具體的值,即兩個對象里面包含的值--可能是對象的引用,也可能是值類型的值。
hashCode():計算出對象實例的哈希碼,并返回哈希碼,又稱為散列函數(shù)。根類Object的hashCode()方法的計算依賴于對象實例的D(內(nèi)存地址),故每個Object對象的hashCode都是唯一的;當(dāng)然,當(dāng)對象所對應(yīng)的類重寫了hashCode()方法時,結(jié)果就截然不同了。
之所以有hashCode方法,是因為在批量的對象比較中,hashCode要比equals來得快,很多集合都用到了hashCode,比如HashTable。
兩個obj,如果equals()相等,hashCode()一定相等。
兩個obj,如果hashCode()相等,equals()不一定相等(Hash散列值有沖突的情況,雖然概率很低)。
所以:
可以考慮在集合中,判斷兩個對象是否相等的規(guī)則是:
第一步,如果hashCode()相等,則查看第二步,否則不相等;
第二步,查看equals()是否相等,如果相等,則兩obj相等,否則還是不相等。
7.final
(1)修飾類
final修飾的類不能被繼承,該類中的成員方法被隱式的置為final方法。所以說,使用的時候需謹(jǐn)慎。

(2)修飾方法
final修飾方法,是以防任何繼承類修改它的含義。
(3)修飾變量
一個final變量,如果是基本數(shù)據(jù)類型的變量,則其數(shù)值一旦在初始化之后便不能更改;如果是引用類型的變量,則在對其初始化之后便不能再讓其指向另一個對象(其內(nèi)容是可以改變的)。
當(dāng)用final作用于類的成員變量時,成員變量(注意是類的成員變量,局部變量只需要保證在使用之前被初始化賦值即可)必須在定義時或者構(gòu)造器中進(jìn)行初始化賦值,而且final變量一旦被初始化賦值之后,就不能再被賦值了。
so 舉個例子感受下。
public class test1 {
final int i = 0;
public static void main(String[] args) {
i = 1;//error:Cannot make a static reference to the non-static field i
final StringBuffer str = new StringBuffer("123");
str = new StringBuffer("");//error:The final local variable str cannot be assigned. It must be blank and not using a compound assignment
str.append("123555");
}
}
參考:
3