1. 程式人生 > >2018年Java面試彙總(還有最近一些面試問題未總結,下次更新)

2018年Java面試彙總(還有最近一些面試問題未總結,下次更新)

再次更新,面試中主要是問基礎和專案。想要找到工作,首先基礎要紮實,然後有一個拿得出手的專案(拿不出也無所謂咯)。對於Java面試的這些常見問題一定要弄懂。
另外還有資料結構、演算法部分,則需要另外去複習,這裡只有Java方面。

Java基礎

1. Java四大特性

封裝,繼承,多型和抽象

封裝(模組化)

封裝給物件提供了隱藏內部特性和行為的能力。物件提供一些能被其他物件訪問的方法來改
變它內部的資料。在 Java 當中,有 3 種修飾符: public, private 和 protected。每一種修飾符
給其他的位於同一個包或者不同包下面物件賦予了不同的訪問許可權。
下面列出了使用封裝的一些好處:

通過隱藏物件的屬性來保護物件內部的狀態。
提高了程式碼的可用性和可維護性,因為物件的行為可以被單獨的改變或者是擴充套件。
禁止物件之間的不良互動提高模組化

繼承(可複用性)

繼承給物件提供了從基類獲取欄位和方法的能力。繼承提供了程式碼的重用,也可以在不修改類的情況下給現存的類新增新特性。

父類的靜態方法能否被子類重寫
不能,重寫只適用於例項方法,不能用於靜態方法。

父類子類的初始化順序(筆試常考)

  1. 父類中靜態成員變數和靜態程式碼塊
  2. 子類中靜態成員變數和靜態程式碼塊
  3. 父類中普通成員變數和程式碼塊,父類的建構函式
  4. 子類中普通成員變數和程式碼塊,子類的建構函式

總結:

  1. 在類載入的時候執行父類的static程式碼塊,並且只執行一次
  2. 執行子類的static程式碼塊,並且只執行一次
  3. 執行父類的類成員初始化,並且是從上往下按出現順序執行
  4. 執行父類的建構函式
  5. 執行子類的類成員初始化,並且從上往下按出現順序執行
  6. 執行子類的建構函式
多型(可擴充套件性)(問的無敵多)

多型是程式語言給不同的底層資料型別做相同的介面展示的一種能力。一個多型型別上的操作可以應用到其他型別的值上面。

多型是指一個引用型別在不同情況下的多種形態,同一個行為有多個不同表現形式或形態的能力。
多型就是同一個介面,使用不同的例項而執行不同操作。
也可以理解為,多型是指父類引用變數通過指向不同的子類物件來呼叫不同子類實現的方法。

多型的好處

  1. 可替換性
  2. 可擴充性
  3. 介面性
  4. 靈活性
  5. 簡化性

多型存在的三個必要條件:

  • 繼承
  • 重寫
  • 父類引用指向子類物件

實現多型的方式主要有以下三種方式

  1. 介面實現
  2. 繼承父類重寫方法
  3. 同一類中進行方法過載

JVM如何實現多型的
多型允許具體訪問時實現方法的動態繫結。Java對於動態繫結的實現主要依賴於方法表,通過繼承和介面的多型實現有所不同。

繼承:在執行某個方法時,在方法區中找到該類的方法表,再確認該方法在方法表中的偏移量,找到該方法後如果被重寫則直接呼叫,否則認為沒有重寫父類該方法,這時會按照繼承關係搜尋父類的方法表中該偏移量對應的方法。

介面:Java 允許一個類實現多個介面,從某種意義上來說相當於多繼承,這樣同一個介面的的方法在不同類方法表中的位置就可能不一樣了。所以不能通過偏移量的方法,而是通過搜尋完整的方法表。

舉例回答:什麼是多型?
多型,顧名思義就是多種形態,指一個行為在不同情況下具有不同的形態,比如說一個介面通過使用不同的例項來執行不同的操作(一個父類引用變數可以通過指向不同的子類物件來呼叫不同的方法)。這是通過多個子類繼承父類,並分別重寫父類的方法實現的。

抽象

抽象是把想法從具體的例項中分離出來的步驟,因此,要根據他們的功能而不是實現細節來建立類。 Java 支援建立只暴漏介面而不包含方法實現的抽象的類。這種抽象技術的主要目的是把類的行為和實現細節分離開。

Java中的8種基本資料型別,以及各佔的位元組:(被問過幾次)

  • 數值型
    • 整數型別:byte(1)、short(2)、int(4)、long(8)
    • 浮點型別:float(4)、double(8)
  • 字元型:char(2)
  • 布林型:boolean(1B)

String能被繼承嗎?

不能,因為String類有final修飾符,所以不能繼承。

String、Stringbuffer、stringbuilder的區別 --TODO

  • String 字串常量(值不可變,每次對String的操作都會生成新的String物件)
  • StringBuilder 字串變數(Java5、非執行緒安全,效率高,速度快)
  • StringBuffer 字串變數(單執行緒、執行緒安全、效率低、速度慢)

多數情況下使用StringBuilder,但在應用程式要求執行緒安全時,必須使用StringBUffer

2. 抽象類和介面有什麼區別

比較 抽象類 介面
預設方法 可以有預設的方法實現 不存在方法的實現
實現方式 extends implements
構造器 可以 不可以
和正常類的區別 抽象類不能被例項化 介面則是完全不同的型別
訪問修飾符 public/protected/default 預設public不能用其他
多繼承 一個子類只能存在一個父類 一個子類可以存在多個介面
新增新方法 可以不修改子類 子類必須重新實現該方法
介面的意義

規範、擴充套件、回撥

抽象的意義
  1. 為其它子類提供一個公共的型別
  2. 封裝子類中重複定義的內容
  3. 定義抽象方法,子類雖然有不同的實現,但是定義時一樣

介面和類的區別:

  • 介面中定義的屬性必須是public abstract的,而介面中的方法則必須也是public abstract。
  • 介面可以被多個類實現,而且必須實現介面中的所有抽象方法,否則只能宣告為抽象類。
  • 介面可以多重實現,即一個介面可以同時實現多個介面。
  • 介面也可以多重繼承,實現多型機制。

abstract-class的使用場景:
一句話,在既需要統一的介面,又需要例項變數或預設的方法的情況下,就可以使用它。最常見的有:

  • A.定義了一組介面,但又不想強迫每個實現類都必須實現所有的介面。可以用abstract-class定義一組方法體,甚至可以是空方法體,然後由子類選擇自己所感興趣的方法來覆蓋。
  • B.某些場合下,只靠純粹的介面不能滿足類與類之間的協調,還必需類中表示狀態的變數來區別不同的關係。abstract的中介作用可以很好地滿足這一點.
  • C.規範了一組相互協調的方法,其中一些方法是共同的,與狀態無關的,可以共享的,無需子類分別實現;而另一些方法卻需要各個子類根據自己特定的狀態來實現特定的功能

異常

Throwable: 有兩個重要的子類:Exception(異常)和 Error(錯誤),二者都是 Java 異常處理的重要子類,各自都包含大量子類。

Error(錯誤):是程式無法處理的錯誤,表示執行應用程式中較嚴重問題。大多數錯誤與程式碼編寫者執行的操作無關,而表示程式碼執行時 JVM(Java 虛擬機器)出現的問題。例如,Java虛擬機器執行錯誤(Virtual MachineError),當 JVM 不再有繼續執行操作所需的記憶體資源時,將出現 OutOfMemoryError。這些異常發生時,Java虛擬機器(JVM)一般會選擇執行緒終止。

Exception(異常):是程式本身可以處理的異常。
包括RuntimeException執行異常,描述程式設計錯誤。

免檢異常和必檢異常
在教材上,免檢異常指得是RuntimeException、Error和他們的子類。

java反射

1. 理解Class類

物件照鏡子後可以得到的資訊:某個類的資料成員名、方法和構造器、某個類到底實現了哪些介面。

Class是什麼?

Class是一個類。
對於每個類而言,JRE 都為其保留一個不變的 Class 型別的物件。一個 Class 物件包含了特定某個類的有關資訊。
  –Class 物件只能由系統建立物件
  –一個類在 JVM 中只會有一個Class例項
  –每個類的例項都會記得自己是由哪個 Class 例項所生成

Class這個類封裝了什麼資訊?

1. Class是一個類,封裝了當前物件所對應的類的資訊
  2. 一個類中有屬性,方法,構造器等,比如說有一個Person類,一個Order類,一個Book類,這些都是不同的類,現在需要一個類,用來描述類,這就是Class,它應該有類名,屬性,方法,構造器等。Class是用來描述類的類
  3. Class類是一個物件照鏡子的結果,物件可以看到自己有哪些屬性,方法,構造器,實現了哪些介面等等
  4. 對於每個類而言,JRE 都為其保留一個不變的 Class 型別的物件。一個 Class 物件包含了特定某個類的有關資訊。
5. Class 物件只能由系統建立物件,一個類(而不是一個物件)在 JVM 中只會有一個Class例項

物件為什麼需要照鏡子(反射)呢?

1. 有可能這個物件是別人傳過來的
  2. 有可能沒有物件,只有一個全類名 ,通過反射,可以得到這個類裡面的資訊

獲取Class物件的三種方式

1.通過類名獲取 類名.class
  2.通過物件獲取 物件名.getClass() 這種方式是用在傳進來一個物件,卻不知道物件型別的時候使用
  3.通過全類名獲取 Class.forName(全類名) 一般框架開發中這種用的比較多

Class clazz = null;
// 1. 通過類名
clazz = Person.class;
// 2. 通過物件名
clazz = person.getClass();
// 3. 通過全類名
clazz = Class.forName("com.test.Person");
Class類常用的方法

forName() 返回指定類名name的Class物件
newInstance() 呼叫預設建構函式,返回該Class物件的一個例項
newInstance(Object[] args)
getName()
//getSuperClass() 返回父類CLass //沒有這個類,搞錯了吧
getSuperclass() 返回超類Class
getInterfaces() 返回介面
getClassLoader() 返回類載入器

2. 理解ClassLoader

類裝載器是用來把類(class)裝載進 JVM 的。JVM 規範定義了兩種型別的類裝載器:啟動類裝載器(bootstrap)和使用者自定義裝載器(user-defined class loader)。 JVM在執行時會產生3個類載入器組成的初始化載入器層次結構

類載入器的原理
類載入器是一個用來載入類檔案的類。java原始碼通過javac編譯器編譯類檔案。然後JVM來執行類檔案中的位元組碼來執行程式。類載入器負責載入檔案系統、網路或其他來源的類檔案。有三種預設使用的類載入器:
Bootstrap類載入器
負責載入rt.jar中的JDK類檔案,它是所有類載入器的父載入器。

Extension類載入器
Extension將載入類的請求先委託給它的父載入器(Bootstrap),

System類載入器

類載入器的作用

Java類載入器的作用就是在執行時載入類。Java類載入器基於三個機制:委託、可見性和單一性。

委託機制是指將載入一個類的請求交給父類載入器,如果這個父類載入器不能夠找到或者載入這個類,那麼再載入它。

可見性的原理是子類的載入器可以看見所有的父類載入器載入的類,而父類載入器看不到子類載入器載入的類。

單一性原理是指僅載入一個類一次,這是由委託機制確保子類載入器不會再次載入父類載入器載入過的類。正確理解類載入器能夠幫你解決NoClassDefFoundError和java.lang.ClassNotFoundException,因為它們和類的載入相關。

3.反射
反射概述

Reflection(反射)是Java被視為動態語言的關鍵,反射機制允許程式在執行期藉助於Reflection API取得任何類的內部資訊,並能直接操作任意物件的內部屬性及方法。

指在執行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法,對於任意一個物件,都能呼叫它的任意一個方法.這種動態獲取資訊,以及動態呼叫物件方法的功能叫java語言的反射機制.

Java反射機制主要提供了以下功能:
在執行時構造任意一個類的物件
在執行時獲取任意一個類所具有的成員變數和方法
在執行時呼叫任意一個物件的方法(屬性)
**生成動態代理**(實現AOP)
Class 是一個類; 一個描述類的類.封裝了
描述方法的 Method,
描述欄位的 Filed,
描述構造器的 Constructor 等屬性.
Method
獲取
getMethods()
getDeclareMethods()
getDeclareMethod()

使用
method.invoke(obj,args)

Field
獲取
getDeclareFields()
getDeclareField("name")

使用
field.get()
field.set(obj,"name")
field.setAccessible(true) // 設定私有屬性可訪問
Constructor
獲取
getConstructors()
getConstructor(String.class, int.class) // 根據引數列表獲取指定構造器
呼叫構造器的newInstance()方法建立物件
Object obj = constructor.newInstance(args[])
Annotation

如何描述註解

Java代理模式

代理的基本構成: 抽象角色、代理角色、真實角色

分為靜態代理和動態代理

靜態代理:
interface Subject {

}

JDK動態代理:

1. JDK動態代理步驟
  1. 建立一個實現InvocationHandler介面的類,並必須實現invoke方法
  2. 建立被代理的類及介面
  3. 呼叫Proxy的靜態方法,建立一個代理類
  4. 通過代理呼叫方法
2. 實現InvocationHandler介面
    public class DynaProxyHello implements InvocationHandler{
        
        private Object target;//目標物件
        /**
         * 通過反射來例項化目標物件
         * @param object
         * @return
         */
        public Object bind(Object object){
            this.target = object;
            return Proxy.newProxyInstance(this.target.getClass().getClassLoader(), this.target.getClass().getInterfaces(), this);
        }
        
        @Override
        public Object invoke(Object proxy, Method method, Object[] args)
                throws Throwable {
            Object result = null;
            Logger.start();//新增額外的方法
            //通過反射機制來執行目標物件的方法
            result = method.invoke(this.target, args);
            Logger.end();
            return result;
        }
        
    }

JVM體系結構

開發人員編寫Java程式碼(.java檔案),然後將之編譯成位元組碼(.class檔案),再然後位元組碼被裝入記憶體,一旦位元組碼進入虛擬機器,它就會被直譯器解釋執行,或者是被即時程式碼發生器有選擇的轉換成機器碼執行。

JVM處在核心位置,是程式與底層作業系統和硬體無關的關鍵。

JVM工作原理和流程
Java工作流程

JVM工作流程

JVM在整個jdk中處於最底層,負責於作業系統的互動,用來遮蔽作業系統環境,提供一個完整的Java執行環境,因此也就虛擬計算機. 作業系統裝入JVM是通過jdk中Java.exe來完成,通過下面4步來完成JVM環境.

1.建立JVM裝載環境和配置
2.裝載JVM.dll
3.初始化JVM.dll並掛界到JNIENV(JNI呼叫介面)例項
4.呼叫JNIEnv例項裝載並處理class類。

Java程式碼編譯和執行的整個過程包含了以下三個重要的機制:

  • Java原始碼編譯機制
  • 類載入機制
  • 類執行機制

類載入機制:
類從被載入到虛擬機器記憶體開始,到卸載出記憶體為止,整個生命週期包括:載入、驗證、準備、解析、初始化、使用和解除安裝七個階段。

JVM的類載入是通過ClassLoader及其子類來完成的

JVM記憶體配置引數

整個堆的大小 = 年輕代大小 + 年老代大小

-Xms JVM啟動申請的初始Heap值
(預設空餘堆記憶體大於70%時,JVM會減小heap的大小到-Xms指定的大小,所以Server端JVM最好將-Xms和-Xmx設為相同值,避免每次垃圾回收完成後JVM重新分配記憶體)

-Xmx JVM可申請的最大Heap值

-Xmn Java Heap Young區大小 (官方推薦為整個堆得3/8)

-Xss Java每個執行緒的Stack大小

JVM記憶體管理 --記憶體的分配和回收
3.1 記憶體分配

JVM記憶體:

  • 執行緒共享
    • 方法區 —儲存方法程式碼(編譯後的java程式碼)和符號表。存放了要載入的類資訊、靜態變數、final型別的常量、屬性和方法資訊
    • 堆/Java堆 —儲存物件例項和陣列值(通過new建立的物件的記憶體都在此分配)
  • 非執行緒共享
    • 本地方法區 – 用於支援native方法的執行,儲存了每個native方法呼叫的狀態。
    • 程式計數器(PC暫存器)
    • JVM方法棧/Java虛擬機器棧 – 區域性變數

Java堆:
Java堆可以分為新生代、老年代
新生代又可以分為Eden空間、From Survivor空間、To Survivor空間

記憶體分配:
Java物件所佔用的記憶體主要是從堆中進行分配。堆是所有執行緒共享的。因此在堆上分配記憶體時需要加鎖,這導致了建立物件的開銷比較大。當堆空間不足時,會觸發GC。如果GC後空間仍然不足,則會丟擲OutOfMemory錯誤資訊。

3.2 記憶體回收 --垃圾回收機制(GC)

Java 提供了一種垃圾回收機制,在後臺建立一個守護程序。該程序會在記憶體緊張的時候自動跳出來,把堆空間的垃圾全部進行回收,從而保證程式的正常執行。

  • 垃圾:不再存活的物件
    • 垃圾的判斷方法:
      • 引用計數法 —引用數為0的
      • 可達性分析 —不可達的
  • 回收
    • 標記-清理
    • 標記-整理
    • 複製
    • 分代垃圾回收機制

可達性分析:
物件之間的引用可以抽象成樹形結構,通過樹根(GC Roots)作為起點,從這些樹根往下起點,從這些樹根往下搜尋,搜尋走過的鏈稱為引用鏈,當一個物件到GC Roots沒有任何引用鏈相連時,則證明這個物件是不可用的,該物件會被判定可回收的物件。

可作為GC Roots的物件有

  1. 虛擬機器棧()中引用的物件
  2. 方法區中類靜態屬性引用的物件
  3. 方法區中常量引用的物件
  4. 本地方法棧中JNI(Native方法)引用的物件

三種演算法的優劣

  1. 標記-清理: 實現簡單,與保守式的GC(物件不能被移動)相容,但容易造成磁碟碎片化,分配速度比較慢,與寫時複製不相容,並且標記,清理效率都不高。

  2. 標記-整理: 避免了記憶體碎片。

  3. 複製: 效率快,但空間代價過高

現在收集器都採用的是分代收集演算法:
年輕代通常使用時間佔優的GC,因為年輕代的GC非常頻繁 。(複製)
年老代通常使用善於處理大空間的GC,因為年老代的空間大,GC頻率低 。(標記整理)

新生代採用複製:
所有新生成的物件首先都是放在年輕代的。年輕代的目標就是儘可能快速的收集掉那些生命週期短的物件。年輕代分三個區。一個Eden區,兩個 Survivor區(一般而言)。大部分物件在Eden區中生成。當Eden區滿時,還存活的物件將被複制到Survivor區(兩個中的一個,這兩個區分別叫做From survivor和To survivor),當這個 Survivor區滿時,此區的存活物件將被複制到另外一個Survivor區,當這個Survivor去也滿了的時候,從第一個Survivor區複製過來的並且此時還存活的物件,將被複制“年老區(Tenured)”。

一般情況下,From中的物件(演算法會考慮經過GC倖存的次數)到一定次數(閾值(如果說每次GC之後這個物件依舊在Survive中存在,GC一次他的Age就會加1,預設15就會放到OldGeneration。

如果沒到次數From中的物件會被複制到To中,複製完成後To中儲存的是有效的物件,Eden和From中剩下的都是無效的物件,這個時候就把Eden和From中所有的物件清空。在複製的時候Eden中的物件進入To中,To可能已經滿了,這個時候Eden中的物件就會被直接複製到Old Generation中,From中的物件也會直接進入Old Generation中。

年老代:

  1. 被複制到老年代的物件大於老年代剩餘空間,就會觸發full gc
  2. 如果有永久代,在不足夠分配時,也會觸發Full GC;
  3. 呼叫System.gc(),提醒JVM FullGC,但不可控

永久代:
Permanent Generation(永久代)可以理解成方法區,(它屬於方法區)也有可能發生GC,例如類的例項物件全部被GC了,同時它的類載入器也被GC掉了,這個時候就會觸發永久代中物件的GC。

minor GC:
一般情況下,當新物件生成,並且在Eden申請空間失敗時,minor GC,對Eden區域進行GC,清除非存活物件,並且把尚且存活的物件移動到Survivor區。然後整理Survivor的兩個區。這種方式的GC是對年輕代的Eden區進行,不會影響到年老代。因為大部分物件都是從Eden區開始的,同時Eden區不會分配的很大,所以Eden區的GC會頻繁進行。因而,一般在這裡需要使用速度快、效率高的演算法,使Eden去能儘快空閒出來。

Full GC:
標記-整理
因為要對整個堆進行整理,包括Young、Tenured和Perm。Full GC因為需要對整個對進行回收,所以比Scavenge GC要慢,因此應該儘可能減少Full GC的次數。在對JVM調優的過程中,很大一部分工作就是對於FullGC的調節。有如下原因可能導致Full GC:

JVM垃圾回收期序列、並行、併發垃圾回收機制

3.3 記憶體洩漏

記憶體洩露就是指一個不再被程式使用的物件或變數一直被佔據在記憶體中。
因為GC只清理未被引用的物件,如果一個物件已經被使用完了,但仍然被引用著,GC無法回收該物件,就是記憶體洩漏。

注意和記憶體溢位的區別:記憶體溢位,要求分配的記憶體超出了系統能給你的。比如說陣列越界。

3.4 陣列在JVM中的儲存

已知棧用來儲存區域性變數,區域性變數包括基本型別和引用型別。對於基本型別來說,
棧儲存的是它的值,而對於引用型別來說,存放的是它的地址,當一個區域性變數使用完之後立馬就會被釋放,
但是堆區不會立即被釋放。

而堆,存放的是動態產生的資料,比如new出來的物件,每個物件都有自己獨立的
堆塊,其中儲存著例項變數。因為同一個類的方法都是共享的,方法只有在

equals()和hashCode()方法

官方文件定義

hashcode方法返回該物件的雜湊碼值。支援該方法是為雜湊表提供一些優點,例如,java.util.Hashtable 提供的雜湊表。

hashCode 的常規協定是:
1. 在 Java應用程式執行期間,在同一物件上多次呼叫hashCode方法時,必須一致地返回相同的整數,前提是物件上equals比較中所用的資訊沒有被修改。從某一應用程式的一次執行到同一應用程式的另一次執行,該整數無需保持一致。
2. 如果根據 equals(Object) 方法,兩個物件是相等的,那麼在兩個物件中的每個物件上呼叫hashCode方法都必須生成相同的整數結果。

以下情況不是必需的:如果根據equals(java.lang.Object)方法,兩個物件不相等,那麼在兩個物件中的任一物件上呼叫 hashCode 方法必定會生成不同的整數結果。但是,程式設計師應該知道,為不相等的物件生成不同整數結果可以提高雜湊表的效能。 實際上,由Object類定義的hashCode方法確實會針對不同的物件返回不同的整數。(這一般是通過將該物件的內部地址轉換成一個整數來實現的,但是 JavaTM 程式語言不需要這種實現技巧。)

3. 當equals方法被重寫時,通常有必要重寫 hashCode 方法,以維護 hashCode 方法的常規協定,該協定宣告相等物件必須具有相等的雜湊碼。

為什麼當equals方法被重寫時,通常有必要重寫 hashCode 方法?
如果不重寫的話,就會違反第二條。假設兩個物件,重寫了其equals方法,其相等條件是屬性相等,就返回true。如果不重寫hashcode方法,其返回的依然是兩個物件的記憶體地址值,必然不相等。這就出現了equals方法相等,但是hashcode不相等的情況。

重寫equals()方法就必須重寫hashCode()方法主要是針對HashSet和Map集合型別。集合框架只能存入物件(物件的引用(基本型別資料:自動裝箱))。
在向HashSet集合中存入一個元素時,HashSet會呼叫該物件(存入物件)的hashCode()方法來得到該物件的hashCode()值,然後根據該hashCode值決定該物件在HashSet中儲存的位置。

在Map集合中,例如其子類Hashtable,HashMap,儲存的資料是<key,value>對,key,value都是物件,被封裝在Map.Entry,即:每個集合元素都是Map.Entry物件。在Map集合中,判斷key相等標準也是:兩個key通過equals()方法比較返回true,兩個key的hashCode的值也必須相等。判斷valude是否相等equal()相等即可。

重寫hashCode()的原則
(1)同一個物件多次呼叫hashCode()方法應該返回相同的值;
(2)當兩個物件通過equals()方法比較返回true時,這兩個物件的hashCode()應該返回相等的(int)值;
(3)物件中用作equals()方法比較標準的Filed(成員變數(類屬性)),都應該用來計算hashCode值。

對於第一個原則,可以想得到,如果重寫的hashCode()是根據物件的欄位來獲取的,那麼當物件欄位被改變後,hashCode也將隨之變化。可能造成HashMap中的值無法被取出。

equals()和==的區別

基本資料型別使用==來比較值
對於複合資料型別,使用==,比較的是記憶體中的存放地址。
而equals在Object中的定義是比較物件的記憶體地址,但在一些類庫中被覆蓋了,比較的不再是堆記憶體中的存放地址。比如說,String,Integer,Date等。

String s1 = "a";
String s2 = "a";
String s3 = new String("a");

s1 == s2 為真
s1 == s3 為假
s1.equals(s3) 為真

程式在執行的時候會建立一個字串緩衝池當使用s2=“a”,這樣的表達是建立字串的時候,程式首先會在這個String緩衝池中尋找相同值的物件,在第一個程式中,s1先被放到了池中,所以在s2被建立的時候,程式找到了具有相同值的 s1將s2引用s1所引用的物件"a"

如果使用new操作符,則是明確重新建立一個物件。

編碼

  1. Ascii/GBK/Unicode/Utf-8
編碼 說明
ASCII 1個位元組,127
GB2313 2個位元組,GB2312 是對 ASCII 的中文擴充套件。

執行緒

1. 建立執行緒的方式 --三種
  • 繼承thread類建立執行緒類
  • 通過Runnable介面建立執行緒類
  • 通過Callable和future建立執行緒
1.1 繼承thread類建立執行緒類

(1)定義Thread類的子類,並重寫該類的run方法,該run方法的方法體就代表了執行緒要完成的任務。因此把run()方法稱為執行體。
(2)建立Thread子類的例項,即建立了執行緒物件。
(3)呼叫執行緒物件的start()方法來啟動該執行緒。

package com.thread;
public class FirstThreadTest extends Thread{
    int i = 0;
    //重寫run方法,run方法的方法體就是現場執行體
    public void run()
    {
        for(;i<100;i++){
          System.out.println(getName()+"  "+i);
        }
    }
    public static void main(String[] args)
    {
        for(int i = 0;i< 100;i++)
        {
            System.out.println(Thread.currentThread().getName()+"  : "+i);
            if(i==20)
            {
                new FirstThreadTest().start();
                new FirstThreadTest().start();
            }
        }
    }
}

上述程式碼中Thread.currentThread()方法返回當前正在執行的執行緒物件。getName()方法返回呼叫該方法的執行緒的名字。

1.2 通過Runnable介面建立執行緒類

(1)定義runnable介面的實現類,並重寫該介面的run()方法,該run()方法的方法體同樣是該執行緒的執行緒執行體。
(2)建立 Runnable實現類的例項,並依此例項作為Thread的target來建立Thread物件,該Thread物件才是真正的執行緒物件。
(3)呼叫執行緒物件的start()方法來啟動該執行緒。

package com.thread;
public class RunnableThreadTest implements Runnable
{
    private int i;
    public void run()
    {
        for(i = 0;i <100;i++)
        {
            System.out.println(Thread.currentThread().getName()+" "+i);
        }
    }
    public static void main(String[] args)
    {
        for(int i = 0;i < 100;i++)
        {
            System.out.println(Thread.currentThread().getName()+" "+i);
            if(i==20)
            {
                RunnableThreadTest rtt = new RunnableThreadTest();
                new Thread(rtt,"新執行緒1").start();
                new Thread(rtt,"新執行緒2").start();
            }
        }
    }
}
1.3 通過Callable和future建立執行緒

(1)建立Callable介面的實現類,並實現call()方法,該call()方法將作為執行緒執行體,並且有返回值。
(2)建立Callable實現類的例項,使用FutureTask類來包裝Callable物件,該FutureTask物件封裝了該Callable物件的call()方法的返回值。
(3)使用FutureTask物件作為Thread物件的target建立並啟動新執行緒。
(4)呼叫FutureTask物件的get()方法來獲得子執行緒執行結束後的返回值

package com.thread;
 
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
 
public class CallableThreadTest implements Callable<Integer>
{
    public static void main(String[] args)
    {
        CallableThreadTest ctt = new CallableThreadTest();
        FutureTask<Integer> ft = new FutureTask<>(ctt);
        for(int i = 0;i < 100;i++)
        {
            System.out.println(Thread.currentThread().getName()+" 的迴圈變數i的值"+i);
            if(i==20)
            {
                new Thread(ft,"有返回值的執行緒").start();
            }
        }
        try
        {
            System.out.println("子執行緒的返回值:"+ft.get());
        } catch (InterruptedException e)
        {
            e.printStackTrace();
        } catch (ExecutionException e)
        {
            e.printStackTrace();
        }
    }
    @Override   // 表明這是重寫父類的方法,編譯器就會檢查重寫是否符合規範,如果不加該註解,函式名寫錯了,編譯器也不會有任何提醒。
    public Integer call() throws Exception
    {
        int i = 0;
        for(;i<100;i++)
        {
            System.out.println(Thread.currentThread().getName()+" "+i);
        }
        return i;
    }
}
建立執行緒的三種方式的對比

採用實現Runnable、Callable介面的方式創見多執行緒時,優勢是:

執行緒類只是實現了Runnable介面或Callable介面,還可以繼承其他類。

在這種方式下,多個執行緒可以共享同一個target物件,所以非常適合多個相同執行緒來處理同一份資源的情況,從而可以將CPU、程式碼和資料分開,形成清晰的模型,較好地體現了面向物件的思想。

劣勢是:

程式設計稍微複雜,如果要訪問當前執行緒,則必須使用Thread.currentThread()方法。

使用繼承Thread類的方式建立多執行緒時優勢是:

編寫簡單,如果需要訪問當前執行緒,則無需使用Thread.currentThread()方法,直接使用this即可獲得當前執行緒。

劣勢是:

執行緒類已經繼承了Thread類,所以不能再繼承其他父類。

執行緒通訊

執行緒池

所謂執行緒池,就是將多個執行緒放在一個池子裡面(所謂池化技術),然後需要執行緒的時候不是建立一個執行緒,而是從執行緒池裡面獲取一個可用的執行緒,然後執行我們的任務。執行緒池的關鍵在於它為我們管理了多個執行緒,我們不需要關心如何建立執行緒,我們只需要關係我們的核心業務,然後需要執行緒來執行任務的時候從執行緒池中獲取執行緒。任務執行完之後執行緒不會被銷燬,而是會被重新放到池子裡面,等待機會去執行任務。

常見執行緒池

sleep() 、join()、yield()有什麼區別

1、sleep()方法

在指定的毫秒數內讓當前正在執行的執行緒休眠(暫停執行),此操作受到系統計時器和排程程式精度和準確性的影響。 讓其他執行緒有機會繼續執行,但它並不釋放物件鎖。也就是如果有Synchronized同步塊,其他執行緒仍然不能訪問共享資料。注意該方法要捕獲異常

比如有兩個執行緒同時執行(沒有Synchronized),一個執行緒優先順序為MAX_PRIORITY,另一個為MIN_PRIORITY,如果沒有Sleep()方法,只有高優先順序的執行緒執行完成後,低優先順序的執行緒才能執行;但當高優先順序的執行緒sleep(5000)後,低優先順序就有機會執行了。
總之,sleep()可以使低優先順序的執行緒得到執行的機會,當然也可以讓同優先順序、高優先順序的執行緒有執行的機會。

2、yield()方法

yield()方法和sleep()方法類似,也不會釋放“鎖標誌”,區別在於,它沒有引數,即yield()方法只是使當前執行緒重新回到可執行狀態,所以執行yield()的執行緒有可能在進入到可執行狀態後馬上又被執行,另外yield()方法只能使同優先順序或者高優先順序的執行緒得到執行機會,這也和sleep()方法不同。

3、join()方法

Thread的非靜態方法join()讓一個執行緒B“加入”到另外一個執行緒A的尾部。在A執行完畢之前,B不能工作。

Thread t = new MyThread(); t.start(); t.join();

保證當前執行緒停止執行,直到該執行緒所加入的執行緒完成為止。然而,如果它加入的執行緒沒有存活,則當前執行緒不需要停止。

Java併發程式設計:執行緒池的使用

執行緒池的幾種方式
newFixedThreadPool(int nThreads)
建立一個固定長度的執行緒池,每當提交一個任務就建立一個執行緒,直到達到執行緒池的最大數量,這時執行緒規模將不再變化,當執行緒發生未預期的錯誤而結束時,執行緒池會補充一個新的執行緒

newCachedThreadPool()
建立一個可快取的執行緒池,如果執行緒池的規模超過了處理需求,將自動回收空閒執行緒,而當需求增加時,則可以自動新增新執行緒,執行緒池的規模不存在任何限制

newSingleThreadExecutor()
這是一個單執行緒的Executor,它建立單個工作執行緒來執行任務,如果這個執行緒異常結束,會建立一個新的來替代它;它的特點是能確保依照任務在佇列中的順序來序列執行

newScheduledThreadPool(int corePoolSize)
建立了一個固定長度的執行緒池,而且以延遲或定時的方式來執行任務,類似於Timer。

舉個栗子

private static final Executor exec=Executors.newFixedThreadPool(50);

Runnable runnable=new Runnable(){
public void run(){

}
}
exec.execute(runnable);

Callable callable=new Callable() {
public Object call() throws Exception {
return null;
}
};

Future future=executorService.submit(callable);
future.get(); // 等待計算完成後,獲取結果
future.isDone(); // 如果任務已完成,則返回 true
future.isCancelled(); // 如果在任務正常完成前將其取消,則返回 true
future.cancel(true); // 試圖取消對此任務的執行,true中斷執行的任務,false允許正在執行的任務執行完成
參考:

建立執行緒池的幾種方式

執行緒的生命週期

新建(New)、就緒(Runnable)、執行(Running)、阻塞(Blocked)和死亡(Dead)5種狀態

(1)生命週期的五種狀態

新建(new Thread)
當建立Thread類的一個例項(物件)時,此執行緒進入新建狀態(未被啟動)。
例如:Thread t1=new Thread();

就緒(runnable)
執行緒已經被啟動,正在等待被分配給CPU時間片,也就是說此時執行緒正在就緒佇列中排隊等候得到CPU資源。例如:t1.start();

執行(running)
執行緒獲得CPU資源正在執行任務(run()方法),此時除非此執行緒自動放棄CPU資源或者有優先順序更高的執行緒進入,執行緒將一直執行到結束。

死亡(dead)
當執行緒執行完畢或被其它執行緒殺死,執行緒就進入死亡狀態,這時執行緒不可能再進入就緒狀態等待執行。

自然終止:正常執行run()方法後終止

異常終止:呼叫stop()方法讓一個執行緒終止執行

堵塞(blocked)
由於某種原因導致正在執行的執行緒讓出CPU並暫停自己的執行,即進入堵塞狀態。

正在睡眠:用sleep(long t) 方法可使執行緒進入睡眠方式。一個睡眠著的執行緒在指定的時間過去可進入就緒狀態。

正在等待:呼叫wait()方法。(呼叫motify()方法回到就緒狀態)

被另一個執行緒所阻塞:呼叫suspend()方法。(呼叫resume()方法恢復)

參考:

執行緒的生命週期

鎖機制
說說執行緒安全問題
執行緒安全是指要控制多個執行緒對某個資源的有序訪問或修改,而在這些執行緒之間沒有產生衝突。
在Java裡,執行緒安全一般體現在兩個方面:
1、多個thread對同一個java例項的訪問(read和modify)不會相互干擾,它主要體現在關鍵字synchronized。如ArrayList和Vector,HashMap和Hashtable(後者每個方法前都有synchronized關鍵字)。如果你在interator一個List物件時,其它執行緒remove一個element,問題就出現了。
2、每個執行緒都有自己的欄位,而不會在多個執行緒之間共享。它主要體現在java.lang.ThreadLocal類,而沒有Java關鍵字支援,如像static、transient那樣。

執行緒併發、執行緒同步、執行緒安全、執行緒通訊

多執行緒程式設計中的三個核心概念:

  1. 原子性:一個操作(可能包含多個子操作),要麼全部執行,要麼全部都不執行
  2. 可見性:多個執行緒訪問共享變數時,一個執行緒對共享變數的修改,其他執行緒能夠立即看到
  3. 順序性:

Java如何解決多執行緒併發問題:
如何保證原子性:
鎖和同步
Lock
Synchronized
CAS AtomicInteger 原子操作類

Java如何保證可見性
volatile關鍵字保證可見性

Java如何保證順序性

如何保證執行緒安全

  1. 多例項、多副本 THreadLocal
  2. 鎖機制Synchronize、lock方式
  3. 使用java.util.concurrent下面的類庫
1. ThreadLocal:

為每一個執行緒維護一個副本變數,從而讓執行緒擁有私有的資源,就不再需要去競爭程序中的資源。每一個執行緒都可以獨立地改變自己的副本,而不會影響其它執行緒所對應的副本。

ThreadLoal方法:

void set(T value)
T get()
void remove()
protected T initialValue()

ThreadLocal底層實現:

Map(叫做ThreadLocalMap ),每一個元素的 key 值為執行緒物件,而值對應執行緒的變數副本 。當呼叫 get、set 方法時,使用的執行緒是當前在執行的執行緒,從而可以獲取、設定當前在執行的執行緒的私有變數。

2. Synchronize

關鍵字synchronize擁有鎖重入的功能,也就是在使用synchronize時,當一個執行緒的得到了一個物件的鎖後,再次請求此物件是可以再次得到該物件的鎖。
當一個執行緒請求一個由其他執行緒持有的鎖時,發出請求的執行緒就會被阻塞,然而,由於內建鎖是可重入的,因此如果某個執行緒試圖獲得一個已經由她自己持有的鎖,那麼這個請求就會成功,“重入” 意味著獲取鎖的 操作的粒度是“執行緒”,而不是呼叫。

Synchronize和Lock的區別:
synchronized的侷限性
佔有鎖的執行緒等待IO或者其他原因被阻塞,沒有釋放鎖的情況下,其他執行緒一直阻塞
多個執行緒同時讀寫檔案的時候,讀和讀操作也會發生衝突
我們沒有辦法知道當前我們的執行緒是否成功獲取了鎖,只能傻傻的等待

執行緒通訊、程序通訊

執行緒通訊的幾種方式:

  1. 共享變數
  2. wait/notify機制
  3. Lock/Condition機制
  4. 管道

程序通訊:

final, finally, finalize 的區別

final

修飾符(關鍵字)如果一個類被宣告為final,意味著它不能再派生出新的子類,不能作為父類被繼承。因此一個類不能既被宣告為 abstract的,又被宣告為final的。將變數或方法宣告為final,可以保證它們在使用中不被改變。被宣告為final的變數必須在宣告時給定初值,而在以後的引用中只能讀取,不可修改。被宣告為final的方法也同樣只能使用,不能過載。

finally

在異常處理時提供 finally 塊來執行任何清除操作。如果丟擲一個異常,那麼相匹配的 catch 子句就會執行,然後控制就會進入 finally 塊(如果有的話)。

finalize

方法名。Java 技術允許使用 finalize() 方法在垃圾收集器將物件從記憶體中清除出去之前做必要的清理工作。這個方法是由垃圾收集器在確定這個物件沒有被引用時對這個物件呼叫的。它是在 Object 類中定義的,因此所有的類都繼承了它。子類覆蓋 finalize() 方法以整理系統資源或者執行其他清理工作。finalize() 方法是在垃圾收集器刪除物件之前對這個物件呼叫的。

int 和 Integer 有什麼區別

int 是基本資料型別
Integer是其包裝類,注意是一個類。
為什麼要提供包裝類呢???
一是為了在各種型別間轉化,通過各種方法的呼叫。否則 你無法直接通過變數轉化。
比如,現在int要轉為String

int a=0;
String result=Integer.toString(a);
在java中包裝類,比較多的用途是用在於各種資料型別的轉化中。
我寫幾個demo
//通過包裝類來實現轉化的

int num=Integer.valueOf(“12”);
int num2=Integer.parseInt(“12”);
double num3=Double.valueOf(“12.2”);
double num4=Double.parseDouble(“12.2”);
//其他的類似。通過基本資料型別的包裝來的valueOf和parseXX來實現String轉為XX
String a=String.valueOf(“1234”);//這裡括號中幾乎可以是任何型別
String b=String.valueOf(true);
String c=new Integer(12).toString();//通過包裝類的toString()也可以
String d=new Double(2.3).toString();
再舉例下。比如我現在要用泛型

List nums;
這裡<>需要類。如果你用int。它會報錯的。

過載和重寫的區別

override(重寫)

  1. 方法名、引數、返回值相同。
  2. 子類方法不能縮小父類方法的訪問許可權。
  3. 子類方法不能丟擲比父類方法更多的異常(但子類方法可以不丟擲異常)。
  4. 存在於父類和子類之間。
  5. 方法被定義為final不能被重寫。

overload(過載)

  1. 引數型別、個數、順序至少有一個不相同。
  2. 不能過載只有返回值不同的方法名。
  3. 存在於父類和子類、同類中。

HTTP 請求的 GET 與 POST 方式的區別

  • GET方法會把名值對追加在請求的URL後面。因為URL對字元數目有限制,進而限制了用在客戶端請求的引數值的數目。並且請求中的引數值是可見的,因此,敏感資訊不能用這種方式傳遞。
  • POST方法通過把請求引數值放在請求體中來克服GET方法的限制,因此,可以傳送的引數的數目是沒有限制的。最後,通過POST請求傳遞的敏感資訊對外部客戶端是不可見的。

session 與 cookie 區別

  • cookie: 是 Web 伺服器傳送給瀏覽器的一塊資訊。瀏覽器會在本地檔案中給每一個 Web 服務
    器儲存 cookie。以後瀏覽器在給特定的 Web 伺服器發請求的時候,同時會發送所有為該服
    務器儲存的 cookie。
  • session:
    下面列出了 session 和 cookie 的區別:
  1. Cookie和Session都是會話技術,Cookie是執行在客戶端,Session是執行在伺服器端。
  2. Cookie有大小限制以及瀏覽器在存cookie的個數也有限制,Session是沒有大小限制和伺服器的記憶體大小有關。
  3. Cookie有安全隱患,通過攔截或本地檔案找得到你的cookie後可以進行攻擊。
  4. Session是儲存在伺服器端上會存在一段時間才會消失,如果session過多會增加伺服器的壓力。
  5. 無論客戶端瀏覽器做怎麼樣的設定,session都應該能正常工作。客戶端可以選擇禁用 cookie,但是, session 仍然是能夠工作的,因為客戶端無法禁用服務端的 session。

JDBC 流程

1、 載入JDBC驅動程式:
在連線資料庫之前,首先要載入想要連線的資料庫的驅動到JVM(Java虛擬機器),
這通過java.lang.Class類的靜態方法forName(String className)實現。
例如:

try{
//載入MySql的驅動類
Class.forName(“com.mysql.jdbc.Driver”) ;
}catch(ClassNotFoundException e){
System.out.println(“找不到驅動程式類 ,載入驅動失敗!”);
e.printStackTrace() ;
}
成功載入後,會將Driver類的例項註冊到DriverManager類中。

2、 提供JDBC連線的URL

連線URL定義了連線資料庫時的協議、子協議、資料來源標識。
書寫形式:協議:子協議:資料來源標識
協議:在JDBC中總是以jdbc開始 子協議:是橋連線的驅動程式或是資料庫管理系統名稱。
資料來源標識:標記找到資料庫來源的地址與連線埠。
例如:
jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=gbk;useUnicode=true;(MySql的連線URL)
表示使用Unicode字符集。如果characterEncoding設定為 gb2312或GBK,本引數必須設定為true 。characterEncoding=gbk:字元編碼方式。

3、建立資料庫的連線

要連線資料庫,需要向java.sql.DriverManager請求並獲得Connection物件, 該物件就代表一個數據庫的連線。
使用DriverManager的getConnectin(String url , String username , String password )方法傳入指定的欲連線的資料庫的路徑、資料庫的使用者名稱和 密碼來獲得。
例如: //連線MySql資料庫,使用者名稱和密碼都是root

String url = "jdbc:mysql://localhost:3306/test" ;
String username = "root" ;
String password = "root" ;
try{
Connection con = DriverManager.getConnection(url , username , password ) ;
}catch(SQLException se){
System.out.println("資料庫連線失敗!");
se.printStackTrace() ;
}

4、 建立一個Statement
•要執行SQL語句,必須獲得java.sql.Statement例項,Statement例項分為以下3 種類型:
1、執行靜態SQL語句。通常通過Statement例項實現。
2、執行動態SQL語句。通常通過PreparedStatement例項實現。
3、執行資料庫儲存過程。通常通過CallableStatement例項實現。
具體的實現方式:

Statement stmt = con.createStatement() ; PreparedStatement pstmt = con.prepareStatement(sql) ; CallableStatement cstmt = con.prepareCall(“{CALL demoSp(? , ?)}”) ;

5、執行SQL語句
Statement介面提供了三種執行SQL語句的方法:executeQuery 、executeUpdate 和execute
1、ResultSet executeQuery(String sqlString):執行查詢資料庫的SQL語句 ,返回一個結果集(ResultSet)物件。
2、int executeUpdate(String sqlString):用於執行INSERT、UPDATE或 DELETE語句以及SQL DDL語句,如:CREATE TABLE和DROP TABLE等
3、execute(sqlString):用於執行返回多個結果集、多個更新計數或二者組合的 語句。 具體實現的程式碼:

ResultSet rs = stmt.executeQuery(“SELECT * FROM …”) ; int rows = stmt.executeUpdate(“INSERT INTO …”) ; boolean flag = stmt.execute(String sql) ;

6、處理結果
兩種情況:
1、執行更新返回的是本次操作影響到的記錄數。
2、執行查詢返回的結果是一個ResultSet物件。
• ResultSet包含符合SQL語句中條件的所有行,並且它通過一套get方法提供了對這些 行中資料的訪問。
• 使用結果集(ResultSet)物件的訪問方法獲取資料:

while(rs.next()){
String name = rs.getString(“name”) ;
String pass = rs.getString(1) ; // 此方法比較高效
}
(列是從左到右編號的,並且從列1開始)

7、關閉JDBC物件
操作完成以後要把所有使用的JDBC物件全都關閉,以釋放JDBC資源,關閉順序和聲 明順序相反:
1、關閉記錄集
2、關閉宣告
3、關閉連線物件

if(rs != null){ // 關閉記錄集
    try{
      rs.close() ;
    }catch(SQLException e){
      e.printStackTrace() ;
    }
}
if(stmt != null){ // 關閉宣告
    try{
      stmt.close() ;
    }catch(SQLException e){
      e.printStackTrace() ;
    }
}
if(conn != null){ // 關閉連線物件
    try{
      conn.close() ;
    }catch(SQLException e){
      e.printStackTrace() ;
    }
}

集合

Arraylist 與 LinkedList 區別

Arraylist:

優點:ArrayList是實現了基於動態陣列的資料結構,因為地址連續,一旦資料儲存好了,查詢操作效率會比較高(在記憶體裡是連著放的)。

缺點:因為地址連續, ArrayList要移動資料,所以插入和刪除操作效率比較低。

LinkedList:

優點:LinkedList基於連結串列的資料結構,地址是任意的,所以在開闢記憶體空間的時候不需要等一個連續的地址,對於新增和刪除操作add和remove,LinedList比較佔優勢。LinkedList 適用於要頭尾操作或插入指定位置的場景

缺點:因為LinkedList要移動指標,所以查詢操作效能比較低。

適用場景分析:

當需要對資料進行對此訪問的情況下選用ArrayList,當需要對資料進行多次增加刪除修改時採用LinkedList。

HashMap 和 Hashtable 的區別

1.hashMap去掉了HashTable 的contains方法,但是加上了containsValue()和containsKey()方法。

2.hashTable同步的,而HashMap是非同步的,效率上逼hashTable要高。

3.hashMap允許空鍵值,而hashTable不允許。

HashMap 和 ConcurrentHashMap 的區別

ConcurrentHashMap是執行緒安全的HashMap的實現。

(1)ConcurrentHashMap對整個桶陣列進行了分割分段(Segment),然後在每一個分段上都用lock鎖進行保護,相對於HashTable的syn關鍵字鎖的粒度更精細了一些,併發效能更好,而HashMap沒有鎖機制,不是執行緒安全的。

(2)HashMap的鍵值對允許有null,但是ConCurrentHashMap都不允許。

資料庫

需要掌握的幾個點:

  1. 資料庫索引原理/複合索引
  2. 資料庫三大正規化
  3. 資料庫事物(四大特性)
  4. 資料庫優化

資料庫事物

  • 原子性
  • 一致性
  • 隔離性
  • 永續性

說說 SQL 優化之道
sql優化的幾種方法

資料庫優化/提高資料庫效能的方法

  1. 排序
  2. 索引
  3. 連續的磁碟儲存
  4. 分類、聚簇 把關係非常緊密,但位於不同表中的行記錄,在磁碟中臨近儲存,當他們做連線運算時,就能迅速得到結果。
  5. 記憶體緩衝
  6. 併發執行
  7. 查詢優化
  8. 日誌和資料分盤儲存

查詢優化和併發執行完全封裝在DBMS中,對資料庫設計者和DBA透明。

其他的方法都需要資料庫設計者和DBA進行配置。

查詢優化
  1. 對查詢進行優化,應儘量避免全表掃描,首先應考慮在where及order by設計的列上簡歷索引。
  2. where避免null判斷(浪費索引,導致全表掃描)
  3. where避免使用!=或<>(浪費索引,導致全表掃描)
  4. where避免使用or,使用union來代替(浪費索引,導致全表掃描)
  5. in或not in慎用(導致全表掃描)
  6. %k%
  7. where避免使用引數,導致全表掃描
  8. where避免使用表示式操作
  9. where避免函式操作
  10. where避免在子句中的”=“左邊進行函式或其他表示式運算
  11. 複合索引
  12. exists代替in
  13. 儘量使用varchar節省空間
  14. 不使用*號,而應該使用具體的欄位列表
  15. 儘量避免大事務操作,提高系統併發能力
  16. 避免頻繁使用臨時表,以減少系統表資源的消耗
雜湊索引適合於什麼特性的表?不適合於什麼特性的表?

雜湊索引基於雜湊表實現,只有精確匹配索引所有列的查詢才有效。
在Mysql中,只有Memory引擎顯式支援雜湊索引。

不支援任何範圍查詢,只支援等值比較查詢
亂序

MySQL千萬或者上億的資料怎麼設計資料庫

垂直分表(根據業務)/水平分表(根據某個欄位)

實際上,水平分表現在最流行的實現方式,是通過水平分庫來實現的。即剛才所說通過id的0-9末尾分成的10個表,分佈在10個mysql資料庫上。這樣可以通過多個低配置主機整合起來,實現高效能。

資料庫

為什麼要用 B-tree
鑑於B-tree具有良好的定位特性,其常被用於對檢索時間要求苛刻的場合,例如:
1、B-tree索引是資料庫中存取和查詢檔案(稱為記錄或鍵值)的一種方法。
2、硬碟中的結點也是B-tree結構的。與記憶體相比,硬碟必須花成倍的時間來存取一個數據元素,這是因為硬碟的機械部件讀寫資料的速度遠遠趕不上純電子媒體的記憶體。與一個結點兩個分支的二元樹相比,B-tree利用多個分支(稱為子樹)的結點,減少獲取記錄時所經歷的結點數,從而達到節省存取時間的目的。

Java Web和框架

Java框架發展

1. 純Servlet開發(Service+Applet)

在servlet中可以通過挨著行輸出HTML語句來實現頁面的樣式和輸出,表現、邏輯、控制、業務全部混在Servlet類中,最多把模型層單獨寫出來。

2. 純JSP開發(Java Server Page)

HTML頁面中可以在<% %>新增Java程式碼。但表現、控制、模型、業務邏輯,依然全部混在JSP中。

3. Model1開發模式(JSP+JavaBean)

JavaBean:JavaBean是一個遵循特定寫法的Java類,這個類必須有一個無參的建構函式,屬性必須私有化,但私有化的屬性必須通過public型別的方法(getter & setter)暴露給其他程,並且方法的命名也必須遵守一定的命名規範。

在JavaEE開發中,JavaBean通常用來封裝資料,對於遵循以上寫法的JavaBean元件,其它程式可以通過反射技術例項化JavaBean物件,並且通過反射那些遵守命名規範的方法,從而獲知JavaBean的屬性,進而呼叫其屬性儲存資料。

即把模型層分離出來了,JavaBean負責了少數的業務邏輯(比如讀寫資料庫)。但JSP仍然需要負責顯示、多數業務邏輯、控制頁面跳轉。

4. Model2開發模式(JSP+JavaBean+Servlet)

MVC開發模式初體現

  • JavaBean 模型層 --包含持久層、業務邏輯層、模型層
  • JSP 檢視層
  • Servlet 控制層

模型層可以繼續分層:dao持久層、service業務邏輯層、entity實體類、util工具包…

servlet

Servlet(Server Applet)是Java servlet的簡稱,稱謂小服務程式或服務聯結器,用Java編寫的服務端程式,主要用於互動式地瀏覽和修改資料,生成動態web內容。

狹義的servlet是指Java語言實現的一個藉口,鋼輪車的Servlet是指任何實現了這個Servlet介面的類,一般情況下,人們將Servlet理解為後者。Servlet執行與支援Java的應用伺服器中。從原理上講,servlet可以相應任何型別的請求,但絕大多數情況下Servlet指用來擴充套件基於HTTP協議的Web伺服器。

Tomcat和servlet的關係:

Tomcat 是Web應用伺服器,是一個Servlet/JSP容器. Tomcat作為Servlet容器,負責處理客戶請求,把請求傳送給Servlet,並將Servlet的響應傳送回給客戶.

而Servlet是一種執行在支援Java語言的伺服器上的元件. Servlet最常見的用途是擴充套件Java Web伺服器功能,提供非常安全的,可移植的,易於使用的CGI替代品.

Servlet的生命週期

伺服器啟動時(web.xml中配置load-on-startup=1,預設為0)或者第一次請求該servlet時,就會初始化一個Servlet物件,也就是會執行初始化方法init(ServletConfigconf),該servlet物件去處理所有客戶端請求,在service(ServletRequest req,ServletResponseres)方法中執行,最後伺服器關閉時,才會銷燬這個servlet物件,執行destroy()方法。

init() -> server() -> destroy()

Servlet的執行過程

Servlet程式是由WEB伺服器呼叫,web伺服器收到客戶端的Servlet訪問請求後:

a. Web伺服器首先檢查是否已經裝載並建立了該Servlet的例項物件。如果是,則直接執行d,否則,執行b。
b. 裝載並建立該Servlet的一個例項物件。
c. 呼叫Servlet例項物件的init()方法。

d. 建立一個用於封裝HTTP請求訊息的HttpServletRequest物件和一個代表HTTP響應訊息的HttpServletResponse物件,然後呼叫Servlet的service()方法並將請求和響應物件作為引數傳遞進去。
e. WEB應用程式被停止或重新啟動之前,Servlet引擎將解除安裝Servlet,並在解除安裝之前呼叫Servlet的destroy()方法。

建立一個簡單的Servlet
public class TestServlet extends HttpServlet {
    @Override
    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("post");
    }
    @Override
    public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("post");
    }
}

為什麼建立Servlet不直接繼承Servlet介面,而是要繼承HttpServlet
HTTPServlet繼承自GenericServlet,而GenericServlet實現了Servlet和ServletConfig介面,實現了Servlet的方法,簡化了編寫Servlet的步驟。

為什麼不重寫Servlet的server()方法,而是重寫doGet和doPost方法?
對於HTTPServlet的Server()方法,作用是判斷瀏覽器過來的請求方式,並執行不同的處理方式(5種),servlet將不同的方法提取了出來,而我們最常用的是get和post方法,所以只需要重寫get和post方法就可以了。

Servlet執行流程

Spring

Spring是一個輕型容器(light-weight container),其核心是Bean工廠(Bean Factory),用以構造我們所需要的M(Model)。在此基礎之上,Spring提供了AOP(Aspect-Oriented
Programming, 面向層面的程式設計)的實現,用它來提供非管理環境下申明方式的事務、安全等服務;對Bean工廠的擴充套件ApplicationContext更加方便我們實現J2EE的應用;DAO/ORM的實現方便我們進行資料庫的開發;Web MVC和Spring Web提供了Java Web應用的框架或與其他流行的Web框架進行整合。

MVC設計思想

MVC就是
M:Model 模型
V:View 檢視
C:Controller 控制器
模型就是封裝業務邏輯和資料的一個一個的模組,控制器就是呼叫這些模組的(java中通常是用Servlet來實現,框架的話很多是用Struts2來實現這一層),檢視就主要是你看到的,比如JSP等.
當用戶發出請求的時候,