不只是簡單的Java面試知識點匯總

面向對象的三大特征

  • 封裝
    把客觀事物封裝成抽象的類,并且類可以把自己的數據和方法只讓可信的類或對象操作,對不可信的信息進行隱藏。
  • 繼承
    對象的繼承關系代表了一種‘is a’的關系,比如A和B,可以描述為‘B是A’,表明B繼承A。
  • 多態
    接口的多種不同的實現方式即為多態。

面向對象和面向過程的區別

面向過程:
  • 優點:性能比面向對象高,因為類調用時需要實例化,開銷比較大,比較消耗資源;比如單片機、嵌入式開發、Linux/Unix等一般采用面向過程開發,性能是最重要的因素。
  • 缺點:沒有面向對象易維護、易復用、易擴展
面向對象:
  • 優點:易維護、易復用、易擴展,由于面向對象有封裝、繼承、多態性的特性,可以設計出低耦合的系統,使系統更加靈活、更加易于維護
  • 缺點:性能比面向過程低

Java代碼的運行

源代碼->編譯器將源代碼編譯為字節碼->JVM(Java虛擬機) 解釋器將字節碼解釋為可執行的二進制機器碼->程序運行

JDK與JRE

JDK是Sun Microsystems針對Java開發人員發布的免費軟件開發工具包(SDK,Software development kit)。除JRE(Java Runtime Environment),也就是Java運行環境外還包含提供給開發者使用的javac(編譯器)、jar(打包器)、javadoc(文檔生成器)等工具包。

重載(overloading)與重寫(overriding)

  • 重載
    1.是在一個類里面,方法名字相同,而參數不同。返回類型可以相同也可以不同。
    2.每個重載的方法(或者構造函數)都必須有一個獨一無二的參數類型列表。
    3.最常用的地方就是構造器的重載。
  • 重寫
    1.重寫是子類對父類的允許訪問的方法的實現過程進行重新編寫, 返回值和形參都不能改變。
    2.重寫方法不能拋出新的檢查異常或者比被重寫方法申明更加寬泛的異常。
    把客觀事物封裝成抽象的類,并且類可以把自己的數據和方法只讓可信的類或對象操作,對不可信的信息進行隱藏。

equals與==的區別

1.對于字符串變量來說,使用“==”和“equals()”方法比較字符串時,其比較方法不同。“==”比較兩個變量本身的值,即兩個對象在內存中的首地址。“equals()”比較字符串中所包含的內容是否相同。
2.對于非字符串變量來說,"=="和"equals"方法的作用是相同的都是用來比較其對象在堆內存的首地址,即用來比較兩個引用變量是否指向同一個對象。

String、StringBuff、StringBuild的區別

  • String類保存字符串的方式為:private final char value[],所以string對象不可變。StringBuilder與StringBuffer都繼承自AbstractStringBuilder類,在AbstractStringBuilder中保存字符串的方式為char[] value,所以StringBuilder對象與StringBuilder對象可變。
  • String是常量,線程安全。StringBuff,對調用方法加了同步鎖(synchronized)因此線程安全,StringBuild,未加鎖因此線程不安全。
  • String改變會生成新的String對象,相對效率較低,StringBuff與StringBuild改變不會產生新的對象,并且StringBuild未加鎖,因此效率最高,但是可能存在線程安全的問題。操作少量數據時,使用String對象,操作大量數據,單線程時使用StringBuild,多線程時使用StringBuff。

類成員訪問修飾符

訪問修飾符 同一個類 同包 不同包,子類 不同包,非子類
private
protected
public
默認

”static”關鍵字是什么意思?Java中是否可以覆蓋(override)一個static方法?

“static”關鍵字表明一個成員變量或者是成員方法可以在沒有所屬的類的實例變量的情況下被訪問。Java中static方法不能被覆蓋,因為方法覆蓋是基于運行時動態綁定的,而static方法是編譯時靜態綁定的。static方法跟類的任何實例都不相關,所以概念上不適用。

Java語言支持的8中基本數據類型對應的長度、對應的包裝類

基本數據類型 長度(字節) 包裝類 默認值
boolean 1 Boolean false
byte 1 Byte (byte)0
char 2 Character '/uoooo'(null)
short 2 Short (short)0
int 4 Integer 0
long 8 Long 0L
float 4 Float 0.0f
double 8 Double 0.0d

接口和抽象類的區別

  • 類可以實現多個接口,但只能繼承一個抽象類
  • 接口中所有的方法都是抽象的。而抽象類則可以包含非抽象的方法。
  • 都不可被實例化,但抽象類如果包含main方法是可以被調用的。

final、finally、finalize

  • final: 常量聲明。final類無法繼承,final方法無法重寫,final值無法改變。
  • finally: 處理異常。 不管有無異常都會執行的塊,關閉連接通常在其中完成。
  • finalize: 幫助進行垃圾回收。finalize()方法在一個對象被銷毀和回收前會被調用。

native方法是什么?

native方法是非Java代碼實現的方法。

如何原地交換兩個變量的值?

  1. 加減法
int a = 5,b = 10;
a = a + b;
b = a - b;
a = a - b;

同理可用乘除法。
注意類型范圍,防止溢出。

  1. 異或法


int a = 5,b = 10;
a = a ^ b;//1111 = 101 ^ 1010;
b = b ^ a;
a = a ^ b;

用最有效率的方法計算 2 乘以 8

2 << 3(左移3位相當于乘以2的3次方,右移3位相當于除以2的3次方)。

集合框架中的泛型有什么優點?

Java1.5 引入了泛型,所有的集合接口和實現都大量地使用它。泛型允許我們為集合提供一個可以容納的對象類型。因此,如果你添加其它類型的任何元素,它會在編譯時報錯。這避免了在運行時出現 ClassCastException,因為你將會在編譯時得到報錯信息。泛型也使得代碼整潔,我們不需要使用顯式轉換和 instanceOf 操作符。它也給運行時帶來好處,因為不會產生類型檢查的字節碼指令。

Java 集合框架的基礎接口有哪些?

  • Collection 為集合層級的根接口。一個集合代表一組對象,這些對象即為它的元素。Java 平臺不提供這個接口任何直接的實現。
  • Set 是一個不能包含重復元素的集合。這個接口對數學集合抽象進行建模,被用來代表集合,就如一副牌。
  • List 是一個有序集合,可以包含重復元素。你可以通過它的索引來訪問任何元素。List 更像長度動態變換的數組。
  • Map 是一個將 key 映射到 value 的對象。一個 Map 不能包含重復的 key,每個 key 最多只能映射一個 value。盡管 Map 接口和它的實現也是集合框架的一部分,但 Map 不是集合,集合也不是 Map。因此,Map 繼承 Collection 毫無意義,反之亦然。如果 Map 繼承 Collection 接口,那么元素去哪兒?Map 包含key-value 對,它提供抽取 key 或 value 列表集合的方法,但是它不適合“一組對象”規范。
  • 一些其它的接口有 Queue、Dequeue、SortedSet、SortedMap 和 ListIterator。


    集合框架圖

Iterator 和 ListIterator 的區別是什么?

  • Iterator 可用來遍歷 Set 和 List 集合,但是 ListIterator 只能用來遍歷 List。
  • Iterator 對集合只能是前向遍歷,ListIterator 既可以前向也可以后向。
  • ListIterator 實現了 Iterator 接口,并包含其他的功能。比如:增加元素,替換元素,獲取前一個和后一個元素的索引等等。

Java 中的 HashMap 的工作原理是什么?

HashMap數據結構

簡單來說,HashMap由數組+鏈表組成的,數組是HashMap的主體,鏈表則是主要為了解決哈希沖突而存在的,如果定位到的數組位置不含鏈表(當前entry的next指向null),那么對于查找,添加等操作很快,僅需一次尋址即可;如果定位到的數組包含鏈表,對于添加操作,其時間復雜度為O(n),首先遍歷鏈表,存在即覆蓋,否則新增;對于查找操作來講,仍需遍歷鏈表,然后通過key對象的equals方法逐一比對查找。所以,性能考慮,HashMap中的鏈表出現越少,性能才會越好。

HashMap 和 HashTable 有什么區別?

  • HashMap 是非線程安全的,HashTable 是線程安全的。
  • HashMap 的鍵和值都允許有 null 值存在,而 HashTable 則不行。
  • 因為線程安全的問題,HashMap 效率比 HashTable 的要高。
  • HashTable 是同步的,而 HashMap 不是。因此,HashMap 更適合于單線程環境,而 HashTable 適合于多線程環境。
  • 一般現在不建議用 HashTable,一是 HashTable 是遺留類,內部實現很多沒優化和冗余。二是即使在多線程環境下,現在也有同步的 ConcurrentHashMap 替代,沒有必要因為是多線程而用 HashTable。

ConcurrentHashMap的并發度是什么?

ConcurrentHashMap把實際map劃分成若干部分來實現它的可擴展性和線程安全。這種劃分是使用并發度獲得的,它是ConcurrentHashMap類構造函數的一個可選參數,ConcurrentHashMap的并發度就是segment的大小,默認值為16,這意味著最多同時可以有16條線程操作ConcurrentHashMap,這樣在多線程情況下就能避免爭用。

快速失敗(fail-fast)和安全失敗(fail-safe)的區別是什么?

  • 快速失敗:當你在迭代一個集合的時候,如果有另一個線程正在修改你正在訪問的那個集合時,就會拋出一個 ConcurrentModification 異常。 在 java.util 包下的都是快速失敗。
  • 安全失敗:你在迭代的時候會去底層集合做一個拷貝,所以你在修改上層集合的時候是不會受影響的,不會拋出 ConcurrentModification 異常。在java.util.concurrent 包下的全是安全失敗的。

值傳遞與引用傳遞

  1. 值傳遞
    對象被值傳遞,意味著傳遞了對象的一個副本。因此,就算是改變了對象副本,也不會影響源對象的值。我們可以來看下面的一個例子:
        public class Break {
            public static void change(int a) {
                a = 1;
            }
            public static void main(String[] args) {
                int a = 2;
                System.out.println(a);
                change(a);
                System.out.println(a);
            }
        }

輸出結果是: 2 2
這個只是傳遞一份拷貝,和a的值沒有什么關系,也可以看成是方法change的值沒有一個變量來接收。
2.引用傳遞
對象被引用傳遞,意味著傳遞的并不是實際的對象,而是對象的引用。因此,外部對引用對象所做的改變會反映到所有的對象上。

        public class Break {
            public static void change(int[] a) {
                a[0] = 3;
            }
            public static void main(String[] args) {
                int[] a = {1, 2};
                System.out.println(a[0]);
                change(a);
                System.out.println(a[0]);
            }
        }

輸出結果是: 1 3
這個傳遞的,就是實際傳遞的是引用的地址值。所以a[0]的值會改變。

什么是線程?線程和進程區別在哪?

  • 線程是操作系統能夠進行運算調度的最小單位,它被包含在進程之中,是進程中的實際運作單位。
  • 線程是進程的子集,一個進程可以有很多線程,每條線程并行執行不同的任務。不同的進程使用不同的內存空間,而所有的線程共享一片相同的內存空間。每個線程都擁有單獨的棧內存用來存儲本地數據。

Java中用到的線程調度算法是什么?

搶占式。一個線程用完CPU之后,操作系統會根據線程優先級、線程饑餓情況等數據算出一個總的優先級并分配下一個時間片給某個線程執行。

如何實現多線程?

java.lang.Thread 類的實例就是一個線程,但是它需要調用java.lang.Runnable接口來執行,由于線程類本身就是調用的Runnable接口,所以你可以繼承java.lang.Thread 類或者直接調用Runnable接口來重寫run()方法實現線程。

用Thread還是用Runnable?

大家都知道我們可以通過繼承Thread類或者調用Runnable接口來實現線程,問題是,那個方法更好呢?什么情況下使用它?這個問題很容易回答,如果你知道Java不支持類的多重繼承,但允許你調用多個接口。所以如果你要繼承其他類,當然是調用Runnable接口好了。

Thread 類中的start() 和 run() 方法有什么區別?

這個問題經常被問到,但還是能從此區分出面試者對Java線程模型的理解程度。start()方法被用來啟動新創建的線程,而且start()內部調用了run()方法,這和直接調用run()方法的效果不一樣。當你調用run()方法的時候,只會是在原來的線程中調用,沒有新的線程啟動,start()方法才會啟動新線程。

Runnable和Callable有什么不同?

使用ExecutorService、Callable、Future可以實現有返回結果的多線程。
Runnable和Callable都代表那些要在不同的線程中執行的任務。Runnable從JDK1.0開始就有了,Callable是在JDK1.5增加的。它們的主要區別是Callable的 call() 方法可以返回值和拋出異常,而Runnable的run()方法沒有這些功能。Callable可以返回裝載有計算結果的Future對象。

常用線程池

  1. newCachedThreadPool創建一個可緩存線程池程
  2. newFixedThreadPool 創建一個定長線程池
  3. newScheduledThreadPool 創建一個定長線程池,支持定時及周期性任務執行
  4. newSingleThreadExecutor 創建一個單線程化的線程池


    線程池工作流程圖.png

sleep和wait的區別

  • sleep是Thread線程類的方法,而wait是Object頂級類的方法。
  • sleep可以在任何地方使用,而wait只能在同步方法或者同步塊中使用。
  • sleep,wait調用后都會暫停當前線程并讓出cpu的執行時間,但不同的是sleep不會釋放當前持有的對象的鎖資源,到時間后會繼續執行,而wait會放棄所有鎖并需要notify/notifyAll后重新獲取到對象鎖資源后才能繼續執行。
  • sleep需要捕獲或者拋出異常,而wait/notify/notifyAll不需要。

如何強制啟動一個線程?

這個問題就像是如何強制進行Java垃圾回收,目前還沒有可靠方法,雖然你可以使用System.gc()來進行垃圾回收,但是不保證能成功。在Java里面沒有辦法強制啟動一個線程,它是被線程調度器控制著且Java沒有公布相關的API。

volatile關鍵字的作用是什么?

  • 多線程使用volatile關鍵字修飾的變量,保證了其在多線程之間的可見性,即每次讀取到volatile變量,一定是最新的數據。
  • Java代碼執行中,為了獲取更好的性能JVM可能會對指令進行重排序,多線程下可能會出現一些意想不到的問題。使用volatile則會對禁止語義重排序,當然這也一定程度上降低了代碼執行效率。

什么是樂觀鎖和悲觀鎖?

  1. 樂觀鎖:對于并發間操作產生的線程安全問題持樂觀狀態,樂觀鎖認為競爭不總是會發生,因此它不需要持有鎖,將比較-設置這兩個動作作為一個原子操作嘗試去修改內存中的變量,如果失敗則表示發生沖突,那么就應該有相應的重試邏輯。
  2. 悲觀鎖:對于并發間操作產生的線程安全問題持悲觀狀態,悲觀鎖認為競爭總是會發生,因此每次對某資源進行操作時,都會持有一個獨占的鎖,就像synchronized,直接對操作資源上了鎖。

共享鎖和排它鎖

  • 共享鎖【S鎖,MyISAM 叫做讀鎖】
    又稱讀鎖,若事務T對數據對象A加上S鎖,則事務T可以讀A但不能修改A,其他事務只能再對A加S鎖,而不能加X鎖,直到T釋放A上的S鎖。這保證了其他事務可以讀A,但在T釋放A上的S鎖之前不能對A做任何修改。
select * from table lock in share mode
  • 排他鎖【X鎖,MyISAM 叫做寫鎖】
    數據庫的增刪改操作默認都會加排他鎖,而查詢不會加任何鎖。
    又稱寫鎖。若事務T對數據對象A加上X鎖,事務T可以讀A也可以修改A,其他事務不能再對A加任何鎖,直到T釋放A上的鎖。這保證了其他事務在T釋放A上的鎖之前不能再讀取和修改A。
select * from table for update
  • 計劃鎖
  • 意向鎖
    • 意向共享鎖
    • 意向排它鎖

可重入鎖、可中斷鎖、公平鎖、讀寫鎖

  1. 可重入鎖實際是指鎖的分配機制:基于線程的分配,而不是基于方法調用的分配。synchronized和Lock都具備可重入性。
  2. 可中斷鎖,顧名思義,就是可以相應中斷的鎖。在Java中,synchronized就不是可中斷鎖,而Lock是可中斷鎖。
  3. 公平鎖即盡量以請求鎖的順序來獲取鎖。在Java中,synchronized就是非公平鎖,它無法保證等待的線程獲取鎖的順序。而對于ReentrantLock和ReentrantReadWriteLock,它默認情況下是非公平鎖,但是可以設置為公平鎖。
  4. 讀寫鎖將對一個資源(比如文件)的訪問分成了2個鎖,一個讀鎖和一個寫鎖。ReadWriteLock就是讀寫鎖,它是一個接口,ReentrantReadWriteLock實現了這個接口。可以通過readLock()獲取讀鎖,通過writeLock()獲取寫鎖。

表鎖、頁鎖、行鎖

在 Mysql 中,行級鎖并不是直接鎖記錄,而是鎖索引。索引分為主鍵索引和非主鍵索引兩種,如果一條sql 語句操作了主鍵索引,Mysql 就會鎖定這條主鍵索引;如果一條語句操作了非主鍵索引,MySQL會先鎖定該非主鍵索引,再鎖定相關的主鍵索引。
InnoDB 行鎖是通過給索引項加鎖實現的,如果沒有索引,InnoDB 會通過隱藏的聚簇索引來對記錄加鎖。也就是說:如果不通過索引條件檢索數據,那么InnoDB將對表中所有數據加鎖,實際效果跟表鎖一樣。因為沒有了索引,找到某一條記錄就得掃描全表,要掃描全表,就得鎖定表。

synchronized與lock的區別

public interface Lock {
    void lock(); //用來獲取鎖。如果鎖已被其他線程獲取,則進行等待。
    void lockInterruptibly() throws InterruptedException;//當通過這個方法去獲取鎖時,如果線程正在等待獲取鎖,則這個線程能夠響應中斷,即中斷線程的等待狀態。也就使說,當兩個線程同時通過lock.lockInterruptibly()想獲取某個鎖時,假若此時線程A獲取到了鎖,而線程B只有在等待,那么對線程B調用threadB.interrupt()方法能夠中斷線程B的等待過程。
    boolean tryLock();//方法是有返回值的,它表示用來嘗試獲取鎖,如果獲取成功,則返回true,如果獲取失敗(即鎖已被其他線程獲取),則返回false,也就說這個方法無論如何都會立即返回。在拿不到鎖時不會一直在那等待。
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;//和tryLock()方法是類似的,只不過區別在于這個方法在拿不到鎖時會等待一定的時間,在時間期限之內如果還拿不到鎖,就返回false。如果如果一開始拿到鎖或者在等待期間內拿到了鎖,則返回true。
    void unlock();//解鎖
    Condition newCondition();
}
  1. Lock是一個接口,而synchronized是Java中的關鍵字,synchronized是內置的語言實現;
  2. synchronized在發生異常時,會自動釋放線程占有的鎖,因此不會導致死鎖現象發生;而Lock在發生異常時,如果沒有主動通過unLock()去釋放鎖,則很可能造成死鎖現象,因此使用Lock時需要在finally塊中釋放鎖;
  3. Lock可以讓等待鎖的線程響應中斷,而synchronized卻不行,使用synchronized時,等待的線程會一直等待下去,不能夠響應中斷;
  4. 通過Lock可以知道有沒有成功獲取鎖,而synchronized卻無法辦到。
  5. Lock可以提高多個線程進行讀操作的效率。

在靜態方法和非靜態方法上加 Synchronized的區別

  • Synchronzied 修飾非靜態方法==》對象鎖。
  • Synchronzied 修飾靜態方法==》其實是類鎖,因為是靜態方法,它把整個類鎖起來了;

jdk1.8新特性

  • Lambda表達式
  • 函數式接口
  • 方法引用和構造器調用
  • Stream API
  • 接口中的默認方法和靜態方法
  • 新時間日期API

常見算法

  1. 二分查找
            public static int binSearch(int srcArray[], int key) {
                int mid = srcArray.length / 2;
                if (key == srcArray[mid]) {
                    return mid;
                }
                int start = 0;
                int end = srcArray.length - 1;
                while (start <= end) {
                    mid = (end - start) / 2 + start;
                    if (key < srcArray[mid]) {
                        end = mid - 1;
                    } else if (key > srcArray[mid]) {
                        start = mid + 1;
                    } else {
                        return mid;
                    }
                }
                return -1;
            }
  1. 冒泡排序
    public static void bubbleSort(int srcArray[]) {
    for (int i = 0; i < srcArray.length - 1; i++) {//外層循環控制排序趟數
      for (int j = 0; j < srcArray.length - 1 - i; j++) {//內層循環控制每一趟排序多少次
        if (srcArray[j] > srcArray[j + 1]) {
          int temp = srcArray[j];
          srcArray[j] = srcArray[j + 1];
          srcArray[j + 1] = temp;
        }
      }
    }
    }
  1. 快速排序
public static void quickSort(int[] arr, int low, int high) {
                int i, j, temp, t;
                if (low > high) {
                    return;
                }
                i = low;
                j = high;
                //temp就是基準位
                temp = arr[low];

                while (i < j) {
                    //先看右邊,依次往左遞減
                    while (temp <= arr[j] && i < j) {
                        j--;
                    }
                    //再看左邊,依次往右遞增
                    while (temp >= arr[i] && i < j) {
                        i++;
                    }
                    //如果滿足條件則交換
                    if (i < j) {
                        t = arr[j];
                        arr[j] = arr[i];
                        arr[i] = t;
                    }

                }
                //最后將基準為與i和j相等位置的數字交換
                arr[low] = arr[i];
                arr[i] = temp;
                //遞歸調用左半數組
                quickSort(arr, low, j - 1);
                //遞歸調用右半數組
                quickSort(arr, j + 1, high);
            }

常見數據結構

數據結構的物理存儲結構只有兩種:順序存儲結構和鏈式存儲結構(像棧,隊列,樹,圖等是從邏輯結構去抽象的,映射到內存中,也這兩種物理組織形式)。

  1. 線性表
    1. 數組
      采用一段連續的存儲單元來存儲數據。對于指定下標的查找,時間復雜度為O(1);通過給定值進行查找,需要遍歷數組,逐一比對給定關鍵字和數組元素,時間復雜度為O(n),當然,對于有序數組,則可采用二分查找,插值查找,斐波那契查找等方式,可將查找復雜度提高為O(logn);對于一般的插入刪除操作,涉及到數組元素的移動,其平均復雜度也為O(n)。
    2. 鏈表
      對于鏈表的新增,刪除等操作(在找到指定操作位置后),僅需處理結點間的引用即可,時間復雜度為O(1),而查找操作需要遍歷鏈表逐一進行比對,復雜度為O(n)。
  2. 棧與隊列
  3. 樹與二叉樹
    1. 二叉樹基本概念
    2. 二叉查找樹
    3. 平衡二叉樹
    4. 紅黑樹
      對一棵相對平衡的有序二叉樹,對其進行插入,查找,刪除等操作,平均復雜度均為O(logn)。
  4. Hash表
    在哈希表中進行添加,刪除,查找等操作,性能十分之高,不考慮哈希沖突的情況下,僅需一次定位即可完成,時間復雜度為O(1)。
    哈希沖突的解決方案有多種:開放定址法(發生沖突,繼續尋找下一塊未被占用的存儲地址),再散列函數法,鏈地址法。

常用設計模式

  1. 單例模式
    單例設計模式簡單說就是無論程序如何運行,采用單例設計模式的類(Singleton類)永遠只會有一個實例化對象產生。
        public class Singleton {
            private Singleton() {
            }

            private static class SingletonHolder {
                private final static Singleton instance = new Singleton();
            }

            public static Singleton getInstance() {
                return SingletonHolder.instance;
            }
        }
  1. 工廠模式
    程序在接口和子類之間加入了一個過渡端,通過此過渡端可以動態取得實現了共同接口的子類實例化對象。
  2. 代理模式
    指由一個代理主題來操作真實主題,真實主題執行具體的業務操作,而代理主題負責其他相關業務的處理。比如生活中的通過代理訪問網絡,客戶通過網絡代理連接網絡(具體業務),由代理服務器完成用戶權限和訪問限制等與上網相關的其他操作(相關業務)。

對稱加密與非對稱加密

  • 對稱加密:
    對稱加密是最快速、最簡單的一種加密方式,加密(encryption)與解密(decryption)用的是同樣的密鑰(secret key)。
    常用對稱加密算法:AES、IDEA
  • 非對稱加密:
    非對稱加密為數據的加密與解密提供了一個非常安全的方法,它使用了一對密鑰,公鑰(public key)和私鑰(private key)。
    常用非對稱加密算法:RSA、ElGamal

何謂RESTful?

RESTful(Representational State Transfer)架構風格,是一個Web自身的架構風格,底層主要基于HTTP協議(ps:提出者就是HTTP協議的作者),是分布式應用架構的偉大實踐理論。RESTful架構是無狀態的,表現為請求-響應的形式,有別于基于Bower的SessionId不同。

何謂MVC?

  • MVC是Model—View—Controler的簡稱,即模型—視圖—控制器。
  • 模型:處理。
  • 視圖:展示。
  • 控制器:接受。
  • 流程:控制器接受用戶發來的請求,調用相應模型處理邏輯,然后返回數據給控制器,控制器調用相應視圖展示結果給用戶。

SpringMVC工作流程

SpringMVC工作流程圖.jpg
  1. 客戶端請求提交到DispatcherServlet
  2. 由DispatcherServlet控制器查詢一個或多個HandlerMapping,找到處理請求的Controller
  3. DispatcherServlet將請求提交到Controller
  4. Controller調用業務邏輯處理后,返回ModelAndView
  5. DispatcherServlet查詢一個或多個ViewResoler視圖解析器,找到ModelAndView指定的視圖
  6. 視圖負責將結果顯示到客戶端

strus2與Spring MVC的區別

  1. Struts2是類級別上的攔截,一個類對應一個request上下文,SpringMVC是方法級別的攔截,一個方法對應一個request上下文。而且Struts過濾后是去Struts配置文件中找Action,而SpringMVC過濾后是去controller中找對應于@RequestMapping注解的url綁定的方法。
  2. 因為攔截器原因,導致Struts2的action比較亂,因為它要定義屬性來獲取請求中參數的數據,而屬性在一個類的方法間是共享的(方法間不能獨享request、response數據),所以會有點亂。而SpringMVC中請求參數與controller中方法的形參自動配對(在名字相同,或請求參數與形參的屬性名相同,或通過@RequestParam注解指定條件下會自動將請求參數的值賦給形參)方法間可以獨享request、response數據。
  3. SpringMVC集成了Ajax,使用非常方便,只需一個注解@ResponseBody就可以實現,然后直接返回響應文本即可,而Struts2攔截器集成了Ajax,在Action中處理時一般必須安裝插件或者自己寫代碼集成進去,使用起來也相對不方便。

SpringMVC常用參數綁定注解

  1. @RequestParam
  2. @RequestBody
  3. @RequestHeader
  4. @CookieValue
  5. @PathVariable

SpringMVC怎樣設定重定向和轉發的?

  1. 在返回值前面加"forward:"就可以讓結果轉發,譬如"forward:user.do?name=jianshu"
  2. 在返回值前面加"redirect:"就可以讓返回值重定向,譬如"redirect:http://www.baidu.com"

SpringIOC容器

Spring IOC負責創建對象、管理對象(通過依賴注入)、整合對象、配置對象以及管理這些對象的生命周期,在Spring中BeanFactory是IOC容器的實際代表者。

BeanFactory 接口和 ApplicationContext 接口有什么區別 ?

  1. ApplicationContext 接口繼承BeanFactory接口,Spring核心工廠是BeanFactory ,BeanFactory采取延遲加載,第一次getBean時才會初始化Bean, ApplicationContext是會在加載配置文件時初始化Bean。
  2. ApplicationContext是對BeanFactory擴展,它可以進行國際化處理、事件傳遞和bean自動裝配以及各種不同應用層的Context實現。
    開發中基本都在使用ApplicationContext, web項目使用WebApplicationContext ,很少用到BeanFactory。
BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
IHelloService helloService = (IHelloService) beanFactory.getBean("helloService");
helloService.sayHello();

IOC、DI

  • IOC(Inversion of Control):由IOC容器幫對象找相應的依賴對象并注入,而不是由對象主動去找。
  • DI(Dependency Injection):被注入對象依賴IOC容器配置依賴對象。

依賴注入的幾種方式

  1. set注入
    控制層代碼:
private OrderServiceImp orderService;
    
public void setOrderService(OrderServiceImp orderService) {
       this.orderService = orderService;
}

Spring配置XML文件:其中配置聲明OrderAction類存在屬性orderService。程序運行時候,會將已經實例化的orderService對象調用setOrderService方式注入。

<bean name="orderAction" class="com.pec.action.OrderAction">
        <property name="orderService" ref="orderService"></property>
</bean>
<bean name="orderService" class="com.pec.service.imp.OrderServiceImp"></bean>
  1. 構造器注入
    控制層代碼:
private OrderServiceImp orderService;
    
public OrderAction(OrderServiceImp orderService) {
        this.orderService = orderService;
    }

Spring配置XML文件:

<bean name="orderAction" class="com.pec.action.OrderAction">
      <constructor-arg ref="orderService"></constructor-arg>
</bean>
<bean name="orderService" class="com.pec.service.imp.OrderServiceImp"></bean>
  1. 注解注入

Spring中bean實例化有幾種方式?

  1. 使用類構造器實例化(默認無參數)
    <bean id="bean1" class="cn.jianshu.Bean1"></bean>
  2. 靜態工廠
    <bean id="bean2" class="cn.jianshu.Bean2Factory" factory-method="getBean2"></bean>
  3. 實例工廠
    <bean id="bean3Factory" class="cn.jianshu.Bean3Factory"></bean>
    <bean id="bean3" factory-bean="bean3Factory" factory-method="getBean3"></bean>

簡單說下Bean的生命周期

  1. bean定義
  2. bean初始化
    有兩種方式初始化:
    1. 在配置文件中通過指定init-method屬性來完成
    2. 實現org.springframwork.beans.factory.InitializingBean接口
  3. bean調用
    三種方式獲得bean實例(見上題)
  4. bean銷毀
    有兩種方式銷毀:
    1. 使用配置文件指定的destroy-method屬性
    2. 實現org.springframwork.bean.factory.DisposeableBean接口
      **注意:
      在配置 <bean> 元素,通過 init-method 指定Bean的初始化方法,通過 destroy-method 指定Bean銷毀方法
      <beanid="lifeCycleBean"class="cn.jianshu.LifeCycleBean"init-method="setup"destroy-method="teardown"></bean>
    • destroy-method 只對 scope="singleton" 有效
    • 銷毀方法,必須關閉ApplicationContext對象(手動調用),才會被調用
      ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
      applicationContext.close();**

Bean的作用域

  • singleton
    當一個bean的作用域為singleton, 那么Spring IoC容器中只會存在一個共享的bean實例,并且所有對bean的請求,只要id與該bean定義相匹配,則只會返回bean的同一實例。
  • prototype
    Prototype作用域的bean會導致在每次對該bean請求(將其注入到另一個bean中,或者以程序的方式調用容器的getBean() 方法)時都會創建一個新的bean實例。根據經驗,對所有有狀態的bean應該使用prototype作用域,而對無狀態的bean則應該使用 singleton作用域。
  • request
    在一次HTTP請求中,一個bean定義對應一個實例;即每次HTTP請求將會有各自的bean實例, 它們依據某個bean定義創建而成。該作用域僅在基于web的Spring ApplicationContext情形下有效。
  • session
    在一個HTTP Session中,一個bean定義對應一個實例。該作用域僅在基于web的Spring ApplicationContext情形下有效。
  • global session
    在一個全局的HTTP Session中,一個bean定義對應一個實例。典型情況下,僅在使用portlet context的時候有效。該作用域僅在基于web的Spring ApplicationContext情形下有效。

AOP的理解

  • 面向切面編程(AOP)提供另外一種角度來思考程序結構,通過這種方式彌補了面向對象編程(OOP)的不足,除了類(classes)以外,AOP提供了切面。切面對關注點進行模塊化,例如橫切多個類型和對象的事務管理。
  • Spring的一個關鍵的組件就是AOP框架,可以自由選擇是否使用AOP 提供聲明式企業服務,特別是為了替代EJB聲明式服務。最重要的服務是聲明性事務管理,這個服務建立在Spring的抽象事物管理之上。允許用戶實現自定義切面,用AOP來完善OOP的使用,可以把Spring AOP看作是對Spring的一種增強。


    AOP的理解圖.png

Spring里面的applicationContext.xml文件能不能改成其他名字?

ContextLoaderListener是一個ServletContextListener, 它在你的web應用啟動的時候初始化。缺省情況下, 它會在WEB-INF/applicationContext.xml文件找Spring的配置。 你可以通過定義一個<context-param>元素名字為”contextConfigLocation”來改變Spring配置文件的 位置。示例如下:
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/jianshu.xml</param-value>
</context-param>
</listener-class>
</listener>

Spring如何處理線程并發問題?

Spring使用ThreadLocal解決線程安全問題
我們知道在一般情況下,只有無狀態的Bean才可以在多線程環境下共享,在Spring中,絕大部分Bean都可以聲明為singleton作用域。就是因為Spring對一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非線程安全狀態采用ThreadLocal進行處理,讓它們也成為線程安全的狀態,因為有狀態的Bean就可以在多線程中共享了。
ThreadLocal和線程同步機制都是為了解決多線程中相同變量的訪問沖突問題。
在同步機制中,通過對象的鎖機制保證同一時間只有一個線程訪問變量。這時該變量是多個線程共享的,使用同步機制要求程序慎密地分析什么時候對變量進行讀寫,什么時候需要鎖定某個對象,什么時候釋放對象鎖等繁雜的問題,程序設計和編寫難度相對較大。
而ThreadLocal則從另一個角度來解決多線程的并發訪問。ThreadLocal會為每一個線程提供一個獨立的變量副本,從而隔離了多個線程對數據的訪問沖突。因為每一個線程都擁有自己的變量副本,從而也就沒有必要對該變量進行同步了。ThreadLocal提供了線程安全的共享對象,在編寫多線程代碼時,可以把不安全的變量封裝進ThreadLocal。
由于ThreadLocal中可以持有任何類型的對象,低版本JDK所提供的get()返回的是Object對象,需要強制類型轉換。但JDK5.0通過泛型很好的解決了這個問題,在一定程度地簡化ThreadLocal的使用。
概括起來說,對于多線程資源共享的問題,同步機制采用了“以時間換空間”的方式,而ThreadLocal采用了“以空間換時間”的方式。前者僅提供一份變量,讓不同的線程排隊訪問,而后者為每一個線程都提供了一份變量,因此可以同時訪問而互不影響。

如何解決GET、POST請求中文亂碼問題?

GET請求中文亂碼問題解決
  • 重新編碼參數
String name= new String(request.getParamter("name").getBytes("ISO8859-1"),"utf-8")
  • 修改tomcat中server.xml的配置
<Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" URIEncoding="utf-8"/>
POST請求中文亂碼問題解決
  • web.xml中配置字符編碼過濾器
<filter>
    <filter-name>CharacterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>utf-8</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>CharacterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

過濾器、監聽器、攔截器

過濾器

所謂過濾器顧名思義是用來過濾的,Java的過濾器能夠為我們提供系統級別的過濾,也就是說,能過濾所有的web請求,這一點,是攔截器無法做到的。在Java Web中,你傳入的request,response提前過濾掉一些信息,或者提前設置一些參數,然后再傳入servlet或
者struts的action進行業務邏輯,比如過濾掉非法url(不是login.do的地址請求,如果用戶沒有登陸都過濾掉),或者在傳入servlet或者struts的action前統一設置字符集,或者去除掉一些非法字符(聊天室經常用到的,一些罵人的話)。filter 流程是線性的,url傳來之后,檢查之后,可保持原來的流程繼續向下執行,被下一個filter, servlet接收。

監聽器

Java的監聽器,也是系統級別的監聽。監聽器隨web應用的啟動而啟動。Java的監聽器在c/s模式里面經常用到,它會對特定的事件產生產生一個處理。監聽在很多模式下用到,比如說觀察者模式,就是一個使用監聽器來實現的,在比如統計網站的在線人數。又比如struts2可以用監聽來啟動。Servlet監聽器用于監聽一些重要事件的發生,監聽器對象可以在事情發生前、發生后可以做一些必要的處理。

攔截器

java里的攔截器提供的是非系統級別的攔截,也就是說,就覆蓋面來說,攔截器不如過濾器強大,但是更有針對性。Java中的攔截器是基于Java反射機制實現的,更準確的劃分,應該是基于JDK實現的動態代理。它依賴于具體的接口,在運行期間動態生成字節碼。攔截器是動態攔截Action調用的對象,它提供了一種機制可以使開發者在一個Action執行的前后執行一段代碼,也可以在一個Action執行前阻止其執行,同時也提供了一種可以提取Action中可重用部分代碼的方式。在AOP中,攔截器用于在某個方法或者字段被訪問之前,進行攔截然后再之前或者之后加入某些操作。java的攔截器主要是用在插件上,擴展件上比如Hibernate Spring Struts2等,有點類似面向切片的技術,在用之前先要在配置文件即xml,文件里聲明一段的那個東西。

攔截器和過濾器的區別

  1. 攔截器是基于java的反射機制的,而過濾器是基于函數回調。
  2. 攔截器不依賴與servlet容器,過濾器依賴與servlet容器。
  3. 攔截器只能對action請求起作用,而過濾器則可以對幾乎所有的請求起作用。
  4. 攔截器可以訪問action上下文、值棧里的對象,而過濾器不能訪問。
  5. 在action的生命周期中,攔截器可以多次被調用,而過濾器只能在容器初始化時被調用一次。
  6. 攔截器可以獲取IOC容器中的各個bean,而過濾器就不行,這點很重要,在攔截器里注入一個service,可以調用業務邏輯。
  7. 攔截器需要在Spring配置文件中配置,過濾器只需要在web.xml中配置

為什么要有事務傳播行為?

為什么要有事務傳播行為圖.png

spring管理事務有幾種方式?

  1. 編程式事務,在代碼中硬編碼。(不推薦使用)
  2. 聲明式事務,在配置文件中配置(推薦使用)
    聲明式事務又分為兩種:
    1. 基于XML的聲明式事務
    2. 基于注解的聲明式事務

事務

事務圖.png

'#{}'和'${}'的區別是什么?

'#{}'是預編譯處理,{}是字符串替換。 Mybatis在處理#{}時,會將sql中的#{}替換為?號,調用PreparedStatement的set方法來賦值; Mybatis在處理{}時,就是把${}替換成變量的值。
使用#{}可以有效的防止SQL注入,提高系統安全性。

一對一、一對多關聯查詢

<mapper namespace="com.jianshu.userMapper">  
    <!--association  一對一關聯查詢 -->  
    <select id="getClass" parameterType="int" resultMap="ClassesResultMap">  
        select * from class c,teacher t where c.teacher_id=t.t_id and c.c_id=#{id}  
    </select>  
 
    <resultMap type="com.jianshu.Classes" id="ClassesResultMap">  
        <!-- 實體類的字段名和數據表的字段名映射 -->  
        <id property="id" column="c_id"/>  
        <result property="name" column="c_name"/>  
        <association property="teacher" javaType="com.jianshu.Teacher">  
            <id property="id" column="t_id"/>  
            <result property="name" column="t_name"/>  
        </association>  
    </resultMap>  

    <!--collection  一對多關聯查詢 -->  
    <select id="getClass2" parameterType="int" resultMap="ClassesResultMap2">  
        select * from class c,teacher t,student s where c.teacher_id=t.t_id and c.c_id=s.class_id and c.c_id=#{id}  
    </select>  
 
    <resultMap type="com.jianshu.Classes" id="ClassesResultMap2">  
        <id property="id" column="c_id"/>  
        <result property="name" column="c_name"/>  
        <association property="teacher" javaType="com.jianshu.Teacher">  
            <id property="id" column="t_id"/>  
            <result property="name" column="t_name"/>  
        </association>  
 
        <collection property="student" ofType="com.jianshu.Student">  
            <id property="id" column="s_id"/>  
            <result property="name" column="s_name"/>  
        </collection>  
    </resultMap>  
</mapper> 

MyBatis緩存

  • MyBatis的緩存分為一級緩存和二級緩存。
  • 一級緩存,SqlSession級別,默認開啟。
  • 二級緩存,namespace級別,需手動配置和開啟。
  1. 總配置文件開啟二級緩存。
  2. 映射文件添加<cache>標簽。
  3. 實體類實現序列化接口。

Spring Boot 的核心注解是哪個?它主要由哪幾個注解組成的?

核心注解是啟動類上的@SpringBootApplication
它由以下四個注解組成:

  1. @SpringBootConfiguration:組合了 @Configuration 注解,實現配置文件的功能。
  2. @EnableAutoConfiguration:打開自動配置的功能,也可以關閉某個自動配置的選項,如關閉數據源自動配置功能。
  3. @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })。
  4. @ComponentScan:Spring組件掃描。

SpringCloud五大組件

  1. 服務發現
    Netflix Eureka
  2. 客戶端負載均衡
    Netflix Ribbon
  3. 斷路器
    Netflix Hystri
  4. 服務網關
    Netflix Zuul
  5. 分布式配置
    Spring Cloud Config

dubbo支持的通信協議

  1. dubbo://
  2. rmi://
  3. hessian://
  4. http://
  5. webservice://
  6. thrift://
  7. memcached://
  8. redis://
    底層采用socket進行通信

CAP理論

任何一個分布式系統都無法同時滿足一致性(Consistency)、可用性(Availability)和分區容錯性(Partition tolerance),最多只能同時滿足兩項。

分布式環境下的session處理策略

  1. 粘性session
upstream mycluster{
    #這里添加的是上面啟動好的兩臺Tomcat服務器
    ip_hash;#粘性Session
     server 192.168.22.229:8080 weight=1;
     server 192.168.22.230:8080 weight=1;
}
  1. 服務器session復制
  2. session共享機制
    1. 粘性session處理方式


    2. 非粘性session處理方式


  3. session持久化到數據庫
  4. terracotta實現session復制


分布式事務

ByteTCC、LCN
阿里分布式事務框架GTS開源了一個免費社區版Fescar

FESCAR管理分布式事務的典型生命周期圖.png

分布式鎖

基于數據庫做分布式鎖
  1. 基于表主鍵唯一做分布式鎖
  2. 基于表字段版本號做分布式鎖
  3. 基于數據庫排他鎖做分布式鎖
基于 Redis 做分布式鎖
public class RedisTool {

    private static final String LOCK_SUCCESS = "OK";
    private static final String SET_IF_NOT_EXIST = "NX";
    private static final String SET_WITH_EXPIRE_TIME = "PX";

    /**
     * 嘗試獲取分布式鎖
     * @param jedis Redis客戶端
     * @param lockKey 鎖
     * @param requestId 請求標識
     * @param expireTime 超期時間
     * @return 是否獲取成功
     */
    public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {

        String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);

        if (LOCK_SUCCESS.equals(result)) {
            return true;
        }
        return false;
    }

}
public class RedisTool {

    private static final Long RELEASE_SUCCESS = 1L;

    /**
     * 釋放分布式鎖
     * @param jedis Redis客戶端
     * @param lockKey 鎖
     * @param requestId 請求標識
     * @return 是否釋放成功
     */
    public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {

        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));

        if (RELEASE_SUCCESS.equals(result)) {
            return true;
        }
        return false;

    }

}
  1. 基于 REDIS 的 SETNX()、EXPIRE() 方法做分布式鎖
  2. 基于 REDIS 的 SETNX()、GET()、GETSET()方法做分布式鎖
  3. 基于 REDLOCK 做分布式鎖
  4. 基于 REDISSON 做分布式鎖
基于 ZooKeeper 做分布式鎖
基于 Consul 做分布式鎖

RabbitMQ

fanout

direct

topic

headers

MyISAM與InnoDB的區別

  1. 存儲結構
  2. 存儲空間
  3. 可移值性、備份、恢復
  4. 事務支持
  5. AUTO_INCREMENT
  6. 表鎖差異
  7. 全文索引
  8. 表主鍵
  9. 表的具體行數
  10. CURD操作
  11. 外鍵
    MySQL默認采用的是MyISAM。

索引底層實現原理

  1. 索引的本質


  2. 二叉樹


  3. B樹



    4.B+樹


  4. 帶有順序訪問指針的B+Tree


SQL優化

避免全表掃描,改為索引掃描。

  1. 適當的索引。
  2. 盡量不要有空判斷的語句,因為空判斷將導致全表掃描,而不是索引掃描。盡量不要有空判斷的語句,因為空判斷將導致全表掃描,而不是索引掃描。 對于空判斷這種情況,可以考慮對這個列創建數據庫默認值。
  3. 盡量不要使用不等于條件,因為,這會導致全表掃描,對于不等于這種情況,考慮改為范圍查詢解決。
  4. 盡量不要使用or條件,因為,這會導致全表掃描,對于or這種情況,可以改為 分別查詢,然后 union all。
  5. 盡量不要使用左右模糊查詢,因為,這會導致全表掃描, 對于左右模糊查詢的情況,試著改為右側模糊查詢,這樣是可以索引查找的。
  6. 盡量不要在執行算數運算后的比較,因為,函數、算術運算或其他表達式運算通常將導致全表掃描,對于這種情況,可以考慮冗余部分數據到表中。
  7. 盡量使用exists代替in。
  8. 盡量避免一次性返回大數據量,可以考慮分頁返回。

union與union all

  1. union :得到兩個查詢結果的并集,并且自動去掉重復行。不會排序。
  2. union all:得到兩個查詢結果的并集,不會去掉重復行。也不會排序。

Oracle與MySQL分頁查詢的寫法

  1. Oracle
        SELECT * FROM
                (
                        SELECT A. *, ROWNUM RN
                        FROM(SELECT * FROM TABLE_NAME)A
                        WHERE ROWNUM <= 40
                )
        WHERE RN >=21

2.MySQL

SELECT * FROM TABLE LIMIT 5, 10;

SQL小貼士

  • 執行順序
    from->where->group by->having->select->order by
  • HAVING 子句
    在 SQL 中增加 HAVING 子句原因是,WHERE 關鍵字無法與合計函數一起使用。
  • union去重,union all 不去重。
  • case when then else end
  • 當前時間
SELECT NOW(),CURDATE(),CURTIME() FROM DUAL

結果:

NOW() CURDATE() CURTIME()
2008-12-29 16:25:46 2008-12-29 16:25:46
  • 時間間隔
mysql> SELECT DATEDIFF('2017-08-08','2017-08-17');
+-------------------------------------+
| DATEDIFF('2017-08-08','2017-08-17') |
+-------------------------------------+
|                                  -9 |
+-------------------------------------+
1 row in set

查看數據庫引擎命令

show variables like '%storage_engine%';

Linux下查詢所有tomcat進程命令

ps -ef|grep tomcat

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,622評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,716評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,746評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,991評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,706評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,036評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,029評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,203評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,725評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,451評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,677評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,161評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,857評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,266評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,606評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,407評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,643評論 2 380

推薦閱讀更多精彩內容