Java面試知識點(diǎn)總結(jié)-基礎(chǔ)


java基礎(chǔ)

集合承繼包含圖

  1. Collection vs Collections

首先,"Collection" and "Collections"是兩個(gè)不同的概念,從下面的繼承關(guān)系圖中我們可以看到,"Collection"是一個(gè)基礎(chǔ)接口類,而"Collections"是一個(gè)靜態(tài)的類,它提供了一些靜態(tài)方法來操作某些集合類型。

image
  1. 集合的類繼承關(guān)系圖

下圖說明了集合的繼承關(guān)系。

image
  1. Map的繼承關(guān)系圖


    image

ArrayList如何實(shí)現(xiàn)排序?

// 排序方法
    //ArrayList.class
    public void sort(Comparator<? super E> c) {
        final int expectedModCount = modCount;
        Arrays.sort((E[]) elementData, 0, size, c);
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
        modCount++;
    }

    //Array.class   實(shí)現(xiàn)比較接口
    public static <T> void sort(T[] a, int fromIndex, int toIndex,
                                Comparator<? super T> c) {
        // 如果沒有實(shí)現(xiàn)比較方法
        if (c == null) {
            sort(a, fromIndex, toIndex);
        } else {
            rangeCheck(a.length, fromIndex, toIndex);
            if (Arrays.LegacyMergeSort.userRequested)
                legacyMergeSort(a, fromIndex, toIndex, c);
            else
                TimSort.sort(a, fromIndex, toIndex, c, null, 0, 0);
        }
    }

//Array.class  未實(shí)現(xiàn)比較接口
    public static void sort(Object[] a, int fromIndex, int toIndex) {
        rangeCheck(a.length, fromIndex, toIndex);
        //經(jīng)查資料,這是個(gè)傳統(tǒng)的歸并排序,需要通過設(shè)置系統(tǒng)屬性后,才能進(jìn)行調(diào)用
        // System.setProperty("java.util.Arrays.useLegacyMergeSort", "true");
        if (Arrays.LegacyMergeSort.userRequested)
            legacyMergeSort(a, fromIndex, toIndex);
        else
            ComparableTimSort.sort(a, fromIndex, toIndex, null, 0, 0);
    }

底層是一種傳統(tǒng)歸并排序。

Collection 和 Collections的區(qū)別

首先,"Collection" and "Collections"是兩個(gè)不同的概念,從下面的繼承關(guān)系圖中我們可以看到,"Collection"是一個(gè)基礎(chǔ)接口類,而"Collections"是一個(gè)靜態(tài)的類,它提供了一些靜態(tài)方法來操作某些集合類型。


image

ArrayList 和 Vector的區(qū)別

ArrayList,Vector主要區(qū)別為以下幾點(diǎn):
(1):Vector是線程安全的,源碼中有很多的synchronized可以看出,而ArrayList不是。導(dǎo)致Vector效率無法和ArrayList相比;
(2):ArrayList和Vector都采用線性連續(xù)存儲空間,當(dāng)存儲空間不足的時(shí)候,ArrayList默認(rèn)增加為原來的50%,Vector默認(rèn)增加為原來的一倍;
(3):Vector可以設(shè)置capacityIncrement,而ArrayList不可以,從字面理解就是capacity容量,Increment增加,容量增長的參數(shù)。

ArrayList的底層實(shí)現(xiàn)和擴(kuò)容情況

構(gòu)造ArrayList的時(shí)候,默認(rèn)初始化容量為10,保存容器為 Object[] elementData。
向集合添加元素的時(shí)候,調(diào)用add方法,比如list.add("a");
add方法做的操作是:elementData[size++] = e; 然后元素就被存放進(jìn)了elementData。
初始化容量為10,當(dāng)我們存第十一個(gè)元素的時(shí)候,會怎么做呢?看ArrayList類的部分源碼:

public class ArrayList<E> extends AbstractList<E> implements List<E> {
    private transient Object[] elementData;
    
    public ArrayList(int initialCapacity) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
        this.elementData = new Object[initialCapacity];
    }
    //Constructs an empty list with an initial capacity of ten.
    public ArrayList() {
        this(10);
    }
    public boolean add(E e) {
        ensureCapacity(size + 1);  // 擴(kuò)充長度
        elementData[size++] = e; // 先賦值,后進(jìn)行size++。所以是從[0]開始存。
        return true;
    }
    public void ensureCapacity(int minCapacity) {
        modCount++;
        int oldCapacity = elementData.length; // 舊集合長度
        if (minCapacity > oldCapacity) {
            Object oldData[] = elementData; // 舊集合數(shù)據(jù)
            int newCapacity = (oldCapacity * 3)/2 + 1; // 計(jì)算新長度,舊長度的1.5倍+1
                if (newCapacity < minCapacity)
                    newCapacity = minCapacity;
                // minCapacity is usually close to size, so this is a win:
                elementData = Arrays.copyOf(elementData, newCapacity); // 這就是傳說中的可變集合。用新長度復(fù)制原數(shù)組。
        }
    }
    public E get(int index) {
        RangeCheck(index);
        return (E) elementData[index];
    }
}

add方法中先調(diào)用ensureCapacity方法對原數(shù)組長度進(jìn)行擴(kuò)充,擴(kuò)充方式為,通過Arrays類的copyOf方法對原數(shù)組進(jìn)行拷貝,長度為原數(shù)組的1.5倍+1。
然后把擴(kuò)容后的新數(shù)組實(shí)例對象地址賦值給elementData引用類型變量。擴(kuò)容完畢。

經(jīng)測試,如果要存100萬數(shù)據(jù),需要擴(kuò)容28次,數(shù)據(jù)量越大,擴(kuò)容次數(shù)越多,每一次的擴(kuò)容代表著創(chuàng)建新數(shù)組對象,復(fù)制原有數(shù)據(jù)。

ArrayList 和 LinkedList的區(qū)別

ArrayList和Vector使用了數(shù)組的實(shí)現(xiàn),可以認(rèn)為ArrayList或者Vector封裝了對內(nèi)部數(shù)組的操作,比如向數(shù)組中添加,刪除,插入新的元素或者數(shù)據(jù)的擴(kuò)展和重定向。

LinkedList使用了循環(huán)雙向鏈表數(shù)據(jù)結(jié)構(gòu)。與基于數(shù)組ArrayList相比,這是兩種截然不同的實(shí)現(xiàn)技術(shù),這也決定了它們將適用于完全不同的工作場景。

LinkedList鏈表由一系列表項(xiàng)連接而成。一個(gè)表項(xiàng)總是包含3個(gè)部分:元素內(nèi)容,前驅(qū)表和后驅(qū)表

(1)增加元素到列表尾端

ArrayList中add()方法的性能決定于ensureCapacity()方法。 擴(kuò)容函數(shù)。
LinkeList由于使用了鏈表的結(jié)構(gòu),因此不需要維護(hù)容量的大小

(2)增加元素到列表任意位置

由于實(shí)現(xiàn)的不同,ArrayList和LinkedList在這個(gè)方法上存在一定的性能差異,由于ArrayList是基于數(shù)組實(shí)現(xiàn)的,而數(shù)組是一塊連續(xù)的內(nèi)存空間,如果在數(shù)組的任意位置插入元素,必然導(dǎo)致在該位置后的所有元素需要重新排列,因此,其效率相對會比較低。

(3)刪除任意位置元素

對ArrayList來說,remove()方法和add()方法是雷同的。在任意位置移除元素后,都要進(jìn)行數(shù)組的重組。

(4)容量參數(shù)

容量參數(shù)是ArrayList和Vector等基于數(shù)組的List的特有性能參數(shù)。它表示初始化的數(shù)組大小。當(dāng)ArrayList所存儲的元素?cái)?shù)量超過其已有大小時(shí)。它便會進(jìn)行擴(kuò)容,數(shù)組的擴(kuò)容會導(dǎo)致整個(gè)數(shù)組進(jìn)行一次內(nèi)存復(fù)制。因此合理的數(shù)組大小有助于減少數(shù)組擴(kuò)容的次數(shù),從而提高系統(tǒng)性能。
ArrayList提供了一個(gè)可以制定初始數(shù)組大小的構(gòu)造函數(shù) :
public ArrayList(int initialCapacity)

(5)遍歷列表

最簡便的ForEach循環(huán)并沒有很好的性能表現(xiàn),綜合性能不如普通的迭代器,而是用for循環(huán)通過隨機(jī)訪問遍歷列表時(shí),ArrayList表項(xiàng)很好,但是LinkedList的表現(xiàn)卻無法讓人接受,甚至沒有辦法等待程序的結(jié)束。這是因?yàn)閷inkedList進(jìn)行隨機(jī)訪問時(shí),總會進(jìn)行一次列表的遍歷操作。性能非常差,應(yīng)避免使用。

Array和ArrayList的區(qū)別?什么時(shí)候更適合Array

(1)ArrayList是Array的復(fù)雜版本

ArrayList內(nèi)部封裝了一個(gè)Object類型的數(shù)組,從一般的意義來說,它和數(shù)組沒有本質(zhì)的差別,甚至于ArrayList的許多方法,如Index、IndexOf、Contains、Sort等都是在內(nèi)部數(shù)組的基礎(chǔ)上直接調(diào)用Array的對應(yīng)方法。

(2)存儲的數(shù)據(jù)類型

ArrayList可以存儲異構(gòu)對象,而Array只能存儲相同數(shù)據(jù)類型的數(shù)據(jù)。

(3)長度的可變

Array的長度實(shí)際上是不可變的,二維變長數(shù)組實(shí)際上的長度也是固定的,可變的只是其中元素的長度。而ArrayList的長度既可以指定(即使指定了長度,也會自動2倍擴(kuò)容)也可以不指定,是變長的。

(4)存取和增刪元素

對于一般的引用類型來說,這部分的影響不是很大,但是對于值類型來說,往ArrayList里面添加和修改元素,都會引起裝箱和拆箱的操作,頻繁的操作可能會影響一部分效率。另外,ArrayList是動態(tài)數(shù)組,它不包括通過Key或者Value快速訪問的算法,所以實(shí)際上調(diào)用IndexOf、Contains等方法是執(zhí)行的簡單的循環(huán)來查找元素,所以頻繁的調(diào)用此類方法并不比你自己寫循環(huán)并且稍作優(yōu)化來的快,如果有這方面的要求,建議使用Hashtable或SortedList等鍵值對的集合。

適用場景:
如果想要保存一些在整個(gè)程序運(yùn)行期間都會存在而且不變的數(shù)據(jù),我們可以將它們放進(jìn)一個(gè)全局?jǐn)?shù)組里,但是如果我們單純只是想要以數(shù)組的形式保存數(shù)據(jù),而不對數(shù)據(jù)進(jìn)行增加等操作,只是方便我們進(jìn)行查找的話,那么,我們就選擇ArrayList。

去掉Vector中一個(gè)反復(fù)的元素

1. Vector.contains()
通過Vector.contains()方法判斷是否包含該元素,如果沒有包含就添加到新的集合當(dāng)中,用于數(shù)據(jù)較小的情況

2. HashSet()
使用HashSet來解決這個(gè)問題,通過hashcode的過濾解決問題。

HashMap HasnTable concurrentHashMap原理、區(qū)別

HashTable
底層數(shù)組+鏈表實(shí)現(xiàn),無論key還是value都不能為null,線程安全,實(shí)現(xiàn)線程安全的方式是在修改數(shù)據(jù)時(shí)鎖住整個(gè)HashTable,效率低,ConcurrentHashMap做了相關(guān)優(yōu)化
初始size為11,擴(kuò)容:newsize = olesize*2+1
計(jì)算index的方法:index = (hash & 0x7FFFFFFF) % tab.length

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

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

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

哈希沖突:若干Key的哈希值按數(shù)組大小取模后,如果落在同一個(gè)數(shù)組下標(biāo)上,將組成一條Entry鏈,對Key的查找需要遍歷Entry鏈上的每個(gè)元素執(zhí)行equals()比較。
加載因子:為了降低哈希沖突的概率,默認(rèn)當(dāng)HashMap中的鍵值對達(dá)到數(shù)組大小的75%時(shí),即會觸發(fā)擴(kuò)容。因此,如果預(yù)估容量是100,即需要設(shè)定100/0.75=134的數(shù)組大小。
空間換時(shí)間:如果希望加快Key查找的時(shí)間,還可以進(jìn)一步降低加載因子,加大初始大小,以降低哈希沖突的概率。

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

List Map Set三個(gè)接口,存取數(shù)據(jù)時(shí)各有什么特點(diǎn)

List與Set都是單列元素的集合,它們有一個(gè)功共同的父接口Collection。

Set里面不允許有重復(fù)的元素,

存元素:add方法有一個(gè)boolean的返回值,當(dāng)集合中沒有某個(gè)元素,此時(shí)add方法可成功加入該元素時(shí),則返回true;當(dāng)集合含有與某個(gè)元素equals相等的元素時(shí),此時(shí)add方法無法加入該元素,返回結(jié)果為false。

取元素:沒法說取第幾個(gè),只能以Iterator接口取得所有的元素,再逐一遍歷各個(gè)元素。

List表示有先后順序的集合,

存元素:多次調(diào)用add(Object)方法時(shí),每次加入的對象按先來后到的順序排序,也可以插隊(duì),即調(diào)用add(int index,Object)方法,就可以指定當(dāng)前對象在集合中的存放位置。

取元素
方法1:Iterator接口取得所有,逐一遍歷各個(gè)元素

方法2:調(diào)用get(index i)來明確說明取第幾個(gè)。

Map是雙列的集合,

存放用put方法:put(obj key,obj value),每次存儲時(shí),要存儲一對key/value,不能存儲重復(fù)的key,這個(gè)重復(fù)的規(guī)則也是按equals比較相等。

取元素:用get(Object key)方法根據(jù)key獲得相應(yīng)的value。也可以獲得所有的key的集合,還可以獲得所有的value的集合,還可以獲得key和value組合成的Map.Entry對象的集合。

List以特定次序來持有元素,可有重復(fù)元素。Set 無法擁有重復(fù)元素,內(nèi)部排序。Map 保存key-value值,value可多值。

介紹一下TreeSet

Java中的TreeSet是Set的一個(gè)子類,TreeSet集合是用來對象元素進(jìn)行排序的,同樣他也可以保證元素的唯一。
TreeSet 是一個(gè)有序的集合,它的作用是提供有序的Set集合。它繼承于AbstractSet抽象類,實(shí)現(xiàn)了NavigableSet<E>, Cloneable, java.io.Serializable接口。
TreeSet 繼承于AbstractSet,所以它是一個(gè)Set集合,具有Set的屬性和方法。
TreeSet 實(shí)現(xiàn)了NavigableSet接口,意味著它支持一系列的導(dǎo)航方法。比如查找與指定目標(biāo)最匹配項(xiàng)。
TreeSet 實(shí)現(xiàn)了Cloneable接口,意味著它能被克隆。
TreeSet 實(shí)現(xiàn)了java.io.Serializable接口,意味著它支持序列化。

TreeSet是基于TreeMap實(shí)現(xiàn)的。TreeSet中的元素支持2種排序方式:自然排序 或者 根據(jù)創(chuàng)建TreeSet 時(shí)提供的 Comparator 進(jìn)行排序。這取決于使用的構(gòu)造方法。
TreeSet為基本操作(add、remove 和 contains)提供受保證的 log(n) 時(shí)間開銷。
另外,TreeSet是非同步的。 它的iterator 方法返回的迭代器是fail-fast的。

image

(1) TreeSet繼承于AbstractSet,并且實(shí)現(xiàn)了NavigableSet接口。
(2) TreeSet的本質(zhì)是一個(gè)"有序的,并且沒有重復(fù)元素"的集合,它是通過TreeMap實(shí)現(xiàn)的。TreeSet中含有一個(gè)"NavigableMap類型的成員變量"m,而m實(shí)際上是"TreeMap的實(shí)例"。

TreeSet不支持快速隨機(jī)遍歷,只能通過迭代器進(jìn)行遍歷!

Java集合中那些類是線程安全的

在集合框架中,有些類是線程安全的,這些都是jdk1.1中的出現(xiàn)的。在jdk1.2之后,就出現(xiàn)許許多多非線程安全的類。 下面是這些線程安全的同步的類:

vector:就比arraylist多了個(gè)同步化機(jī)制(線程安全),因?yàn)樾瘦^低,現(xiàn)在已經(jīng)不太建議使用。在web應(yīng)用中,特別是前臺頁面,往往效率(頁面響應(yīng)速度)是優(yōu)先考慮的。

statck:堆棧類,先進(jìn)后出

hashtable:就比hashmap多了個(gè)線程安全

enumeration:枚舉,相當(dāng)于迭代器

除了這些之外,其他的都是非線程安全的類和接口。

線程安全的類其方法是同步的,每次只能一個(gè)訪問。是重量級對象,效率較低。

BlockingQueue是什么?如何用wait和notify實(shí)現(xiàn)blockingqueue

BlockingQueue是阻塞隊(duì)列。

阻塞隊(duì)列與普通隊(duì)列的區(qū)別在于,當(dāng)隊(duì)列是空的時(shí),從隊(duì)列中獲取元素的操作將會被阻塞,或者當(dāng)隊(duì)列是滿時(shí),往隊(duì)列里添加元素的操作會被阻塞。試圖從空的阻塞隊(duì)列中獲取元素的線程將會被阻塞,直到其他的線程往空的隊(duì)列插入新的元素。

public class BlockingQueueDemo {
    //定義兩把鎖,只是簡單的鎖
    private Object full = new Object();
    private Object empty = new Object();
    private int[] array;
    private int head;
    private int last; 
    private int size;
    public BlockingQueueDemo(int maxSize) {
        this.head = 0; 
        this.last = 0;
        array = new int[maxSize];
    }
    public void put(int e) throws InterruptedException {
        synchronized(full){  //滿鎖
            while (size == array.length) {//沒有更多空間,需要阻塞
                full.wait();
            }
        }
        
        if (last < array.length) {
            array[last] = e;
            last++;
        } else {
            array[0] = e;
            last = 1;
        }
        size++;
        System.out.println(size+" :size大小,last: "+last+"    :e:   "+e);
        
        //放入數(shù)據(jù)以后,就可以喚醒obj2對象的鎖
        synchronized(empty){  //空鎖
            empty.notify();//達(dá)到了喚醒poll方法的條件
        }
    }
    
    public int pool() throws InterruptedException {
        synchronized(empty){
            while (size == 0) {//沒有數(shù)據(jù),阻塞
                empty.wait();
            }
        }
        int returnValue = 0;
        //隊(duì)列中有數(shù)據(jù),且head小于array的長度
        returnValue = array[head];
        array[head] = -1;
        System.out.println(returnValue + "              彈出的"+"head:"+ head);
        if (head < array.length) {//彈出head下標(biāo)的數(shù)據(jù)
            head++;
        } else {
            head = 0;
        }
        size--; 
        //拿走數(shù)據(jù)后,喚醒full對象鎖
        synchronized(full){
            full.notify();
        }
        return returnValue;
    }
    
    public String toString(){
        for (int i = 0; i < array.length; i++) {
            System.out.println(array[i]);
        }
        return "";
    }
    
    public static void main(String[] args) throws InterruptedException {
        BlockingQueueDemo bq = new BlockingQueueDemo(5);
        bq.put(10);
        bq.put(20);
        bq.put(30);
        bq.put(40);
        bq.put(50);
        System.out.println();
        bq.pool();
        bq.pool();
        bq.pool();
        bq.pool();
        System.out.println();
        bq.toString();
        System.out.println();
        bq.put(100);
        bq.put(200);
        bq.put(300);
        bq.put(400);
        System.out.println();
        bq.toString();
    }
}

并發(fā)集合類有哪些?有沒有研究過源碼

當(dāng)需要在并發(fā)程序中使用數(shù)據(jù)集合時(shí),必須要謹(jǐn)慎地選擇相應(yīng)的實(shí)現(xiàn)方式。大多數(shù)集合類并不能直接用于并發(fā)應(yīng)用,因?yàn)樗麄儧]有對本身數(shù)據(jù)的并發(fā)訪問進(jìn)行控制,如果一些并發(fā)任務(wù)共享一個(gè)不適用于病發(fā)任務(wù)的數(shù)據(jù)結(jié)構(gòu),將會遇到數(shù)據(jù)不一致性的錯(cuò)誤,并將影響程序的正確運(yùn)行,這類數(shù)據(jù)結(jié)構(gòu)的一個(gè)例子就是ArrayList。

 java提供了一些可以用于并發(fā)程序中的數(shù)據(jù)集合,他們不會引起任何問題,一般來說,java提供了兩類適用于并發(fā)應(yīng)用的集合:

(1)阻塞式集合(blocking collection):這類集合包括添加和移除數(shù)據(jù)的方法。當(dāng)集合已滿或者為空時(shí),被調(diào)用的添加或者 移除方法就不能立即執(zhí)行,那么調(diào)用這個(gè)飯方法的線程將被阻塞,一直到該方法可以被成功執(zhí)行。

(2)非阻塞式集合(Non-blocking collection):這類集合也包括添加和移除數(shù)據(jù)的方法。如果集合已滿或者為空時(shí),在調(diào)用添加或者移除方法時(shí)會返回null或者拋出異常,但是調(diào)用這個(gè)方法的線程不會被阻塞。

以下就是java常用的并發(fā)集合:

非阻塞式列表對應(yīng)的實(shí)現(xiàn)類:ConcurrentLinkedDeque

阻塞式列表對應(yīng)的實(shí)現(xiàn)類:LinkedBlockingDeque

用于數(shù)據(jù)生成或者消費(fèi)的阻塞式列表對應(yīng)的實(shí)現(xiàn)類:LinkedTransferQueue

按優(yōu)先級排序列表元素的阻塞式列表對應(yīng)的實(shí)現(xiàn)類:PriorityBlockingQueue

帶有延遲列表元素的阻塞式列表對應(yīng)的實(shí)現(xiàn)類:DelayQueue

非阻塞式列表可遍歷映射對應(yīng)的餓實(shí)現(xiàn)類:ConcurrentSkipListMap

隨機(jī)數(shù)字對應(yīng)的實(shí)現(xiàn)類:ThreadLockRandom

原子變量對應(yīng)的實(shí)現(xiàn)類:AtomicLong和AtomicIntegerArray

ThreadPoolExecutor 的內(nèi)部工作原理,整個(gè)思路總結(jié)起來就是 5 句話:

  1. 如果當(dāng)前池大小 poolSize 小于 corePoolSize ,則創(chuàng)建新線程執(zhí)行任務(wù)。

  2. 如果當(dāng)前池大小 poolSize 大于 corePoolSize ,且等待隊(duì)列未滿,則進(jìn)入等待隊(duì)列

  3. 如果當(dāng)前池大小 poolSize 大于 corePoolSize 且小于 maximumPoolSize ,且等待隊(duì)列已滿,則創(chuàng)建新線程執(zhí)行任務(wù)。

  4. 如果當(dāng)前池大小 poolSize 大于 corePoolSize 且大于 maximumPoolSize ,且等待隊(duì)列已滿,則調(diào)用拒絕策略來處理該任務(wù)。

  5. 線程池里的每個(gè)線程執(zhí)行完任務(wù)后不會立刻退出,而是會去檢查下等待隊(duì)列里是否還有線程任務(wù)需要執(zhí)行,如果在 keepAliveTime 里等不到新的任務(wù)了,那么線程就會退出。

Comparable 和 Comparator接口是什么?有何區(qū)別?代碼如何實(shí)現(xiàn)(背代碼)

java中,對集合對象或者數(shù)組對象排序,有兩種實(shí)現(xiàn)方式。
(1)對象實(shí)現(xiàn)Comparable 接口
(2)定義比較器,實(shí)現(xiàn)Comparator接口。

Comparable
Comparable是在集合內(nèi)部定義的方法實(shí)現(xiàn)的排序,位于java.lang下。
Comparable是一個(gè)對象,本身就已經(jīng)支持自比較所需要實(shí)現(xiàn)的接口。

package java.lang;
import java.util.*;
 
 
public interface Comparable<T> {
    
    public int compareTo(T o);
}

自定義類要在加入list容器中后能夠排序,也可以實(shí)現(xiàn)Comparable接口。

在用Collections類的sort方法排序時(shí)若不指定Comparator,那就以自然順序排序。所謂自然順序就是實(shí)現(xiàn)Comparable接口設(shè)定的排序方式。

若一個(gè)類實(shí)現(xiàn)了comparable接口,則意味著該類支持排序。如String、Integer自己就實(shí)現(xiàn)了Comparable接口,可完成比較大小操作。

一個(gè)已經(jīng)實(shí)現(xiàn)comparable的類的對象或數(shù)據(jù),可以通過Collections.sort(list) 或者Arrays.sort(arr)實(shí)現(xiàn)排序。通過Collections.sort(list,Collections.reverseOrder());對list進(jìn)行倒序排列。

Comparator
Comparator是在集合外部實(shí)現(xiàn)的排序,位于java.util下。
Comparator接口包含了兩個(gè)函數(shù)。

package java.util;
public interface Comparator<T> {
 
    int compare(T o1, T o2);
    boolean equals(Object obj);
}

我們?nèi)粜枰刂颇硞€(gè)類的次序,而該類本身不支持排序(即沒有實(shí)現(xiàn)Comparable接口);那么,我們可以新建一個(gè)該類的比較器來進(jìn)行排序。這個(gè)比較器只需要實(shí)現(xiàn)comparator即可。
如果引用的為第三方j(luò)ar包,這時(shí)候,沒辦法改變類本身,可是使用這種方式。

Comparator是一個(gè)專用的比較器,當(dāng)這個(gè)對象不支持自比較或者自比較函數(shù)不能滿足要求時(shí),可寫一個(gè)比較器來完成兩個(gè)對象之間大小的比較。
Comparator體現(xiàn)了一種策略模式(strategy design pattern),就是不改變對象自身,而用一個(gè)策略對象(strategy object)來改變它的行為。
comparable相當(dāng)于內(nèi)部比較器。comparator相當(dāng)于外部比較器。

如何對一組對象進(jìn)行排序

  1. 創(chuàng)建對象類
  2. 實(shí)例化對象
  3. 存入適當(dāng)?shù)募项?/li>
  4. 實(shí)現(xiàn)Comparable接口或者建立實(shí)現(xiàn)了Comparator接口的比較器類

迭代器

Iterator是什么?

詳見:http://www.itdecent.cn/p/24a642f1a1b8

Enumeration接口和Iterator接口的區(qū)別

Enumeration 接口的作用與 Iterator 接口類似,但只提供了遍歷VectorHashtable類型集合元素的功能,不支持元素的移除操作。
例如:遍歷Vector<E> v中的元素:

for (Enumeration<E> e = v.elements();e.hasMoreElements();)
       System.out.println(e.nextElement());

Iterator 接口添加了一個(gè)可選的移除操作,并使用較短的方法名。新的實(shí)現(xiàn)應(yīng)該優(yōu)先考慮使用 Iterator 接口而不是 Enumeration 接口。

區(qū)別:Enumeration速度是Iterator的2倍,同時(shí)占用更少的內(nèi)存。但是,Iterator遠(yuǎn)遠(yuǎn)比Enumeration安全,因?yàn)槠渌€程不能夠修改正在被iterator遍歷的集合里面的對象。同時(shí),Iterator允許調(diào)用者刪除底層集合里面的元素,這對Enumeration來說是不可能的。

Iterator 接口的用法:

Iterator it = list.iterator();
while(it.hasNext()){
    System.out.println(it.next());
}

為什么沒有像Iterator.add()這樣的方法向集合中添加元素?

邏輯上講,迭代時(shí)可以添加元素,但是一旦開放這個(gè)功能,很有可能造成很多意想不到的情況。 比如你在迭代一個(gè)ArrayList,迭代器的工作方式是依次返回給你第0個(gè)元素,第1個(gè)元素,等等,假設(shè)當(dāng)你迭代到第5個(gè)元素的時(shí)候,你突然在ArrayList的頭部插入了一個(gè)元素,使得你所有的元素都往后移動,于是你當(dāng)前訪問的第5個(gè)元素就會被重復(fù)訪問。 java認(rèn)為在迭代過程中,容器應(yīng)當(dāng)保持不變。因此,java容器中通常保留了一個(gè)域稱為modCount,每次你對容器修改,這個(gè)值就會加1。當(dāng)你調(diào)用iterator方法時(shí),返回的迭代器會記住當(dāng)前的modCount,隨后迭代過程中會檢查這個(gè)值,一旦發(fā)現(xiàn)這個(gè)值發(fā)生變化,就說明你對容器做了修改,就會拋異常。

為何迭代器沒有一個(gè)方法可以直接獲取下一個(gè)元素,而不需要移動游標(biāo)?

它可以在當(dāng)前Iterator的頂層實(shí)現(xiàn),但是它用得很少,如果將它加到接口中,每個(gè)繼承都要去實(shí)現(xiàn)它,這沒有意義。

Iterator和ListIterator之間有什么區(qū)別?

(1)我們可以使用Iterator來遍歷Set和List集合,而ListIterator只能遍歷List。

(2)Iterator只可以向前遍歷,而LIstIterator可以雙向遍歷。

(3)ListIterator從Iterator接口繼承,然后添加了一些額外的功能,比如添加一個(gè)元素、替換一個(gè)元素、獲取前面或后面元素的索引位置。

遍歷一個(gè)List有哪些不同的方式?

List<String> strList = new ArrayList<>();
//使用for-each循環(huán)
for(String obj : strList){
  System.out.println(obj);
}
//using iterator
Iterator<String> it = strList.iterator();
while(it.hasNext()){
  String obj = it.next();
  System.out.println(obj);
}

使用迭代器更加線程安全,因?yàn)樗梢源_保,在當(dāng)前遍歷的集合元素被更改的時(shí)候,它會拋出ConcurrentModificationException。

map遍歷的四種方式

public static void main(String[] args) {


  Map<String, String> map = new HashMap<String, String>();
  map.put("1", "value1");
  map.put("2", "value2");
  map.put("3", "value3");

  //第一種:普遍使用,二次取值
  System.out.println("通過Map.keySet遍歷key和value:");
  for (String key : map.keySet()) {
   System.out.println("key= "+ key + " and value= " + map.get(key));
  }

  //第二種
  System.out.println("通過Map.entrySet使用iterator遍歷key和value:");
  Iterator<Map.Entry<String, String>> it = map.entrySet().iterator();
  while (it.hasNext()) {
   Map.Entry<String, String> entry = it.next();
   System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue());
  }

  //第三種:推薦,尤其是容量大時(shí)
  System.out.println("通過Map.entrySet遍歷key和value");
  for (Map.Entry<String, String> entry : map.entrySet()) {
   System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue());
  }

  //第四種
  System.out.println("通過Map.values()遍歷所有的value,但不能遍歷key");
  for (String v : map.values()) {
   System.out.println("value= " + v);
  }
 }

通過迭代器fail-fast屬性,你明白了什么?

每次我們嘗試獲取下一個(gè)元素的時(shí)候,Iterator fail-fast屬性檢查當(dāng)前集合結(jié)構(gòu)里的任何改動。如果發(fā)現(xiàn)任何改動,它拋出ConcurrentModificationException。Collection中所有Iterator的實(shí)現(xiàn)都是按fail-fast來設(shè)計(jì)的(ConcurrentHashMapCopyOnWriteArrayList這類并發(fā)集合類除外)。

fail-fast與fail-safe有什么區(qū)別?

Iterator的fail-fast屬性與當(dāng)前的集合共同起作用,因此它不會受到集合中任何改動的影響。Java.util包中的所有集合類都被設(shè)計(jì)為fail-fast的,而java.util.concurrent中的集合類都為fail-safe的。Fail-fast迭代器拋出ConcurrentModificationException,而fail-safe迭代器從不拋出ConcurrentModificationException。

為何Iterator接口沒有具體的實(shí)現(xiàn)?

Iterator接口定義了遍歷集合的方法,但它的實(shí)現(xiàn)則是集合實(shí)現(xiàn)類的責(zé)任。每個(gè)能夠返回用于遍歷的Iterator的集合類都有它自己的Iterator實(shí)現(xiàn)內(nèi)部類。

這就允許集合類去選擇迭代器是fail-fast還是fail-safe的。比如,ArrayList迭代器是fail-fast的,而CopyOnWriteArrayList迭代器是fail-safe的。

UnsupportedOperationException是什么?

UnsupportedOperationException是用于表明操作不支持的異常。在JDK類中已被大量運(yùn)用,在集合框架java.util.Collections.UnmodifiableCollection將會在所有add和remove操作中拋出這個(gè)異常。

其他

java中的sleep()和wait()的區(qū)別

sleep()方法,是屬于Thread類中的。而wait()方法,則是屬于Object類中的。

sleep()方法導(dǎo)致了程序暫停執(zhí)行指定的時(shí)間,讓出cpu該其他線程,但是他的監(jiān)控狀態(tài)依然保持者,當(dāng)指定的時(shí)間到了又會自動恢復(fù)運(yùn)行狀態(tài)。
在調(diào)用sleep()方法的過程中,線程不會釋放對象鎖。

而當(dāng)調(diào)用wait()方法的時(shí)候,線程會放棄對象鎖,進(jìn)入等待此對象的等待鎖定池,只有針對此對象調(diào)用notify()方法后本線程才進(jìn)入對象鎖定池準(zhǔn)備獲取對象鎖進(jìn)入運(yùn)行狀態(tài)。

String stringbuff stringbuilder不變性

有時(shí)候,需要較短的字符串構(gòu)建字符串,例如來自按鍵或者文件中的單詞。這時(shí)采用拼接的方式達(dá)到此目的效率比較低。而String為不可變字符串,每次連接字符串就需要創(chuàng)建一個(gè)新的String對象,既耗時(shí)有占用空間。使用StringBuffer和 StringBuilder可以解決(可修改的)

這里一StringBuilder為例

新建一個(gè)構(gòu)造器

StringBuilder builder =new StringBuilder();

當(dāng)每次需要添加內(nèi)容是就用append()方法 需要構(gòu)建字符串時(shí)就調(diào)用同String方法

String s=builder.toString();

StringBuffer是StringBuilder的前身,可想而知 Builder的效率要高于Buffer但是 BUffer是線程安全的,允許采用多線程的方式執(zhí)行添加,刪除字符串的操作。但是大多數(shù)情況下我們都是單線程進(jìn)行操作,因此使用builder的情況更多

一致性hash原理

https://blog.csdn.net/luyaran/article/details/53665523

HashMap,concurrentHashMap原理(1.7 和 1.8的區(qū)別)、區(qū)別以及在什么情況下性能會不好

詳見:https://www.cnblogs.com/study-everyday/p/6430462.html

JDK1.7的實(shí)現(xiàn)
在JDK1.7版本中,ConcurrentHashMap的數(shù)據(jù)結(jié)構(gòu)是由一個(gè)Segment數(shù)組和多個(gè)HashEntry組成,如下圖所示:

image

Segment數(shù)組的意義就是將一個(gè)大的table分割成多個(gè)小的table來進(jìn)行加鎖,也就是上面的提到的鎖分離技術(shù),而每一個(gè)Segment元素存儲的是HashEntry數(shù)組+鏈表,這個(gè)和HashMap的數(shù)據(jù)存儲結(jié)構(gòu)一樣

初始化
ConcurrentHashMap的初始化是會通過位與運(yùn)算來初始化Segment的大小,用ssize來表示,如下所示:

int sshift = 0;
int ssize = 1;
while (ssize < concurrencyLevel) {
    ++sshift;
    ssize <<= 1;
}

如上所示,因?yàn)閟size用位于運(yùn)算來計(jì)算(ssize <<=1),所以Segment的大小取值都是以2的N次方,無關(guān)concurrencyLevel的取值,當(dāng)然concurrencyLevel最大只能用16位的二進(jìn)制來表示,即65536,換句話說,Segment的大小最多65536個(gè),沒有指定concurrencyLevel元素初始化,Segment的大小ssize默認(rèn)為16

每一個(gè)Segment元素下的HashEntry的初始化也是按照位于運(yùn)算來計(jì)算,用cap來表示,如下所示:

int cap = 1;
while (cap < c)
    cap <<= 1;

如上所示,HashEntry大小的計(jì)算也是2的N次方(cap <<=1), cap的初始值為1,所以HashEntry最小的容量為2

JDK1.8的實(shí)現(xiàn)
JDK1.8的實(shí)現(xiàn)已經(jīng)摒棄了Segment的概念,而是直接用Node數(shù)組+鏈表+紅黑樹的數(shù)據(jù)結(jié)構(gòu)來實(shí)現(xiàn),并發(fā)控制使用Synchronized和CAS來操作,整個(gè)看起來就像是優(yōu)化過且線程安全的HashMap,雖然在JDK1.8中還能看到Segment的數(shù)據(jù)結(jié)構(gòu),但是已經(jīng)簡化了屬性,只是為了兼容舊版本

image

在深入JDK1.8的put和get實(shí)現(xiàn)之前要知道一些常量設(shè)計(jì)和數(shù)據(jù)結(jié)構(gòu),這些是構(gòu)成ConcurrentHashMap實(shí)現(xiàn)結(jié)構(gòu)的基礎(chǔ)

Node
Node是ConcurrentHashMap存儲結(jié)構(gòu)的基本單元,繼承于HashMap中的Entry,用于存儲數(shù)據(jù)。
Node數(shù)據(jù)結(jié)構(gòu)很簡單,從上可知,就是一個(gè)鏈表,但是只允許對數(shù)據(jù)進(jìn)行查找,不允許進(jìn)行修改。

TreeNode
TreeNode繼承與Node,但是數(shù)據(jù)結(jié)構(gòu)換成了二叉樹結(jié)構(gòu),它是紅黑樹的數(shù)據(jù)的存儲結(jié)構(gòu),用于紅黑樹中存儲數(shù)據(jù),當(dāng)鏈表的節(jié)點(diǎn)數(shù)大于8時(shí)會轉(zhuǎn)換成紅黑樹的結(jié)構(gòu)。

TreeBin
TreeBin從字面含義中可以理解為存儲樹形結(jié)構(gòu)的容器,而樹形結(jié)構(gòu)就是指TreeNode,所以TreeBin就是封裝TreeNode的容器,它提供轉(zhuǎn)換黑紅樹的一些條件和鎖的控制。

其實(shí)可以看出JDK1.8版本的ConcurrentHashMap的數(shù)據(jù)結(jié)構(gòu)已經(jīng)接近HashMap,相對而言,ConcurrentHashMap只是增加了同步的操作來控制并發(fā),從JDK1.7版本的ReentrantLock+Segment+HashEntry,到JDK1.8版本中synchronized+CAS+HashEntry+紅黑樹,相對而言,總結(jié)如下思考:

  1. JDK1.8的實(shí)現(xiàn)降低鎖的粒度,JDK1.7版本鎖的粒度是基于Segment的,包含多個(gè)HashEntry,而JDK1.8鎖的粒度就是HashEntry(首節(jié)點(diǎn))
  2. JDK1.8版本的數(shù)據(jù)結(jié)構(gòu)變得更加簡單,使得操作也更加清晰流暢,因?yàn)橐呀?jīng)使用synchronized來進(jìn)行同步,所以不需要分段鎖的概念,也就不需要Segment這種數(shù)據(jù)結(jié)構(gòu)了,由于粒度的降低,實(shí)現(xiàn)的復(fù)雜度也增加了
  3. JDK1.8使用紅黑樹來優(yōu)化鏈表,基于長度很長的鏈表的遍歷是一個(gè)很漫長的過程,而紅黑樹的遍歷效率是很快的,代替一定閾值的鏈表,這樣形成一個(gè)最佳拍檔
  4. JDK1.8為什么使用內(nèi)置鎖synchronized來代替重入鎖ReentrantLock,我覺得有以下幾點(diǎn):
    4.1. 因?yàn)榱6冉档土?,在相對而言的低粒度加鎖方式,synchronized并不比ReentrantLock差,在粗粒度加鎖中ReentrantLock可能通過Condition來控制各個(gè)低粒度的邊界,更加的靈活,而在低粒度中,Condition的優(yōu)勢就沒有了
    4.2. JVM的開發(fā)團(tuán)隊(duì)從來都沒有放棄synchronized,而且基于JVM的synchronized優(yōu)化空間更大,使用內(nèi)嵌的關(guān)鍵字比使用API更加自然
    4.3. 在大量的數(shù)據(jù)操作下,對于JVM的內(nèi)存壓力,基于API的ReentrantLock會開銷更多的內(nèi)存,雖然不是瓶頸,但是也是一個(gè)選擇依據(jù)

try里面return,finally再return 哪個(gè)會返回,字節(jié)碼如何變

finally 語句塊應(yīng)該是在控制轉(zhuǎn)移語句之前執(zhí)行,控制轉(zhuǎn)移語句除了 return 外,還有 break 和 continue。

HashSet和HashMap的區(qū)別

HashMap
HashMap實(shí)現(xiàn)了Map接口
HashMap儲存鍵值對
使用put()方法將元素放入map中
HashMap中使用鍵對象來計(jì)算hashcode值
HashMap比較快,因?yàn)槭鞘褂梦ㄒ坏逆I來獲取對象

**HashSet **
HashSet實(shí)現(xiàn)了Set接口
HashSet僅僅存儲對象
使用add()方法將元素放入set中
HashSet使用成員對象來計(jì)算hashcode值,對于兩個(gè)對象來說hashcode可能相同,所以equals()方法用來判斷對象的相等性,如果兩個(gè)對象不同的話,那么返回false
HashSet較HashMap來說比較慢

object類的方法有哪些

Object是所有類的父類,任何類都默認(rèn)繼承Object。Object類到底實(shí)現(xiàn)了哪些方法?

  1. clone方法
    保護(hù)方法,實(shí)現(xiàn)對象的淺復(fù)制,只有實(shí)現(xiàn)了Cloneable接口才可以調(diào)用該方法,否則拋出CloneNotSupportedException異常。

  2. getClass方法
    final方法,獲得運(yùn)行時(shí)類型。

  3. toString方法
    該方法用得比較多,一般子類都有覆蓋。

  4. finalize方法
    該方法用于釋放資源。因?yàn)闊o法確定該方法什么時(shí)候被調(diào)用,很少使用。

  5. equals方法
    該方法是非常重要的一個(gè)方法。一般equals和==是不一樣的,但是在Object中兩者是一樣的。子類一般都要重寫這個(gè)方法。

  6. hashCode方法
    該方法用于哈希查找,重寫了equals方法一般都要重寫hashCode方法。這個(gè)方法在一些具有哈希功能的Collection中用到。

一般必須滿足obj1.equals(obj2)==true。可以推出obj1.hash- Code()==obj2.hashCode(),但是hashCode相等不一定就滿足equals。不過為了提高效率,應(yīng)該盡量使上面兩個(gè)條件接近等價(jià)。

  1. wait方法
    wait方法就是使當(dāng)前線程等待該對象的鎖,當(dāng)前線程必須是該對象的擁有者,也就是具有該對象的鎖。wait()方法一直等待,直到獲得鎖或者被中斷。wait(long timeout)設(shè)定一個(gè)超時(shí)間隔,如果在規(guī)定時(shí)間內(nèi)沒有獲得鎖就返回。

調(diào)用該方法后當(dāng)前線程進(jìn)入睡眠狀態(tài),直到以下事件發(fā)生。

(1)其他線程調(diào)用了該對象的notify方法。

(2)其他線程調(diào)用了該對象的notifyAll方法。

(3)其他線程調(diào)用了interrupt中斷該線程。

(4)時(shí)間間隔到了。

此時(shí)該線程就可以被調(diào)度了,如果是被中斷的話就拋出一個(gè)InterruptedException異常。

  1. notify方法
    該方法喚醒在該對象上等待的某個(gè)線程。

  2. notifyAll方法
    該方法喚醒在該對象上等待的所有線程。

volatile的作用以及用法

volatile讓變量每次在使用的時(shí)候,都從主存中取。而不是從各個(gè)線程的“工作內(nèi)存”。

volatile具有synchronized關(guān)鍵字的“可見性”,但是沒有synchronized關(guān)鍵字的“并發(fā)正確性”,也就是說不保證線程執(zhí)行的有序性。

也就是說,volatile變量對于每次使用,線程都能得到當(dāng)前volatile變量的最新值。但是volatile變量并不保證并發(fā)的正確性。

在Java內(nèi)存模型中,有main memory,每個(gè)線程也有自己的memory (例如寄存器)。為了性能,一個(gè)線程會在自己的memory中保持要訪問的變量的副本。這樣就會出現(xiàn)同一個(gè)變量在某個(gè)瞬間,在一個(gè)線程的memory中的值可能與另外一個(gè)線程memory中的值,或者main memory中的值不一致的情況。

一個(gè)變量聲明為volatile,就意味著這個(gè)變量是隨時(shí)會被其他線程修改的,因此不能將它c(diǎn)ache在線程memory中。

靜態(tài)類和單利模式的區(qū)別

1、單例可以繼承類,實(shí)現(xiàn)接口,而靜態(tài)類不能(可以集成類,但不能集成實(shí)例成員);
2、單例可以被延遲初始化,靜態(tài)類一般在第一次加載時(shí)初始化;?
3、單例類可以被集成,他的方法可以被覆寫
4、靜態(tài)方法是面向過程的,如果我們Java面向?qū)ο髮W(xué)得好的話應(yīng)該是不會出現(xiàn)這種類的

內(nèi)存上:
單例模式執(zhí)行的時(shí)候需要new 一個(gè)對象出來存儲在堆棧里面,而靜態(tài)方法不需要,它不依賴于對象(普通方法是Object.method而靜態(tài)方法是class.method),但是他也是需要內(nèi)存的,它是以代碼塊來存儲

生命周期:
靜態(tài)方法的類會在代碼編譯的時(shí)候就被加載,靜態(tài)方法中產(chǎn)生的對象,會隨著靜態(tài)方法執(zhí)行完畢而釋放掉,而且執(zhí)行類中的靜態(tài)方法時(shí),不會實(shí)例化靜態(tài)方法所在的類。

如果用單例模式, 產(chǎn)生的那一個(gè)唯一的實(shí)例,會一直在內(nèi)存中,不會被GC清除的(原因是靜態(tài)的屬性變量不會被GC清除),除非整個(gè)應(yīng)用退出了JVM

**執(zhí)行效率: **
靜態(tài)方法與實(shí)例方法,在加載時(shí)機(jī)和占用內(nèi)存上,靜態(tài)方法和實(shí)例方法是一樣的,在類型第一次被使用時(shí)加載。調(diào)用的速度基本上沒有差別。
但是從日志打印來看,個(gè)人感覺還是靜態(tài)方法在執(zhí)行效率上快一點(diǎn)。

super和this能不能同時(shí)使用

必須在構(gòu)造器的第一行放置super或者this構(gòu)造器,否則編譯器會自動地放一個(gè)空參數(shù)的super構(gòu)造器的,其他的構(gòu)造器也可以調(diào)用super或者this,調(diào)用成一個(gè)遞歸構(gòu)造鏈,最后的結(jié)果是父類的構(gòu)造器(可能有多級父類構(gòu)造器)始終在子類的構(gòu)造器之前執(zhí)行,遞歸的調(diào)用父類構(gòu)造器。無法執(zhí)行當(dāng)前的類的構(gòu)造器。也就不能實(shí)例化任何對象,這個(gè)類就成為一個(gè)無為類。

從另外一面說,子類是從父類繼承而來,繼承了父類的屬性和方法,如果在子類中先不完成父類的成員的初始化,則子類無法使用,應(yīng)為在java中不允許調(diào)用沒初始化的成員。在構(gòu)造器中是順序執(zhí)行的,也就是說必須在第一行進(jìn)行父類的初始化。而super能直接完成這個(gè)功能。This()通過調(diào)用本類中的其他構(gòu)造器也能完成這個(gè)功能。

因此,this()或者super()必須放在第一行。

switch能否用String做參數(shù)

jdk1.7并沒有新的指令來處理switch string,而是通過調(diào)用switch中string.hashCode,將string轉(zhuǎn)換為int從而進(jìn)行判斷。

九種基本數(shù)據(jù)類型 長度以及它們的封裝類

java提供了一組基本數(shù)據(jù)類型,包括

boolean, byte, char, short, int, long, float, double, void.

同時(shí),java也提供了這些類型的封裝類,分別為

Boolean, Byte, Character, Short, Integer, Long, Float, Double, Void

什么Java會這么做?

在java中使用基本類型來存儲語言支持的基本數(shù)據(jù)類型,這里沒有采用對象,而是使用了傳統(tǒng)的面向過程語言所采用的基本類在型,主要是從性能方面來考慮的:因?yàn)榧词棺詈唵蔚臄?shù)學(xué)計(jì)算,使用對象來處理也會引起一些開銷,而這些開銷對于數(shù)學(xué)計(jì)算本來是毫無必要的。但是在java中,泛型類包括預(yù)定義的集合,使用的參數(shù)都是對象類型,無法直接使用這些基本數(shù)據(jù)類型,所以java又提供了這些基本類型的包裝器。

基本數(shù)據(jù)類型與其對應(yīng)的封裝類由于本質(zhì)的不同,具有一些區(qū)別:

  • 基本數(shù)據(jù)類型只能按值傳遞,而封裝類按引用傳遞。
  • 基本類型在堆棧中創(chuàng)建;而對于對象類型,對象在堆中創(chuàng)建,對象的引用在堆棧中創(chuàng)建。基本類型由于在堆棧中,效率會比較高,但是可能會存在內(nèi)存泄漏的問題。

Java的四種引用(強(qiáng)弱軟虛),應(yīng)用場景

在JDK1.2后,java對引用的概念進(jìn)行了擴(kuò)充。按照引用強(qiáng)度依次從強(qiáng)到弱分為:強(qiáng)引用、軟引用(SoftReference)、弱引用(WeakReference)、虛引用(PhantomReference)用四種。

強(qiáng)引用:最常見的,不會被GC回收的對象,如 Object obj = new Object();

軟引用:可有可無的對象,如果內(nèi)存空間足夠,GC就不會去回收這個(gè)對象,如果內(nèi)存不足,就會回收,軟引用可有和ReferenceQueue(引用隊(duì)列)聯(lián)合使用,如果軟引用所引用的對象被GC回收,JVM就會把這個(gè)軟引用加入到引用隊(duì)列中。

public static void soft() throws Exception{
        Object obj = new Object();
        ReferenceQueue rq = new ReferenceQueue<>();
        SoftReference sr = new SoftReference(obj, rq);//創(chuàng)建關(guān)于obj的軟引用,使用引用隊(duì)列
        System.out.println(sr.get());   //get方法會輸出這個(gè)obj對象的hashcode
        System.out.println(rq.poll());  //輸出為null
        obj = null;
        System.gc();
        Thread.sleep(200);          //因?yàn)閒inalizer線程優(yōu)先級很低,所以讓線程等待200ms
        System.out.println(sr.get());   //因?yàn)槎芽臻g沒滿,可有可無的特性,所以還是會輸出這個(gè)obj對象的hashcode
        
        System.out.println(rq.poll());  //自然隊(duì)列為null
    }

弱引用:也是描述可有可無的對象,和軟引用不同的是,它的生命周期更短,在GC的過程中,一旦發(fā)現(xiàn)有弱引用的對象,不管當(dāng)前內(nèi)存空間是否足夠,都會回收它的內(nèi)存。 真是因?yàn)檫@個(gè)特性,所以弱引用常用于Map數(shù)據(jù)結(jié)構(gòu)中,引用占用空間內(nèi)存較大的對象。

public static void weak() throws Exception{
        Object obj = new Object();
        ReferenceQueue rq = new ReferenceQueue<>();
        WeakReference wr = new WeakReference(obj,rq);       
        System.out.println(wr.get());
        System.out.println(rq.poll());
        obj = null;
        System.gc();
        Thread.sleep(200);
        System.out.println(wr.get());   //這時(shí)候會輸出null
        System.out.println(rq.poll());  //rq隊(duì)列里也會存放這個(gè)弱引用,輸出它的hashcode
    }

虛引用:也叫幽靈引用,他的構(gòu)造方法必須傳遞RefenceQueue參數(shù),當(dāng)GC準(zhǔn)備回收一個(gè)對象時(shí),發(fā)現(xiàn)它還有虛引用,就會在回收前,把虛引用加入到引用隊(duì)列中,程序可以通過判斷隊(duì)列中是否加入虛引用來判斷被引用的對象是否將要GC回收,從而可以在finalize方法中采取措施。

Excption與Error包結(jié)構(gòu)。OOM你遇到過哪些情況,SOF你遇到過哪些情況

image

詳見: http://www.itdecent.cn/p/e7069f363baa

Override(重寫) 和 Overload(重載)的含義與區(qū)別

1. 綜述
重寫(Override)也稱覆蓋,它是父類與子類之間多態(tài)性的一種表現(xiàn),而重載(Overload)是一個(gè)類中多態(tài)性的一種表現(xiàn)。 override從字面就可以知道,它是覆蓋了一個(gè)方法并且對其重寫,以求達(dá)到不同的作用。overload它是指我們可以定義一些名稱相同的方法,通過定義不同的輸入?yún)?shù)來區(qū)分這些方法,然后再調(diào)用時(shí),VM就會根據(jù)不同的參數(shù)樣式,來選擇合適的方法執(zhí)行。

2. override(重寫,覆蓋)
(1)方法名、參數(shù)、返回值相同。
(2)子類方法不能縮小父類方法的訪問權(quán)限。
(3)子類方法不能拋出比父類方法更多的異常(但子類方法可以不拋出異常)。
(4)存在于父類和子類之間。
(5)方法被定義為final不能被重寫。
(6)被覆蓋的方法不能為private,否則在其子類中只是新定義了一個(gè)方法,并沒有對其進(jìn)行覆蓋。

3. overload(重載,過載)
(1)參數(shù)類型、個(gè)數(shù)、順序至少有一個(gè)不相同。
(2)不能重載只有返回值不同的方法名。
(3)針對于一個(gè)類而言。
(4)不能通過訪問權(quán)限、返回類型、拋出的異常進(jìn)行重載;
(5)方法的異常類型和數(shù)目不會對重載造成影響;

4. override應(yīng)用:
(1)最熟悉的覆蓋就是對接口方法的實(shí)現(xiàn),在接口中一般只是對方法進(jìn)行了聲明,而我們在實(shí)現(xiàn)時(shí),就需要實(shí)現(xiàn)接口聲明的所有方法。
(2)除了這個(gè)典型的用法以外,我們在繼承中也可能會在子類覆蓋父類中的方法。

5. 總結(jié)
  override是在不同類之間的行為,overload是在同一個(gè)類中的行為。

interface 和 abstract類的區(qū)別

抽象類(Abstract class):含有abstract修飾符的class即為抽象類。

(1)abstract class不能創(chuàng)建實(shí)例對象;
(2)含有abstract方法的類必須定義為abstract class,但abstract class類中的方法不必是抽象的;
(3)abstract類中定義的抽象方法必須在具體子類中實(shí)現(xiàn),所以不能有抽象的構(gòu)造方法和抽象的靜態(tài)方法;

【解析:針對不能有抽象的構(gòu)造方法---構(gòu)造方法不能被繼承,而抽象類的所有抽象方法必須在具體的子類中實(shí)現(xiàn),否則就不能實(shí)例化,所以構(gòu)造方法不能為abstract;針對不能有抽象的靜態(tài)方法---static修飾的方法在類加載之后、實(shí)例化之前就已經(jīng)分配了內(nèi)存,而抽象類不能實(shí)例化,所以矛盾】
(4)如果子類沒有實(shí)現(xiàn)父類的abstract方法,那么子類也必須定義為abstract類型;

接口(Interface):可以說是抽象類的一種特例,即接口中的所有方法都必須是抽象的。

(1)接口中的方法定義默認(rèn)為public abstract類型;

(2)接口中的變量定義默認(rèn)為public static final類型;

具體區(qū)別

(1)抽象類可以有構(gòu)造方法,但接口不能有構(gòu)造方法;

(2)抽象類可以有普通成員變量,但接口沒有普通成員變量;

(3)抽象類中可以包含非抽象的普通方法,而接口中的方法必須都是抽象的、不能有非抽象的方法;

(4)抽象類中的抽象方法的訪問類型可以是public、protected,但接口中的抽象方法只能是public類型且默認(rèn)為public abstract類型;

(5)抽象類中可以包含靜態(tài)方法,而接口中不能包含靜態(tài)方法;

(6)抽象類和接口都可以包含靜態(tài)成員變量,抽象類的靜態(tài)成員變量的訪問類型可以任意,但接口中定義的變量只能是public static final類型,并且默認(rèn)為public static final類型;

(7)一個(gè)類可以實(shí)現(xiàn)多個(gè)接口,但只能繼承一個(gè)抽象類;

Static class 與non static class的區(qū)別

外部類只能使用public、final、abstract修飾,不能使用private、protected、static修飾,但是內(nèi)部類可以。非靜態(tài)內(nèi)部類不能擁有靜態(tài)成員。
內(nèi)部類的作用:①不允許同包的其他類訪問該類;②內(nèi)部類成員可以直接訪問外部類私有數(shù)據(jù);③匿名內(nèi)部類適合用于創(chuàng)建那些僅需要使用一次的類。
非靜態(tài)內(nèi)部類可以訪問外部類的private成員,但非靜態(tài)內(nèi)部類的成員不能被外部類直接使用,如需訪問則必須要創(chuàng)建非靜態(tài)內(nèi)部類的對象進(jìn)行訪問。
非靜態(tài)內(nèi)部類訪問變量x,首先判斷是否存在局部變量x,如果存在則使用該變量;如果沒有,判斷是否存在非靜態(tài)內(nèi)部類成員變量x,如果存在則使用該變量;如果沒有,判斷是否存在外部類成員變量x,如果存在則使用該變量;如果沒有,系統(tǒng)出現(xiàn)編譯錯(cuò)誤。
如果外部類成員變量、內(nèi)部類成員變量和局部變量重名,則通過外部類類名.this.變量名、this.變量名和變量名區(qū)分。
靜態(tài)內(nèi)部類,使用static修飾的內(nèi)部類,這種內(nèi)部類屬于外部類本身,而不是外部類的對象,因此又叫類內(nèi)部類。
靜態(tài)內(nèi)部類可以包含靜態(tài)成員和非靜態(tài)成員。
靜態(tài)內(nèi)部類(即使時(shí)實(shí)例成員)不能訪問外部類的實(shí)例成員,只能訪問外部類的靜態(tài)成員。
接口內(nèi)部類只能時(shí)靜態(tài)內(nèi)部類。

繼承的好處和壞處

A:繼承的好處

a:提高了代碼的復(fù)用性
b:提高了代碼的維護(hù)性
c:讓類與類之間產(chǎn)生了關(guān)系,是多態(tài)的前提

B:繼承的弊端

類的耦合性增強(qiáng)了。
打破了封裝,因?yàn)榛愊蜃宇惐┞读藢?shí)現(xiàn)細(xì)節(jié)
白盒重用,因?yàn)榛惖膬?nèi)部細(xì)節(jié)通常對子類是可見的
當(dāng)父類的實(shí)現(xiàn)改變時(shí)可能要相應(yīng)的對子類做出改變
不能在運(yùn)行時(shí)改變由父類繼承來的實(shí)現(xiàn)

開發(fā)的原則:高內(nèi)聚,低耦合。
耦合:類與類的關(guān)系
內(nèi)聚:就是自己完成某件事情的能力

多態(tài)的實(shí)現(xiàn)原理

詳細(xì)解釋:https://www.cnblogs.com/startRuning/p/5673485.html
多態(tài)的概念:同一操作作用于不同對象,可以有不同的解釋,有不同的執(zhí)行結(jié)果,這就是多態(tài),簡單來說就是:父類的引用指向子類對象。

JAVA使用了后期綁定的概念。當(dāng)向?qū)ο蟀l(fā)送消息時(shí),在編譯階段,編譯器只保證被調(diào)用方法的存在,并對調(diào)用參數(shù)和返回類型進(jìn)行檢查,但是并不知道將被執(zhí)行的確切代碼,被調(diào)用的代碼直到運(yùn)行時(shí)才能確定。

將一個(gè)方法調(diào)用同一個(gè)方法主體關(guān)聯(lián)起來被稱作綁定,JAVA中分為前期綁定和后期綁定(動態(tài)綁定或運(yùn)行時(shí)綁定),在程序執(zhí)行之前進(jìn)行綁定(由編譯器和連接程序?qū)崿F(xiàn))叫做前期綁定,因?yàn)樵诰幾g階段被調(diào)用方法的直接地址就已經(jīng)存儲在方法所屬類的常量池中了,程序執(zhí)行時(shí)直接調(diào)用,具體解釋請看最后參考資料地址。后期綁定含義就是在程序運(yùn)行時(shí)根據(jù)對象的類型進(jìn)行綁定,想實(shí)現(xiàn)后期綁定,就必須具有某種機(jī)制,以便在運(yùn)行時(shí)能判斷對象的類型,從而找到對應(yīng)的方法,簡言之就是必須在對象中安置某種“類型信”,JAVA中除了static方法、final方法(private方法屬于)之外,其他的方法都是后期綁定。后期綁定會涉及到JVM管理下的一個(gè)重要的數(shù)據(jù)結(jié)構(gòu)——方法表,方法表以數(shù)組的形式記錄當(dāng)前類及其所有父類的可見方法字節(jié)碼在內(nèi)存中的直接地址。

動態(tài)綁定具體的調(diào)用過程為:

1.首先會找到被調(diào)用方法所屬類的全限定名

2.在此類的方法表中尋找被調(diào)用方法,如果找到,會將方法表中此方法的索引項(xiàng)記錄到常量池中(這個(gè)過程叫常量池解析),如果沒有,編譯失敗。

3.根據(jù)具體實(shí)例化的對象找到方法區(qū)中此對象的方法表,再找到方法表中的被調(diào)用方法,最后通過直接地址找到字節(jié)碼所在的內(nèi)存空間。

最后說明,域和靜態(tài)方法都是不具有多態(tài)性的,任何的域訪問操作都將由編譯器解析,因此不是多態(tài)的。靜態(tài)方法是跟類,而并非單個(gè)對象相關(guān)聯(lián)的。

jvm classLoader architecture

詳解:https://www.cnblogs.com/kaikailele/p/3916320.html
當(dāng)JVM(Java虛擬機(jī))啟動時(shí),會形成由三個(gè)類加載器組成的初始類加載器層次結(jié)構(gòu):

   bootstrap classloader
            |
   extension classloader
            |
   system classloader

bootstrap classloader -引導(dǎo)(也稱為原始)類加載器,它負(fù)責(zé)加載Java的核心類。在Sun的JVM中,在執(zhí)行java的命令中使用-Xbootclasspath選項(xiàng)或使用- D選項(xiàng)指定sun.boot.class.path系統(tǒng)屬性值可以指定附加的類。這個(gè)加載器的是非常特殊的,它實(shí)際上不是 java.lang.ClassLoader的子類,而是由JVM自身實(shí)現(xiàn)的。

extension classloader -擴(kuò)展類加載器,它負(fù)責(zé)加載JRE的擴(kuò)展目錄(JAVA_HOME/jre/lib/ext或者由java.ext.dirs系統(tǒng)屬性指定的)中JAR的類包。這為引入除Java核心類以外的新功能提供了一個(gè)標(biāo)準(zhǔn)機(jī)制。因?yàn)槟J(rèn)的擴(kuò)展目錄對所有從同一個(gè)JRE中啟動的JVM都是通用的,所以放入這個(gè)目錄的 JAR類包對所有的JVM和system classloader都是可見的。

system classloader -系統(tǒng)(也稱為應(yīng)用)類加載器,它負(fù)責(zé)在JVM被啟動時(shí),加載來自在命令java中的-classpath或者java.class.path系統(tǒng)屬性或者 CLASSPATH操作系統(tǒng)屬性所指定的JAR類包和類路徑??偰芡ㄟ^靜態(tài)方法ClassLoader.getSystemClassLoader()找到該類加載器。如果沒有特別指定,則用戶自定義的任何類加載器都將該類加載器作為它的父加載器。

classloader 加載類用的是全盤負(fù)責(zé)委托機(jī)制。所謂全盤負(fù)責(zé),即是當(dāng)一個(gè)classloader加載一個(gè)Class的時(shí)候,這個(gè)Class所依賴的和引用的所有 Class也由這個(gè)classloader負(fù)責(zé)載入,除非是顯式的使用另外一個(gè)classloader載入;委托機(jī)制則是先讓parent(父)類加載器 (而不是super,它與parent classloader類不是繼承關(guān)系)尋找,只有在parent找不到的時(shí)候才從自己的類路徑中去尋找。此外類加載還采用了cache機(jī)制,也就是如果 cache中保存了這個(gè)Class就直接返回它,如果沒有才從文件中讀取和轉(zhuǎn)換成Class,并存入cache,這就是為什么我們修改了Class但是必須重新啟動JVM才能生效的原因。

每個(gè)ClassLoader加載Class的過程是:

  1. 檢測此Class是否載入過(即在cache中是否有此Class),如果有到8,如果沒有到2
  2. 如果parent classloader不存在(沒有parent,那parent一定是bootstrap classloader了),到4
  3. 請求parent classloader載入,如果成功到8,不成功到5
  4. 請求jvm從bootstrap classloader中載入,如果成功到8
  5. 尋找Class文件(從與此classloader相關(guān)的類路徑中尋找)。如果找不到則到7.
  6. 從文件中載入Class,到8.
  7. 拋出ClassNotFoundException.
  8. 返回Class.

阻塞和非阻塞與異步和同步的理解

詳解: https://www.linuxidc.com/Linux/2015-07/120338.htm

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 在一個(gè)方法內(nèi)部定義的變量都存儲在棧中,當(dāng)這個(gè)函數(shù)運(yùn)行結(jié)束后,其對應(yīng)的棧就會被回收,此時(shí),在其方法體中定義的變量將不...
    Y了個(gè)J閱讀 4,585評論 1 14
  • 一、基礎(chǔ)知識:1、JVM、JRE和JDK的區(qū)別:JVM(Java Virtual Machine):java虛擬機(jī)...
    殺小賊閱讀 2,576評論 0 4
  • 段某應(yīng)聘進(jìn)某教育培訓(xùn)機(jī)構(gòu)擔(dān)任帶班老師,雙方約定段某每月基本工資為4200元,然后根據(jù)招生情況以及帶班情況每月還有不...
    daring婧閱讀 177評論 0 0
  • 每個(gè)在你生命中出現(xiàn)的人,都必然會對你產(chǎn)生一定的影響。有些人在你的心里存在著分量,而有些人卻只是匆匆過客。我感...
    可桐閱讀 629評論 4 8
  • 江南的小河邊從來不缺氤氳的霧嵐。在似有還無的牛毛細(xì)雨中,一個(gè)人緩緩的走著,不時(shí)低頭看一眼腳下有些濕滑的苔痕。不經(jīng)意...
    微行天下閱讀 1,065評論 0 3

友情鏈接更多精彩內(nèi)容