java面試題文檔(QA)

  • 基礎篇
    • 1、 Java語言有哪些特點
    • 2、面向對象和面向過程的區別
    • 3 、八種基本數據類型的大小,以及他們的封裝類
    • 4、標識符的命名規則。
    • 5、instanceof 關鍵字的作用
    • 6、Java自動裝箱與拆箱
    • 7、 重載和重寫的區別
    • 8、 equals與==的區別
    • 9、 Hashcode的作用
    • 10、String、String StringBuffer 和 StringBuilder 的區別是什么?
    • 11、ArrayList和linkedList的區別
    • 12、 HashMap和HashTable的區別
    • 13、 Collection包結構,與Collections的區別
    • 14、 Java的四種引用,強弱軟虛
    • 15、 泛型常用特點
    • 16、Java創建對象有幾種方式?
    • 17、有沒有可能兩個不相等的對象有相同的hashcode
    • 18、深拷貝和淺拷貝的區別是什么?
    • 19、final有哪些用法?
    • 20、static都有哪些用法?
    • 21、3*0.1==0.3返回值是什么
    • 22、a=a+b與a+=b有什么區別嗎?
    • 23、try catch finally,try里有return,finally還執行么?
    • 24、 Excption與Error包結構
    • 25、OOM你遇到過哪些情況,SOF你遇到過哪些情況
    • 26、 簡述線程、程序、進程的基本概念。以及他們之間關系是什么?
    • 27、線程有哪些基本狀態?
    • 28、Java 序列化中如果有些字段不想進行序列化,怎么辦?
    • 29、Java 中 IO 流
    • 30、 Java IO與 NIO的區別
    • 31、java反射的作用于原理
    • 32、說說List,Set,Map三者的區別?
  • JVM篇
    • 1、知識點匯總
    • 2、知識點詳解:
    • 3、類加載與卸載
    • 4、簡述一下JVM的內存模型
      - 線程私有區
      - 線程共享區
    • 5、堆和棧的區別
    • 6、 什么時候會觸發FullGC
    • 7、什么是Java虛擬機?為什么Java被稱作是“平臺無關的編程語言”?
    • 8、Java內存結構
    • 9、對象分配規則
    • 10、描述一下JVM加載class文件的原理機制?
    • 11、Java對象創建過程
    • 12、類的生命周期
    • 13、簡述Java的對象結構
    • 14、如何判斷對象可以被回收?
    • 15、JVM的永久代中會發生垃圾回收么?
    • 16、垃圾收集算法
    • 17、調優命令有哪些?
    • 18、調優工具
    • 19、Minor GC與Full GC分別在什么時候發生?
    • 20、你知道哪些JVM性能調優
  • 多線程&并發篇
    • 1、Java中實現多線程有幾種方法
    • 2、如何停止一個正在運行的線程
    • 3、notify()和notifyAll()有什么區別?
    • 4、sleep()和wait() 有什么區別?
    • 5、volatile 是什么?可以保證有序性嗎?
    • 6、Thread 類中的start() 和 run() 方法有什么區別?
    • 7、為什么wait, notify 和 notifyAll這些方法不在thread類里面?
    • 8、為什么wait和notify方法要在同步塊中調用?
    • 9、Java中interrupted 和 isInterruptedd方法的區別?
    • 10、Java中synchronized 和 ReentrantLock 有什么不同?
    • 11、有三個線程T1,T2,T3,如何保證順序執行?
    • 12、SynchronizedMap和ConcurrentHashMap有什么區別?
    • 13、什么是線程安全
    • 14、Thread類中的yield方法有什么作用?
    • 15、Java線程池中submit() 和 execute()方法有什么區別?
    • 16、說一說自己對于 synchronized 關鍵字的了解
    • 17、說說自己是怎么使用 synchronized 關鍵字,在項目中用到了嗎synchronized關鍵字最主要的三種使用方式:
    • 18、什么是線程安全?Vector是一個線程安全類嗎?
    • 19、 volatile關鍵字的作用?
    • 20、常用的線程池有哪些?
    • 21、簡述一下你對線程池的理解
    • 22、Java程序是如何執行的
  • Spring篇
    • 1、 Spring的IOC和AOP機制?
    • 2、 Spring中Autowired和Resource關鍵字的區別?
    • 3、依賴注入的方式有幾種,各是什么?
    • 4、講一下什么是Spring
    • 5、Spring MVC流程
    • 6、SpringMVC怎么樣設定重定向和轉發的?
    • 7、 SpringMVC常用的注解有哪些?
    • 8、 Spring的AOP理解:
    • 9、Spring的IOC理解
    • 10、解釋一下spring bean的生命周期
    • 11、 解釋Spring支持的幾種bean的作用域。
    • 12、 Spring基于xml注入bean的幾種方式:
    • 13、Spring框架中都用到了哪些設計模式?
  • MyBatis篇
    • 1、什么是MyBatis
    • 2、MyBatis的優點和缺點
    • 3、#{}和${}的區別是什么?
    • 4、當實體類中的屬性名和表中的字段名不一樣 ,怎么辦 ?
    • 5、Mybatis是如何進行分頁的?分頁插件的原理是什么?
    • 6、Mybatis是如何將sql執行結果封裝為目標對象并返回的?都有哪些映射形式?
    • 7、 如何執行批量插入?
    • 8、Xml映射文件中,除了常見的select|insert|updae|delete標簽之外,還有哪些標簽?
    • 9、MyBatis實現一對一有幾種方式?具體怎么操作的?
    • 10、Mybatis是否支持延遲加載?如果支持,它的實現原理是什么?
    • 11、Mybatis的一級、二級緩存:
  • SpringBoot篇
    • 1、什么是SpringBoot?為什么要用SpringBoot
    • 2、Spring Boot 的核心注解是哪個?它主要由哪幾個注解組成的?
    • 3、運行Spring Boot有哪幾種方式?
    • 4、如何理解 Spring Boot 中的 Starters?
    • 5、 如何在Spring Boot啟動的時候運行一些特定的代碼?
    • 6、 Spring Boot 需要獨立的容器運行嗎?
    • 7、 Spring Boot中的監視器是什么?
    • 8、 如何使用Spring Boot實現異常處理?
    • 9、 你如何理解 Spring Boot 中的 Starters?
    • 10、 springboot常用的starter有哪些
    • 11、 SpringBoot 實現熱部署有哪幾種方式?
    • 12、 如何理解 Spring Boot 配置加載順序?
    • 13、 Spring Boot 的核心配置文件有哪幾個?它們的區別是什么?
    • 14、如何集成 Spring Boot 和 ActiveMQ?
    • 15、如何重新加載Spring Boot上的更改,而無需重新啟動服務器?
    • 16、 Spring Boot、Spring MVC 和 Spring 有什么區別?
    • 17、 能否舉一個例子來解釋更多 Staters 的內容?
    • 18、 Spring Boot 還提供了其它的哪些 Starter Project Options?
  • MySQL篇
    • 1、數據庫的三范式是什么
    • 2、數據庫引擎有哪些
    • 3、InnoDB與MyISAM的區別
    • 4、數據庫的事務
    • 5、索引問題
    • 6、SQL優化
    • 7、簡單說一說drop、delete與truncate的區別
    • 8、什么是視圖
    • 9、 什么是內聯接、左外聯接、右外聯接?
    • 10、并發事務帶來哪些問題?
    • 11、事務隔離級別有哪些?MySQL的默認隔離級別是?
    • 12、大表如何優化?
      • 1. 限定數據的范圍
      • 2. 讀/寫分離
      • 3. 垂直分區
      • 4. 水平分區
    • 13、分庫分表之后,id 主鍵如何處理?
    • 14、mysql有關權限的表都有哪幾個
    • 15、mysql有哪些數據類型
    • 16、創建索引的三種方式,刪除索引
  • Redis篇
    • 1、Redis持久化機制
    • 2、緩存雪崩、緩存穿透、緩存預熱、緩存更新、緩存降級等問題
    • 3、熱點數據和冷數據是什么
    • 4、Memcache與Redis的區別都有哪些?
    • 5、單線程的redis為什么這么快
    • 6、redis的數據類型,以及每種數據類型的使用場景
    • 7、redis的過期策略以及內存淘汰機制
    • 8、Redis 為什么是單線程的
    • 9、Redis 常見性能問題和解決方案?
    • 10、為什么Redis的操作是原子性的,怎么保證原子性的?
    • 11、Redis事務
  • SpringCloud篇
    • 1、什么是SpringCloud
    • 2、什么是微服務
    • 3、SpringCloud有什么優勢
    • 4、 什么是服務熔斷?什么是服務降級?
    • 5、 Eureka和zookeeper都可以提供服務注冊與發現的功能,請說說兩個的區別?
    • 6、SpringBoot和SpringCloud的區別?
    • 7、負載平衡的意義什么?
    • 8、什么是Hystrix?它如何實現容錯?
    • 9、什么是Hystrix斷路器?我們需要它嗎?
    • 10、說說 RPC 的實現原理
  • Nginx篇
    • 1、簡述一下什么是Nginx,它有什么優勢和功能?
    • 2、Nginx是如何處理一個HTTP請求的呢?
    • 3、列舉一些Nginx的特性
    • 4、請列舉Nginx和Apache 之間的不同點
    • 5、在Nginx中,如何使用未定義的服務器名稱來阻止處理請求?
    • 6、請解釋Nginx服務器上的Master和Worker進程分別是什么?
    • 7、請解釋代理中的正向代理和反向代理
    • 8、解釋Nginx用途
  • MQ篇
    • 1、為什么使用MQ
    • 2、MQ優缺點
    • 3、Kafka、ActiveMQ、RabbitMQ、RocketMQ 都有什么區別?
    • 4、如何保證高可用的?
    • 5、如何保證消息的可靠傳輸?如果消息丟了怎么辦
    • 6、如何保證消息的順序性
    • 7、 如何解決消息隊列的延時以及過期失效問題?消息隊列滿了以后該怎么處理?有幾百萬消息持續積壓幾小時,說說怎么解決?
    • 8、設計MQ的思路
  • 數據結構與算法篇
    • 1、常用的數據結構
      - 1. 數組
      - 2. 棧
      - 3. 隊列
      - 4. 鏈表
      - 5. 圖
      - 6. 樹
      - 7. 前綴樹
      - 8. 哈希表
    • 2、 數據里有{1,2,3,4,5,6,7,8,9},請隨機打亂順序,生成一個新的數組(請以代碼實現)
    • 3、 寫出代碼判斷一個整數是不是2的階次方(請代碼實現,謝絕調用API方法)
    • 4、 假設今日是2015年3月1日,星期日,請算出13個月零6天后是星期幾,距離現在多少天(請用代碼實現,謝絕調用API方法)
    • 5、 有兩個籃子,分別為A 和 B,籃子A里裝有雞蛋,籃子B里裝有蘋果,請用面向對象的思想實現兩個籃子里的物品交換(請用代碼實現)
    • 6、更多算法練習
  • Linux篇
    • 1、 絕對路徑用什么符號表示?當前目錄、上層目錄用什么表示?主目錄用什么表示? 切換目錄用什么命令?
    • 2、 怎么查看當前進程?怎么執行退出?怎么查看當前路徑?
    • 3、查看文件有哪些命令
    • 4、列舉幾個常用的Linux命令
    • 5、你平時是怎么查看日志的?
  • 簡歷篇
    • 為什么說簡歷很重要?
    • 先從面試來說
    • 再從面試說起
    • 必知必會的幾點
    • 必須了解的兩大法則
    • 項目經歷怎么寫
    • 專業技能怎么寫
    • 排版注意事項
    • 其他一些小tips

基礎篇

1、 Java語言有哪些特點

1、簡單易學、有豐富的類庫

2、面向對象(Java最重要的特性,讓程序耦合度更低,內聚性更高)

3、與平臺無關性(JVM是Java跨平臺使用的根本)

4、可靠安全

5、支持多線程

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

面向過程:是分析解決問題的步驟,然后用函數把這些步驟一步一步地實現,然后在使用的時候一一調用則可。性能較高,所以單片機、嵌入式開發等一般采用面向過程開發

面向對象:是把構成問題的事務分解成各個對象,而建立對象的目的也不是為了完成一個個步驟,而是為了描述某個事物在解決整個問題的過程中所發生的行為。面向對象有封裝、繼承、多態的特性,所以易維護、易復用、易擴展。可以設計出低耦合的系統。 但是性能上來說,比面向過程要低。

3 、八種基本數據類型的大小,以及他們的封裝類

基本類型大小(字節)默認值封裝類
byte1(byte)0Byte
short2(short)0Short
int40Integer
long80LLong
float40.0fFloat
double80.0dDouble
boolean-falseBoolean
char2\u0000(null)Character

注:

1.int是基本數據類型,Integer是int的封裝類,是引用類型。int默認值是0,而Integer默認值是null,所以Integer能區分出0和null的情況。一旦java看到null,就知道這個引用還沒有指向某個對象,再任何引用使用前,必須為其指定一個對象,否則會報錯。

2.基本數據類型在聲明時系統會自動給它分配空間,而引用類型聲明時只是分配了引用空間,必須通過實例化開辟數據空間之后才可以賦值。數組對象也是一個引用對象,將一個數組賦值給另一個數組時只是復制了一個引用,所以通過某一個數組所做的修改在另一個數組中也看的見。

雖然定義了boolean這種數據類型,但是只對它提供了非常有限的支持。在Java虛擬機中沒有任何供boolean值專用的字節碼指令,Java語言表達式所操作的boolean值,在編譯之后都使用Java虛擬機中的int數據類型來代替,而boolean數組將會被編碼成Java虛擬機的byte數組,每個元素boolean元素占8位。這樣我們可以得出boolean類型占了單獨使用是4個字節,在數組中又是1個字節。使用int的原因是,對于當下32位的處理器(CPU)來說,一次處理數據是32位(這里不是指的是32/64位系統,而是指CPU硬件層面),具有高效存取的特點。

4、標識符的命名規則。

標識符的含義:
是指在程序中,我們自己定義的內容,譬如,類的名字,方法名稱以及變量名稱等等,都是標識符。

命名規則:(硬性要求)
標識符可以包含英文字母,0-9的數字,$以及_
標識符不能以數字開頭
標識符不是關鍵字

命名規范:(非硬性要求)
類名規范:首字符大寫,后面每個單詞首字母大寫(大駝峰式)。
變量名規范:首字母小寫,后面每個單詞首字母大寫(小駝峰式)。
方法名規范:同變量名。

5、instanceof 關鍵字的作用

instanceof 嚴格來說是Java中的一個雙目運算符,用來測試一個對象是否為一個類的實例,用法為:

boolean result = obj instanceof Class

其中 obj 為一個對象,Class 表示一個類或者一個接口,當 obj 為 Class 的對象,或者是其直接或間接子類,或者是其接口的實現類,結果result 都返回 true,否則返回false。

注意:編譯器會檢查 obj 是否能轉換成右邊的class類型,如果不能轉換則直接報錯,如果不能確定類型,則通過編譯,具體看運行時定。

int i = 0;
System.out.println(i instanceof Integer);//編譯不通過  i必須是引用類型,不能是基本類型
System.out.println(i instanceof Object);//編譯不通過
Integer integer = new Integer(1);
System.out.println(integer instanceof  Integer);//true
//false   ,在 JavaSE規范 中對 instanceof 運算符的規定就是:如果 obj 為 null,那么將返回 false。
System.out.println(null instanceof Object);

6、Java自動裝箱與拆箱

裝箱就是自動將基本數據類型轉換為包裝器類型(int–>Integer);調用方法:Integer的valueOf(int) 方法

**拆箱就是自動將包裝器類型轉換為基本數據類型(Integer–>int)。調用方法:Integer的intValue方法 **

在Java SE5之前,如果要生成一個數值為10的Integer對象,必須這樣進行:

Integer i = new Integer(10);

而在從Java SE5開始就提供了自動裝箱的特性,如果要生成一個數值為10的Integer對象,只需要這樣就可以了:

Integer i = 10;

面試題1: 以下代碼會輸出什么?

public class Main {public static void main(String[] args) {Integer i1 = 100;Integer i2 = 100;Integer i3 = 200;Integer i4 = 200;System.out.println(i1==i2);System.out.println(i3==i4);}
}

運行結果:

true
false

為什么會出現這樣的結果?輸出結果表明i1和i2指向的是同一個對象,而i3和i4指向的是不同的對象。此時只需一看源碼便知究竟,下面這段代碼是Integer的valueOf方法的具體實現:

public static Integer valueOf(int i) {if(i >= -128 && i <= IntegerCache.high)return IntegerCache.cache[i + 128];elsereturn new Integer(i);}

其中IntegerCache類的實現為:

private static class IntegerCache {static final int high;static final Integer cache[];static {final int low = -128;// high value may be configured by propertyint h = 127;if (integerCacheHighPropValue != null) {// Use Long.decode here to avoid invoking methods that// require Integer's autoboxing cache to be initializedint i = Long.decode(integerCacheHighPropValue).intValue();i = Math.max(i, 127);// Maximum array size is Integer.MAX_VALUEh = Math.min(i, Integer.MAX_VALUE - -low);}high = h;cache = new Integer[(high - low) + 1];int j = low;for(int k = 0; k < cache.length; k++)cache[k] = new Integer(j++);}private IntegerCache() {}}

從這2段代碼可以看出,在通過valueOf方法創建Integer對象的時候,如果數值在[-128,127]之間,便返回指向IntegerCache.cache中已經存在的對象的引用;否則創建一個新的Integer對象。

上面的代碼中i1和i2的數值為100,因此會直接從cache中取已經存在的對象,所以i1和i2指向的是同一個對象,而i3和i4則是分別指向不同的對象。

面試題2:以下代碼輸出什么?

public class Main {public static void main(String[] args) {Double i1 = 100.0;Double i2 = 100.0;Double i3 = 200.0;Double i4 = 200.0;System.out.println(i1==i2);System.out.println(i3==i4);}
}

運行結果:

false
false

原因: 在某個范圍內的整型數值的個數是有限的,而浮點數卻不是。

7、 重載和重寫的區別

重寫(Override)

從字面上看,重寫就是 重新寫一遍的意思。其實就是在子類中把父類本身有的方法重新寫一遍。子類繼承了父類原有的方法,但有時子類并不想原封不動的繼承父類中的某個方法,所以在方法名,參數列表,返回類型(除過子類中方法的返回值是父類中方法返回值的子類時)都相同的情況下, 對方法體進行修改或重寫,這就是重寫。但要注意子類函數的訪問修飾權限不能少于父類的。

public class Father {public static void main(String[] args) {// TODO Auto-generated method stubSon s = new Son();s.sayHello();}public void sayHello() {System.out.println("Hello");}
}class Son extends Father{@Overridepublic void sayHello() {// TODO Auto-generated method stubSystem.out.println("hello by ");}}

重寫 總結:
1.發生在父類與子類之間
2.方法名,參數列表,返回類型(除過子類中方法的返回類型是父類中返回類型的子類)必須相同
3.訪問修飾符的限制一定要大于被重寫方法的訪問修飾符(public>protected>default>private)
4.重寫方法一定不能拋出新的檢查異常或者比被重寫方法申明更加寬泛的檢查型異常

重載(Overload)

在一個類中,同名的方法如果有不同的參數列表(參數類型不同、參數個數不同甚至是參數順序不同)則視為重載。同時,重載對返回類型沒有要求,可以相同也可以不同,但不能通過返回類型是否相同來判斷重載

public class Father {public static void main(String[] args) {// TODO Auto-generated method stubFather s = new Father();s.sayHello();s.sayHello("wintershii");}public void sayHello() {System.out.println("Hello");}public void sayHello(String name) {System.out.println("Hello" + " " + name);}
}

重載 總結:
1.重載Overload是一個類中多態性的一種表現
2.重載要求同名方法的參數列表不同(參數類型,參數個數甚至是參數順序)
3.重載的時候,返回值類型可以相同也可以不相同。無法以返回型別作為重載函數的區分標準

8、 equals與==的區別

== :

== 比較的是變量(棧)內存中存放的對象的(堆)內存地址,用來判斷兩個對象的地址是否相同,即是否是指相同一個對象。比較的是真正意義上的指針操作。

1、比較的是操作符兩端的操作數是否是同一個對象。
2、兩邊的操作數必須是同一類型的(可以是父子類之間)才能編譯通過。
3、比較的是地址,如果是具體的阿拉伯數字的比較,值相等則為true,如:
int a=10 與 long b=10L 與 double c=10.0都是相同的(為true),因為他們都指向地址為10的堆。

equals

equals用來比較的是兩個對象的內容是否相等,由于所有的類都是繼承自java.lang.Object類的,所以適用于所有對象,如果沒有對該方法進行覆蓋的話,調用的仍然是Object類中的方法,而Object中的equals方法返回的卻是==的判斷。

總結:

所有比較是否相等時,都是用equals 并且在對常量相比較時,把常量寫在前面,因為使用object的equals object可能為null 則空指針

在阿里的代碼規范中只使用equals ,阿里插件默認會識別,并可以快速修改,推薦安裝阿里插件來排查老代碼使用“==”,替換成equals

9、 Hashcode的作用

java的集合有兩類,一類是List,還有一類是Set。前者有序可重復,后者無序不重復。當我們在set中插入的時候怎么判斷是否已經存在該元素呢,可以通過equals方法。但是如果元素太多,用這樣的方法就會比較滿。

于是有人發明了哈希算法來提高集合中查找元素的效率。 這種方式將集合分成若干個存儲區域,每個對象可以計算出一個哈希碼,可以將哈希碼分組,每組分別對應某個存儲區域,根據一個對象的哈希碼就可以確定該對象應該存儲的那個區域。

hashCode方法可以這樣理解:它返回的就是根據對象的內存地址換算出的一個值。這樣一來,當集合要添加新的元素時,先調用這個元素的hashCode方法,就一下子能定位到它應該放置的物理位置上。如果這個位置上沒有元素,它就可以直接存儲在這個位置上,不用再進行任何比較了;如果這個位置上已經有元素了,就調用它的equals方法與新元素進行比較,相同的話就不存了,不相同就散列其它的地址。這樣一來實際調用equals方法的次數就大大降低了,幾乎只需要一兩次。

10、String、String StringBuffer 和 StringBuilder 的區別是什么?

String是只讀字符串,它并不是基本數據類型,而是一個對象。從底層源碼來看是一個final類型的字符數組,所引用的字符串不能被改變,一經定義,無法再增刪改。每次對String的操作都會生成新的String對象。

private final char value[];

每次+操作 : 隱式在堆上new了一個跟原字符串相同的StringBuilder對象,再調用append方法 拼接+后面的字符。

StringBuffer和StringBuilder他們兩都繼承了AbstractStringBuilder抽象類,從AbstractStringBuilder抽象類中我們可以看到

/*** The value is used for character storage.*/char[] value;

他們的底層都是可變的字符數組,所以在進行頻繁的字符串操作時,建議使用StringBuffer和StringBuilder來進行操作。 另外StringBuffer 對方法加了同步鎖或者對調用的方法加了同步鎖,所以是線程安全的。StringBuilder 并沒有對方法進行加同步鎖,所以是非線程安全的。

11、ArrayList和linkedList的區別

Array(數組)是基于索引(index)的數據結構,它使用索引在數組中搜索和讀取數據是很快的。

Array獲取數據的時間復雜度是O(1),但是要刪除數據卻是開銷很大,因為這需要重排數組中的所有數據, (因為刪除數據以后, 需要把后面所有的數據前移)

缺點: 數組初始化必須指定初始化的長度, 否則報錯

例如:

int[] a = new int[4];//推介使用int[] 這種方式初始化int c[] = {23,43,56,78};//長度:4,索引范圍:[0,3]

List—是一個有序的集合,可以包含重復的元素,提供了按索引訪問的方式,它繼承Collection。

List有兩個重要的實現類:ArrayList和LinkedList

ArrayList: 可以看作是能夠自動增長容量的數組

ArrayList的toArray方法返回一個數組

ArrayList的asList方法返回一個列表

ArrayList底層的實現是Array, 數組擴容實現

LinkList是一個雙鏈表,在添加和刪除元素時具有比ArrayList更好的性能.但在get與set方面弱于ArrayList.當然,這些對比都是指數據量很大或者操作很頻繁。

12、 HashMap和HashTable的區別

1、兩者父類不同

HashMap是繼承自AbstractMap類,而Hashtable是繼承自Dictionary類。不過它們都實現了同時實現了map、Cloneable(可復制)、Serializable(可序列化)這三個接口。

2、對外提供的接口不同

Hashtable比HashMap多提供了elments() 和contains() 兩個方法。
elments() 方法繼承自Hashtable的父類Dictionnary。elements() 方法用于返回此Hashtable中的value的枚舉。

contains()方法判斷該Hashtable是否包含傳入的value。它的作用與containsValue()一致。事實上,contansValue() 就只是調用了一下contains() 方法。

3、對null的支持不同

Hashtable:key和value都不能為null。

HashMap:key可以為null,但是這樣的key只能有一個,因為必須保證key的唯一性;可以有多個key值對應的value為null。

4、安全性不同

HashMap是線程不安全的,在多線程并發的環境下,可能會產生死鎖等問題,因此需要開發人員自己處理多線程的安全問題。

Hashtable是線程安全的,它的每個方法上都有synchronized 關鍵字,因此可直接用于多線程中。

雖然HashMap是線程不安全的,但是它的效率遠遠高于Hashtable,這樣設計是合理的,因為大部分的使用場景都是單線程。當需要多線程操作的時候可以使用線程安全的ConcurrentHashMap。

ConcurrentHashMap雖然也是線程安全的,但是它的效率比Hashtable要高好多倍。因為ConcurrentHashMap使用了分段鎖,并不對整個數據進行鎖定。

5、初始容量大小和每次擴充容量大小不同

6、計算hash值的方法不同

13、 Collection包結構,與Collections的區別

Collection是集合類的上級接口,子接口有 Set、List、LinkedList、ArrayList、Vector、Stack、Set;

Collections是集合類的一個幫助類, 它包含有各種有關集合操作的靜態多態方法,用于實現對各種集合的搜索、排序、線程安全化等操作。此類不能實例化,就像一個工具類,服務于Java的Collection框架。

14、 Java的四種引用,強弱軟虛

  • 強引用

    強引用是平常中使用最多的引用,強引用在程序內存不足(OOM)的時候也不會被回收,使用方式:

    String str = new String("str");
    
  • 軟引用

    軟引用在程序內存不足時,會被回收,使用方式:

    // 注意:wrf這個引用也是強引用,它是指向SoftReference這個對象的,
    // 這里的軟引用指的是指向new String("str")的引用,也就是SoftReference類中T
    SoftReference<String> wrf = new SoftReference<String>(new String("str"));
    

    可用場景: 創建緩存的時候,創建的對象放進緩存中,當內存不足時,JVM就會回收早先創建的對象。

  • 弱引用

    弱引用就是只要JVM垃圾回收器發現了它,就會將之回收,使用方式:

    WeakReference<String> wrf = new WeakReference<String>(str);
    

    可用場景: Java源碼中的java.util.WeakHashMap中的key就是使用弱引用,我的理解就是,一旦我不需要某個引用,JVM會自動幫我處理它,這樣我就不需要做其它操作。

  • 虛引用

    虛引用的回收機制跟弱引用差不多,但是它被回收之前,會被放入ReferenceQueue中。注意哦,其它引用是被JVM回收后才被傳入ReferenceQueue中的。由于這個機制,所以虛引用大多被用于引用銷毀前的處理工作。還有就是,虛引用創建的時候,必須帶有ReferenceQueue,使用例子:

    PhantomReference<String> prf = new PhantomReference<String>(new String("str"), new ReferenceQueue<>());
    

    可用場景: 對象銷毀前的一些操作,比如說資源釋放等。**Object.finalize()雖然也可以做這類動作,但是這個方式即不安全又低效

上訴所說的幾類引用,都是指對象本身的引用,而不是指Reference的四個子類的引用(SoftReference等)。

15、 泛型常用特點

泛型是Java SE 1.5之后的特性, 《Java 核心技術》中對泛型的定義是:

“泛型” 意味著編寫的代碼可以被不同類型的對象所重用。

“泛型”,顧名思義,“泛指的類型”。我們提供了泛指的概念,但具體執行的時候卻可以有具體的規則來約束,比如我們用的非常多的ArrayList就是個泛型類,ArrayList作為集合可以存放各種元素,如Integer, String,自定義的各種類型等,但在我們使用的時候通過具體的規則來約束,如我們可以約束集合中只存放Integer類型的元素,如

List<Integer> iniData = new ArrayList<>()

使用泛型的好處?

以集合來舉例,使用泛型的好處是我們不必因為添加元素類型的不同而定義不同類型的集合,如整型集合類,浮點型集合類,字符串集合類,我們可以定義一個集合來存放整型、浮點型,字符串型數據,而這并不是最重要的,因為我們只要把底層存儲設置了Object即可,添加的數據全部都可向上轉型為Object。 更重要的是我們可以通過規則按照自己的想法控制存儲的數據類型。

16、Java創建對象有幾種方式?

java中提供了以下四種創建對象的方式:

  • new創建新對象
  • 通過反射機制
  • 采用clone機制
  • 通過序列化機制

17、有沒有可能兩個不相等的對象有相同的hashcode

有可能.在產生hash沖突時,兩個不相等的對象就會有相同的 hashcode 值.當hash沖突產生時,一般有以下幾種方式來處理:

  • 拉鏈法:每個哈希表節點都有一個next指針,多個哈希表節點可以用next指針構成一個單向鏈表,被分配到同一個索引上的多個節點可以用這個單向鏈表進行存儲.
  • 開放定址法:一旦發生了沖突,就去尋找下一個空的散列地址,只要散列表足夠大,空的散列地址總能找到,并將記錄存入
  • 再哈希:又叫雙哈希法,有多個不同的Hash函數.當發生沖突時,使用第二個,第三個….等哈希函數計算地址,直到無沖突.

18、深拷貝和淺拷貝的區別是什么?

  • 淺拷貝:被復制對象的所有變量都含有與原來的對象相同的值,而所有的對其他對象的引用仍然指向原來的對象.換言之,淺拷貝僅僅復制所考慮的對象,而不復制它所引用的對象.

  • 深拷貝:被復制對象的所有變量都含有與原來的對象相同的值.而那些引用其他對象的變量將指向被復制過的新對象.而不再是原有的那些被引用的對象.換言之.深拷貝把要復制的對象所引用的對象都復制了一遍.

19、final有哪些用法?

final也是很多面試喜歡問的地方,但我覺得這個問題很無聊,通常能回答下以下5點就不錯了:

  • 被final修飾的類不可以被繼承
  • 被final修飾的方法不可以被重寫
  • 被final修飾的變量不可以被改變.如果修飾引用,那么表示引用不可變,引用指向的內容可變.
  • 被final修飾的方法,JVM會嘗試將其內聯,以提高運行效率
  • 被final修飾的常量,在編譯階段會存入常量池中.

除此之外,編譯器對final域要遵守的兩個重排序規則更好:

在構造函數內對一個final域的寫入,與隨后把這個被構造對象的引用賦值給一個引用變量,這兩個操作之間不能重排序
初次讀一個包含final域的對象的引用,與隨后初次讀這個final域,這兩個操作之間不能重排序.

20、static都有哪些用法?

所有的人都知道static關鍵字這兩個基本的用法:靜態變量和靜態方法.也就是被static所修飾的變量/方法都屬于類的靜態資源,類實例所共享.

除了靜態變量和靜態方法之外,static也用于靜態塊,多用于初始化操作:

public calss PreCache{static{//執行相關操作}
}

此外static也多用于修飾內部類,此時稱之為靜態內部類.

最后一種用法就是靜態導包,即import static.import static是在JDK 1.5之后引入的新特性,可以用來指定導入某個類中的靜態資源,并且不需要使用類名,可以直接使用資源名,比如:

import static java.lang.Math.*;public class Test{public static void main(String[] args){//System.out.println(Math.sin(20));傳統做法System.out.println(sin(20));}
}

21、3*0.1==0.3返回值是什么

false,因為有些浮點數不能完全精確的表示出來.

22、a=a+b與a+=b有什么區別嗎?

+=操作符會進行隱式自動類型轉換,此處a+=b隱式的將加操作的結果類型強制轉換為持有結果的類型,而a=a+b則不會自動進行類型轉換.如:

byte a = 127;
byte b = 127;
b = a + b; // 報編譯錯誤:cannot convert from int to byte
b += a; 

以下代碼是否有錯,有的話怎么改?

short s1= 1;
s1 = s1 + 1;

有錯誤.short類型在進行運算時會自動提升為int類型,也就是說s1+1的運算結果是int類型,而s1是short類型,此時編譯器會報錯.

正確寫法:

short s1= 1; 
s1 += 1; 

+=操作符會對右邊的表達式結果強轉匹配左邊的數據類型,所以沒錯.

23、try catch finally,try里有return,finally還執行么?

執行,并且finally的執行早于try里面的return

結論:

1、不管有木有出現異常,finally塊中代碼都會執行;

2、當try和catch中有return時,finally仍然會執行;

3、finally是在return后面的表達式運算后執行的(此時并沒有返回運算后的值,而是先把要返回的值保存起來,管finally中的代碼怎么樣,返回的值都不會改變,任然是之前保存的值),所以函數返回值是在finally執行前確定的;

4、finally中最好不要包含return,否則程序會提前退出,返回值不是try或catch中保存的返回值。

24、 Excption與Error包結構

Java可拋出(Throwable)的結構分為三種類型:被檢查的異常(CheckedException),運行時異常(RuntimeException),錯誤(Error)。

1、運行時異常

定義:RuntimeException及其子類都被稱為運行時異常。

特點:Java編譯器不會檢查它。也就是說,當程序中可能出現這類異常時,倘若既"沒有通過throws聲明拋出它",也"沒有用try-catch語句捕獲它",還是會編譯通過。例如,除數為零時產生的ArithmeticException異常,數組越界時產生的IndexOutOfBoundsException異常,fail-fast機制產生的ConcurrentModificationException異常(java.util包下面的所有的集合類都是快速失敗的,“快速失敗”也就是fail-fast,它是Java集合的一種錯誤檢測機制。當多個線程對集合進行結構上的改變的操作時,有可能會產生fail-fast機制。記住是有可能,而不是一定。例如:假設存在兩個線程(線程1、線程2),線程1通過Iterator在遍歷集合A中的元素,在某個時候線程2修改了集合A的結構(是結構上面的修改,而不是簡單的修改集合元素的內容),那么這個時候程序就會拋出 ConcurrentModificationException 異常,從而產生fail-fast機制,這個錯叫并發修改異常。Fail-safe,java.util.concurrent包下面的所有的類都是安全失敗的,在遍歷過程中,如果已經遍歷的數組上的內容變化了,迭代器不會拋出ConcurrentModificationException異常。如果未遍歷的數組上的內容發生了變化,則有可能反映到迭代過程中。這就是ConcurrentHashMap迭代器弱一致的表現。ConcurrentHashMap的弱一致性主要是為了提升效率,是一致性與效率之間的一種權衡。要成為強一致性,就得到處使用鎖,甚至是全局鎖,這就與Hashtable和同步的HashMap一樣了。)等,都屬于運行時異常。

常見的五種運行時異常:

ClassCastException(類轉換異常)

IndexOutOfBoundsException(數組越界)

NullPointerException(空指針異常)

ArrayStoreException(數據存儲異常,操作數組是類型不一致)

BufferOverflowException

2、被檢查異常

定義:Exception類本身,以及Exception的子類中除了"運行時異常"之外的其它子類都屬于被檢查異常。

特點 : Java編譯器會檢查它。 此類異常,要么通過throws進行聲明拋出,要么通過try-catch進行捕獲處理,否則不能通過編譯。例如,CloneNotSupportedException就屬于被檢查異常。當通過clone()接口去克隆一個對象,而該對象對應的類沒有實現Cloneable接口,就會拋出CloneNotSupportedException異常。被檢查異常通常都是可以恢復的。
如:

IOException

FileNotFoundException

SQLException

被檢查的異常適用于那些不是因程序引起的錯誤情況,比如:讀取文件時文件不存在引發的FileNotFoundException。然而,不被檢查的異常通常都是由于糟糕的編程引起的,比如:在對象引用時沒有確保對象非空而引起的NullPointerException

3、錯誤

定義 : Error類及其子類。

特點 : 和運行時異常一樣,編譯器也不會對錯誤進行檢查。

當資源不足、約束失敗、或是其它程序無法繼續運行的條件發生時,就產生錯誤。程序本身無法修復這些錯誤的。例如,VirtualMachineError就屬于錯誤。出現這種錯誤會導致程序終止運行。OutOfMemoryError、ThreadDeath。

Java虛擬機規范規定JVM的內存分為了好幾塊,比如堆,棧,程序計數器,方法區等

25、OOM你遇到過哪些情況,SOF你遇到過哪些情況

OOM

1,OutOfMemoryError異常

除了程序計數器外,虛擬機內存的其他幾個運行時區域都有發生OutOfMemoryError(OOM)異常的可能。

Java Heap 溢出:

一般的異常信息:java.lang.OutOfMemoryError:Java heap spacess。

java堆用于存儲對象實例,我們只要不斷的創建對象,并且保證GC Roots到對象之間有可達路徑來避免垃圾回收機制清除這些對象,就會在對象數量達到最大堆容量限制后產生內存溢出異常。

出現這種異常,一般手段是先通過內存映像分析工具(如Eclipse Memory Analyzer)對dump出來的堆轉存快照進行分析,重點是確認內存中的對象是否是必要的,先分清是因為內存泄漏(Memory Leak)還是內存溢出(Memory Overflow)。

如果是內存泄漏,可進一步通過工具查看泄漏對象到GCRoots的引用鏈。于是就能找到泄漏對象是通過怎樣的路徑與GC Roots相關聯并導致垃圾收集器無法自動回收。

如果不存在泄漏,那就應該檢查虛擬機的參數(-Xmx與-Xms)的設置是否適當。

2,虛擬機棧和本地方法棧溢出

如果線程請求的棧深度大于虛擬機所允許的最大深度,將拋出StackOverflowError異常。

如果虛擬機在擴展棧時無法申請到足夠的內存空間,則拋出OutOfMemoryError異常

這里需要注意當棧的大小越大可分配的線程數就越少。

3,運行時常量池溢出

異常信息:java.lang.OutOfMemoryError:PermGenspace

如果要向運行時常量池中添加內容,最簡單的做法就是使用String.intern()這個Native方法。該方法的作用是:如果池中已經包含一個等于此String的字符串,則返回代表池中這個字符串的String對象;否則,將此String對象包含的字符串添加到常量池中,并且返回此String對象的引用。由于常量池分配在方法區內,我們可以通過-XX:PermSize和-XX:MaxPermSize限制方法區的大小,從而間接限制其中常量池的容量。

4,方法區溢出

方法區用于存放Class的相關信息,如類名、訪問修飾符、常量池、字段描述、方法描述等。也有可能是方法區中保存的class對象沒有被及時回收掉或者class信息占用的內存超過了我們配置。

異常信息:java.lang.OutOfMemoryError:PermGenspace

方法區溢出也是一種常見的內存溢出異常,一個類如果要被垃圾收集器回收,判定條件是很苛刻的。在經常動態生成大量Class的應用中,要特別注意這點。

SOF(堆棧溢出StackOverflow):

StackOverflowError 的定義:當應用程序遞歸太深而發生堆棧溢出時,拋出該錯誤。

因為棧一般默認為1-2m,一旦出現死循環或者是大量的遞歸調用,在不斷的壓棧過程中,造成棧容量超過1m而導致溢出。

棧溢出的原因:遞歸調用,大量循環或死循環,全局變量是否過多,數組、List、map數據過大。

26、 簡述線程、程序、進程的基本概念。以及他們之間關系是什么?

線程與進程相似,但線程是一個比進程更小的執行單位。一個進程在其執行的過程中可以產生多個線程。與進程不同的是同類的多個線程共享同一塊內存空間和一組系統資源,所以系統在產生一個線程,或是在各個線程之間作切換工作時,負擔要比進程小得多,也正因為如此,線程也被稱為輕量級進程。

程序是含有指令和數據的文件,被存儲在磁盤或其他的數據存儲設備中,也就是說程序是靜態的代碼。

進程是程序的一次執行過程,是系統運行程序的基本單位,因此進程是動態的。系統運行一個程序即是一個進程從創建,運行到消亡的過程。簡單來說,一個進程就是一個執行中的程序,它在計算機中一個指令接著一個指令地執行著,同時,每個進程還占有某些系統資源如 CPU 時間,內存空間,文件,輸入輸出設備的使用權等等。換句話說,當程序在執行時,將會被操作系統載入內存中。 線程是進程劃分成的更小的運行單位。線程和進程最大的不同在于基本上各進程是獨立的,而各線程則不一定,因為同一進程中的線程極有可能會相互影響。從另一角度來說,進程屬于操作系統的范疇,主要是同一段時間內,可以同時執行一個以上的程序,而線程則是在同一程序內幾乎同時執行一個以上的程序段。

27、線程有哪些基本狀態?

Java 線程在運行的生命周期中的指定時刻只可能處于下面6種不同狀態的其中一個狀態(圖源《Java 并發編程藝術》4.1.4節)。
在這里插入圖片描述
線程在生命周期中并不是固定處于某一個狀態而是隨著代碼的執行在不同狀態之間切換。Java 線程狀態變遷如下圖所示(圖源《Java 并發編程藝術》4.1.4節):

在這里插入圖片描述

操作系統隱藏 Java虛擬機(JVM)中的 RUNNABLE 和 RUNNING 狀態,它只能看到 RUNNABLE 狀態(圖源:HowToDoInJava:Java Thread Life Cycle and Thread States),所以 Java 系統一般將這兩個狀態統稱為 RUNNABLE(運行中) 狀態 。

操作系統隱藏 Java虛擬機(JVM)中的 RUNNABLE 和 RUNNING 狀態,它只能看到 RUNNABLE 狀態(圖源:HowToDoInJava:Java Thread Life Cycle and Thread States),所以 Java 系統一般將這兩個狀態統稱為 RUNNABLE(運行中) 狀態 。

在這里插入圖片描述

當線程執行 wait()方法之后,線程進入 **WAITING(等待)**狀態。進入等待狀態的線程需要依靠其他線程的通知才能夠返回到運行狀態,而 TIME_WAITING(超時等待) 狀態相當于在等待狀態的基礎上增加了超時限制,比如通過 sleep(long millis)方法或 wait(long millis)方法可以將 Java 線程置于 TIMED WAITING 狀態。當超時時間到達后 Java 線程將會返回到 RUNNABLE 狀態。當線程調用同步方法時,在沒有獲取到鎖的情況下,線程將會進入到 BLOCKED(阻塞) 狀態。線程在執行 Runnable 的run()方法之后將會進入到 TERMINATED(終止) 狀態。

28、Java 序列化中如果有些字段不想進行序列化,怎么辦?

對于不想進行序列化的變量,使用 transient 關鍵字修飾。

transient 關鍵字的作用是:阻止實例中那些用此關鍵字修飾的的變量序列化;當對象被反序列化時,被 transient 修飾的變量值不會被持久化和恢復。transient 只能修飾變量,不能修飾類和方法。

29、Java 中 IO 流

Java 中 IO 流分為幾種?

  • 按照流的流向分,可以分為輸入流和輸出流;
  • 按照操作單元劃分,可以劃分為字節流和字符流;
  • 按照流的角色劃分為節點流和處理流。

Java Io 流共涉及 40 多個類,這些類看上去很雜亂,但實際上很有規則,而且彼此之間存在非常緊密的聯系, Java I0 流的 40 多個類都是從如下 4 個抽象類基類中派生出來的。

  • InputStream/Reader: 所有的輸入流的基類,前者是字節輸入流,后者是字符輸入流。
  • OutputStream/Writer: 所有輸出流的基類,前者是字節輸出流,后者是字符輸出流。

按操作方式分類結構圖:

IO-操作方式分類

按操作對象分類結構圖:

IO-操作對象分類

30、 Java IO與 NIO的區別

推薦閱讀:https://mp.weixin.qq.com/s/N1ojvByYmary65B6JM1ZWA

31、java反射的作用于原理

1、定義:

反射機制是在運行時,對于任意一個類,都能夠知道這個類的所有屬性和方法;對于任意個對象,都能夠調用它的任意一個方法。在java中,只要給定類的名字,就可以通過反射機制來獲得類的所有信息。

這種動態獲取的信息以及動態調用對象的方法的功能稱為Java語言的反射機制。

2、哪里會用到反射機制?

jdbc就是典型的反射

Class.forName('com.mysql.jdbc.Driver.class');//加載MySQL的驅動類

這就是反射。如hibernate,struts等框架使用反射實現的。

3、反射的實現方式:

第一步:獲取Class對象,有4中方法:
1)Class.forName(“類的路徑”);
2)類名.class
3)對象名.getClass()
4)基本類型的包裝類,可以調用包裝類的Type屬性來獲得該包裝類的Class對象

4、實現Java反射的類:

1)Class:表示正在運行的Java應用程序中的類和接口
注意: 所有獲取對象的信息都需要Class類來實現。
2)Field:提供有關類和接口的屬性信息,以及對它的動態訪問權限。
3)Constructor:提供關于類的單個構造方法的信息以及它的訪問權限
4)Method:提供類或接口中某個方法的信息

5、反射機制的優缺點:

優點:
1)能夠運行時動態獲取類的實例,提高靈活性;
2)與動態編譯結合
缺點:
1)使用反射性能較低,需要解析字節碼,將內存中的對象進行解析。
解決方案:
1、通過setAccessible(true)關閉JDK的安全檢查來提升反射速度;
2、多次創建一個類的實例時,有緩存會快很多
3、ReflectASM工具類,通過字節碼生成的方式加快反射速度
2)相對不安全,破壞了封裝性(因為通過反射可以獲得私有方法和屬性)

32、說說List,Set,Map三者的區別?

  • List(對付順序的好幫手): List接口存儲一組不唯一(可以有多個元素引用相同的對象),有序的對象
  • Set(注重獨一無二的性質): 不允許重復的集合。不會有多個元素引用相同的對象。
  • Map(用Key來搜索的專家): 使用鍵值對存儲。Map會維護與Key有關聯的值。兩個Key可以引用相同的對象,但Key不能重復,典型的Key是String類型,但也可以是任何對象。

JVM篇

1、知識點匯總

JVM是Java運行基礎,面試時一定會遇到JVM的有關問題,內容相對集中,但對只是深度要求較高.

在這里插入圖片描述

其中內存模型,類加載機制,GC是重點方面.性能調優部分更偏向應用,重點突出實踐能力.編譯器優化和執行模式部分偏向于理論基礎,重點掌握知識點.

需了解
內存模型各部分作用,保存哪些數據.

類加載雙親委派加載機制,常用加載器分別加載哪種類型的類.

GC分代回收的思想和依據以及不同垃圾回收算法的回收思路和適合場景.

性能調優常有JVM優化參數作用,參數調優的依據,常用的JVM分析工具能分析哪些問題以及使用方法.

執行模式解釋/編譯/混合模式的優缺點,Java7提供的分層編譯技術,JIT即時編譯技術,OSR棧上替換,C1/C2編譯器針對的場景,C2針對的是server模式,優化更激進.新技術方面Java10的graal編譯器

編譯器優化javac的編譯過程,ast抽象語法樹,編譯器優化和運行器優化.

2、知識點詳解:

1、JVM內存模型:

線程獨占:棧,本地方法棧,程序計數器
線程共享:堆,方法區

2、棧:

又稱方法棧,線程私有的,線程執行方法是都會創建一個棧陣,用來存儲局部變量表,操作棧,動態鏈接,方法出口等信息.調用方法時執行入棧,方法返回式執行出棧.

3、本地方法棧

與棧類似,也是用來保存執行方法的信息.執行Java方法是使用棧,執行Native方法時使用本地方法棧.

4、程序計數器

保存著當前線程執行的字節碼位置,每個線程工作時都有獨立的計數器,只為執行Java方法服務,執行Native方法時,程序計數器為空.

5、堆

JVM內存管理最大的一塊,對被線程共享,目的是存放對象的實例,幾乎所欲的對象實例都會放在這里,當堆沒有可用空間時,會拋出OOM異常.根據對象的存活周期不同,JVM把對象進行分代管理,由垃圾回收器進行垃圾的回收管理

6、方法區:

又稱非堆區,用于存儲已被虛擬機加載的類信息,常量,靜態變量,即時編譯器優化后的代碼等數據.1.7的永久代和1.8的元空間都是方法區的一種實現

7、JVM 內存可見性

在這里插入圖片描述

JMM是定義程序中變量的訪問規則,線程對于變量的操作只能在自己的工作內存中進行,而不能直接對主內存操作.由于指令重排序,讀寫的順序會被打亂,因此JMM需要提供原子性,可見性,有序性保證.

在這里插入圖片描述

3、類加載與卸載

加載過程

img

其中驗證,準備,解析合稱鏈接

加載通過類的完全限定名,查找此類字節碼文件,利用字節碼文件創建Class對象.

驗證確保Class文件符合當前虛擬機的要求,不會危害到虛擬機自身安全.

準備進行內存分配,為static修飾的類變量分配內存,并設置初始值(0或null).不包含final修飾的靜態變量,因為final變量在編譯時分配.

解析將常量池中的符號引用替換為直接引用的過程.直接引用為直接指向目標的指針或者相對偏移量等.

初始化主要完成靜態塊執行以及靜態變量的賦值.先初始化父類,再初始化當前類.只有對類主動使用時才會初始化.

觸發條件包括,創建類的實例時,訪問類的靜態方法或靜態變量的時候,使用Class.forName反射類的時候,或者某個子類初始化的時候.

Java自帶的加載器加載的類,在虛擬機的生命周期中是不會被卸載的,只有用戶自定義的加載器加載的類才可以被卸.

1、加載機制-雙親委派模式

在這里插入圖片描述

雙親委派模式,即加載器加載類時先把請求委托給自己的父類加載器執行,直到頂層的啟動類加載器.父類加載器能夠完成加載則成功返回,不能則子類加載器才自己嘗試加載.*

優點:

  1. 避免類的重復加載
  2. 避免Java的核心API被篡改

2、分代回收

分代回收基于兩個事實:大部分對象很快就不使用了,還有一部分不會立即無用,但也不會持續很長時間.
在這里插入圖片描述

年輕代->標記-復制
老年代->標記-清除

3、回收算法

a、G1算法

1.9后默認的垃圾回收算法,特點保持高回收率的同時減少停頓.采用每次只清理一部分,而不是清理全部的增量式清理,以保證停頓時間不會過長

其取消了年輕代與老年代的物理劃分,但仍屬于分代收集器,算法將堆分為若干個邏輯區域(region),一部分用作年輕代,一部分用作老年代,還有用來存儲巨型對象的分區.

同CMS相同,會遍歷所有對象,標記引用情況,清除對象后會對區域進行復制移動,以整合碎片空間.

年輕代回收:
并行復制采用復制算法,并行收集,會StopTheWorld.

老年代回收:
會對年輕代一并回收

初始標記完成堆root對象的標記,會StopTheWorld.
并發標記 GC線程和應用線程并發執行.
最終標記完成三色標記周期,會StopTheWorld.
復制/清楚會優先對可回收空間加大的區域進行回收

b、ZGC算法

前面提供的高效垃圾回收算法,針對大堆內存設計,可以處理TB級別的堆,可以做到10ms以下的回收停頓時間.

在這里插入圖片描述

  • 著色指針
  • 讀屏障
  • 并發處理
  • 基于region
  • 內存壓縮(整理)

roots標記:標記root對象,會StopTheWorld.
并發標記:利用讀屏障與應用線程一起運行標記,可能會發生StopTheWorld.
清除會清理標記為不可用的對象.
roots重定位:是對存活的對象進行移動,以騰出大塊內存空間,減少碎片產生.重定位最開始會StopTheWorld,卻決于重定位集與對象總活動集的比例.
并發重定位與并發標記類似.

4、簡述一下JVM的內存模型

1.JVM內存模型簡介

JVM定義了不同運行時數據區,他們是用來執行應用程序的。某些區域隨著JVM啟動及銷毀,另外一些區域的數據是線程性獨立的,隨著線程創建和銷毀。jvm內存模型總體架構圖如下:(摘自oracle官方網站)

img

JVM在執行Java程序時,會把它管理的內存劃分為若干個的區域,每個區域都有自己的用途和創建銷毀時間。如下圖所示,可以分為兩大部分,線程私有區和共享區。下圖是根據自己理解畫的一個JVM內存模型架構圖:

img JVM內存分為線程私有區和線程共享區

線程私有區

1、程序計數器

當同時進行的線程數超過CPU數或其內核數時,就要通過時間片輪詢分派CPU的時間資源,不免發生線程切換。這時,每個線程就需要一個屬于自己的計數器來記錄下一條要運行的指令。如果執行的是JAVA方法,計數器記錄正在執行的java字節碼地址,如果執行的是native方法,則計數器為空。

2、虛擬機棧

線程私有的,與線程在同一時間創建。管理JAVA方法執行的內存模型。每個方法執行時都會創建一個楨棧來存儲方法的的變量表、操作數棧、動態鏈接方法、返回值、返回地址等信息。棧的大小決定了方法調用的可達深度(遞歸多少層次,或嵌套調用多少層其他方法,-Xss參數可以設置虛擬機棧大小)。棧的大小可以是固定的,或者是動態擴展的。如果請求的棧深度大于最大可用深度,則拋出stackOverflowError;如果棧是可動態擴展的,但沒有內存空間支持擴展,則拋出OutofMemoryError。
使用jclasslib工具可以查看class類文件的結構。下圖為棧幀結構圖:

img

3、本地方法棧

與虛擬機棧作用相似。但它不是為Java方法服務的,而是本地方法(C語言)。由于規范對這塊沒有強制要求,不同虛擬機實現方法不同。

線程共享區

1、方法區

線程共享的,用于存放被虛擬機加載的類的元數據信息,如常量、靜態變量和即時編譯器編譯后的代碼。若要分代,算是永久代(老年代),以前類大多“static”的,很少被卸載或收集,現回收廢棄常量和無用的類。其中運行時常量池存放編譯生成的各種常量。(如果hotspot虛擬機確定一個類的定義信息不會被使用,也會將其回收。回收的基本條件至少有:所有該類的實例被回收,而且裝載該類的ClassLoader被回收)

2、堆

存放對象實例和數組,是垃圾回收的主要區域,分為新生代和老年代。剛創建的對象在新生代的Eden區中,經過GC后進入新生代的S0區中,再經過GC進入新生代的S1區中,15次GC后仍存在就進入老年代。這是按照一種回收機制進行劃分的,不是固定的。若堆的空間不夠實例分配,則OutOfMemoryError。

img

Young Generation      即圖中的Eden + From Space(s0) + To Space(s1)
Eden                        存放新生的對象
Survivor Space          有兩個,存放每次垃圾回收后存活的對象(s0+s1)
Old Generation          Tenured Generation 即圖中的Old Space主要存放應用程序中生命周期長的存活對象

5、堆和棧的區別

棧是運行時單位,代表著邏輯,內含基本數據類型和堆中對象引用,所在區域連續,沒有碎片;堆是存儲單位,代表著數據,可被多個棧共享(包括成員中基本數據類型、引用和引用對象),所在區域不連續,會有碎片。

1、功能不同

棧內存用來存儲局部變量和方法調用,而堆內存用來存儲Java中的對象。無論是成員變量,局部變量,還是類變量,它們指向的對象都存儲在堆內存中。

2、共享性不同

棧內存是線程私有的。
堆內存是所有線程共有的。

3、異常錯誤不同

如果棧內存或者堆內存不足都會拋出異常。
棧空間不足:java.lang.StackOverFlowError。
堆空間不足:java.lang.OutOfMemoryError。

4、空間大小

棧的空間大小遠遠小于堆的。

6、 什么時候會觸發FullGC

除直接調用System.gc外,觸發Full GC執行的情況有如下四種。
1. 舊生代空間不足
舊生代空間只有在新生代對象轉入及創建為大對象、大數組時才會出現不足的現象,當執行Full GC后空間仍然不足,則拋出如下錯誤:
java.lang.OutOfMemoryError: Java heap space
為避免以上兩種狀況引起的FullGC,調優時應盡量做到讓對象在Minor GC階段被回收、讓對象在新生代多存活一段時間及不要創建過大的對象及數組。

2. Permanet Generation空間滿
PermanetGeneration中存放的為一些class的信息等,當系統中要加載的類、反射的類和調用的方法較多時,Permanet Generation可能會被占滿,在未配置為采用CMS GC的情況下會執行Full GC。如果經過Full GC仍然回收不了,那么JVM會拋出如下錯誤信息:
java.lang.OutOfMemoryError: PermGen space
為避免Perm Gen占滿造成Full GC現象,可采用的方法為增大Perm Gen空間或轉為使用CMS GC。

3. CMS GC時出現promotion failed和concurrent mode failure
對于采用CMS進行舊生代GC的程序而言,尤其要注意GC日志中是否有promotion failed和concurrent mode failure兩種狀況,當這兩種狀況出現時可能會觸發Full GC。
promotionfailed是在進行Minor GC時,survivor space放不下、對象只能放入舊生代,而此時舊生代也放不下造成的;concurrent mode failure是在執行CMS GC的過程中同時有對象要放入舊生代,而此時舊生代空間不足造成的。
應對措施為:增大survivorspace、舊生代空間或調低觸發并發GC的比率,但在JDK 5.0+、6.0+的版本中有可能會由于JDK的bug29導致CMS在remark完畢后很久才觸發sweeping動作。對于這種狀況,可通過設置-XX:CMSMaxAbortablePrecleanTime=5(單位為ms)來避免。

4. 統計得到的Minor GC晉升到舊生代的平均大小大于舊生代的剩余空間
這是一個較為復雜的觸發情況,Hotspot為了避免由于新生代對象晉升到舊生代導致舊生代空間不足的現象,在進行Minor GC時,做了一個判斷,如果之前統計所得到的Minor GC晉升到舊生代的平均大小大于舊生代的剩余空間,那么就直接觸發Full GC。
例如程序第一次觸發MinorGC后,有6MB的對象晉升到舊生代,那么當下一次Minor GC發生時,首先檢查舊生代的剩余空間是否大于6MB,如果小于6MB,則執行Full GC。
當新生代采用PSGC時,方式稍有不同,PS GC是在Minor GC后也會檢查,例如上面的例子中第一次Minor GC后,PS GC會檢查此時舊生代的剩余空間是否大于6MB,如小于,則觸發對舊生代的回收。
除了以上4種狀況外,對于使用RMI來進行RPC或管理的Sun JDK應用而言,默認情況下會一小時執行一次Full GC。可通過在啟動時通過- java-Dsun.rmi.dgc.client.gcInterval=3600000來設置Full GC執行的間隔時間或通過-XX:+ DisableExplicitGC來禁止RMI調用System.gc。

7、什么是Java虛擬機?為什么Java被稱作是“平臺無關的編程語言”?

Java虛擬機是一個可以執行Java字節碼的虛擬機進程。Java源文件被編譯成能被Java虛擬機執行的字節碼文件。 Java被設計成允許應用程序可以運行在任意的平臺,而不需要程序員為每一個平臺單獨重寫或者是重新編譯。Java虛擬機讓這個變為可能,因為它知道底層硬件平臺的指令長度和其他特性。

8、Java內存結構

《24個Jvm面試題總結及答案》

方法區和對是所有線程共享的內存區域;而java棧、本地方法棧和程序員計數器是運行是線程私有的內存區域。

  • Java堆(Heap),是Java虛擬機所管理的內存中最大的一塊。Java堆是被所有線程共享的一塊內存區域,在虛擬機啟動時創建。此內存區域的唯一目的就是存放對象實例,幾乎所有的對象實例都在這里分配內存。
  • 方法區(Method Area),方法區(Method Area)與Java堆一樣,是各個線程共享的內存區域,它用于存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯后的代碼等數據。
  • 程序計數器(Program Counter Register),程序計數器(Program Counter Register)是一塊較小的內存空間,它的作用可以看做是當前線程所執行的字節碼的行號指示器。
  • JVM棧(JVM Stacks),與程序計數器一樣,Java虛擬機棧(Java Virtual Machine Stacks)也是線程私有的,它的生命周期與線程相同。虛擬機棧描述的是Java方法執行的內存模型:每個方法被執行的時候都會同時創建一個棧幀(Stack Frame)用于存儲局部變量表、操作棧、動態鏈接、方法出口等信息。每一個方法被調用直至執行完成的過程,就對應著一個棧幀在虛擬機棧中從入棧到出棧的過程。
  • 本地方法棧(Native Method Stacks),本地方法棧(Native Method Stacks)與虛擬機棧所發揮的作用是非常相似的,其區別不過是虛擬機棧為虛擬機執行Java方法(也就是字節碼)服務,而本地方法棧則是為虛擬機使用到的Native方法服務。

9、對象分配規則

  • 對象優先分配在Eden區,如果Eden區沒有足夠的空間時,虛擬機執行一次Minor GC。
  • 大對象直接進入老年代(大對象是指需要大量連續內存空間的對象)。這樣做的目的是避免在Eden區和兩個Survivor區之間發生大量的內存拷貝(新生代采用復制算法收集內存)。
  • 長期存活的對象進入老年代。虛擬機為每個對象定義了一個年齡計數器,如果對象經過了1次Minor GC那么對象會進入Survivor區,之后每經過一次Minor GC那么對象的年齡加1,知道達到閥值對象進入老年區。
  • 動態判斷對象的年齡。如果Survivor區中相同年齡的所有對象大小的總和大于Survivor空間的一半,年齡大于或等于該年齡的對象可以直接進入老年代。
  • 空間分配擔保。每次進行Minor GC時,JVM會計算Survivor區移至老年區的對象的平均大小,如果這個值大于老年區的剩余值大小則進行一次Full GC,如果小于檢查HandlePromotionFailure設置,如果true則只進行Monitor GC,如果false則進行Full GC。

10、描述一下JVM加載class文件的原理機制?

JVM中類的裝載是由類加載器(ClassLoader)和它的子類來實現的,Java中的類加載器是一個重要的Java運行時系統組件,它負責在運行時查找和裝入類文件中的類。 由于Java的跨平臺性,經過編譯的Java源程序并不是一個可執行程序,而是一個或多個類文件。當Java程序需要使用某個類時,JVM會確保這個類已經被加載、連接(驗證、準備和解析)和初始化。類的加載是指把類的.class文件中的數據讀入到內存中,通常是創建一個字節數組讀入.class文件,然后產生與所加載類對應的Class對象。加載完成后,Class對象還不完整,所以此時的類還不可用。當類被加載后就進入連接階段,這一階段包括驗證、準備(為靜態變量分配內存并設置默認的初始值)和解析(將符號引用替換為直接引用)三個步驟。最后JVM對類進行初始化,包括:1)如果類存在直接的父類并且這個類還沒有被初始化,那么就先初始化父類;2)如果類中存在初始化語句,就依次執行這些初始化語句。 類的加載是由類加載器完成的,類加載器包括:根加載器(BootStrap)、擴展加載器(Extension)、系統加載器(System)和用戶自定義類加載器(java.lang.ClassLoader的子類)。從Java 2(JDK 1.2)開始,類加載過程采取了父親委托機制(PDM)。PDM更好的保證了Java平臺的安全性,在該機制中,JVM自帶的Bootstrap是根加載器,其他的加載器都有且僅有一個父類加載器。類的加載首先請求父類加載器加載,父類加載器無能為力時才由其子類加載器自行加載。JVM不會向Java程序提供對Bootstrap的引用。下面是關于幾個類加載器的說明:

  • Bootstrap:一般用本地代碼實現,負責加載JVM基礎核心類庫(rt.jar);
  • Extension:從java.ext.dirs系統屬性所指定的目錄中加載類庫,它的父加載器是Bootstrap;
  • System:又叫應用類加載器,其父類是Extension。它是應用最廣泛的類加載器。它從環境變量classpath或者系統屬性java.class.path所指定的目錄中記載類,是用戶自定義加載器的默認父加載器。

11、Java對象創建過程

1.JVM遇到一條新建對象的指令時首先去檢查這個指令的參數是否能在常量池中定義到一個類的符號引用。然后加載這個類(類加載過程在后邊講)

2.為對象分配內存。一種辦法“指針碰撞”、一種辦法“空閑列表”,最終常用的辦法“本地線程緩沖分配(TLAB)”

3.將除對象頭外的對象內存空間初始化為0

4.對對象頭進行必要設置

12、類的生命周期

類的生命周期包括這幾個部分,加載、連接、初始化、使用和卸載,其中前三部是類的加載的過程,如下圖;

《24個Jvm面試題總結及答案》

  • 加載,查找并加載類的二進制數據,在Java堆中也創建一個java.lang.Class類的對象
  • 連接,連接又包含三塊內容:驗證、準備、初始化。 1)驗證,文件格式、元數據、字節碼、符號引用驗證; 2)準備,為類的靜態變量分配內存,并將其初始化為默認值; 3)解析,把類中的符號引用轉換為直接引用
  • 初始化,為類的靜態變量賦予正確的初始值
  • 使用,new出對象程序中使用
  • 卸載,執行垃圾回收

13、簡述Java的對象結構

Java對象由三個部分組成:對象頭、實例數據、對齊填充。

對象頭由兩部分組成,第一部分存儲對象自身的運行時數據:哈希碼、GC分代年齡、鎖標識狀態、線程持有的鎖、偏向線程ID(一般占32/64 bit)。第二部分是指針類型,指向對象的類元數據類型(即對象代表哪個類)。如果是數組對象,則對象頭中還有一部分用來記錄數組長度。

實例數據用來存儲對象真正的有效信息(包括父類繼承下來的和自己定義的)

對齊填充:JVM要求對象起始地址必須是8字節的整數倍(8字節對齊)

14、如何判斷對象可以被回收?

判斷對象是否存活一般有兩種方式:

  • 引用計數:每個對象有一個引用計數屬性,新增一個引用時計數加1,引用釋放時計數減1,計數為0時可以回收。此方法簡單,無法解決對象相互循環引用的問題。
  • 可達性分析(Reachability Analysis):從GC Roots開始向下搜索,搜索所走過的路徑稱為引用鏈。當一個對象到GC Roots沒有任何引用鏈相連時,則證明此對象是不可用的,不可達對象。

15、JVM的永久代中會發生垃圾回收么?

垃圾回收不會發生在永久代,如果永久代滿了或者是超過了臨界值,會觸發完全垃圾回收(Full GC)。如果你仔細查看垃圾收集器的輸出信息,就會發現永久代也是被回收的。這就是為什么正確的永久代大小對避免Full GC是非常重要的原因。請參考下Java8:從永久代到元數據區 (注:Java8中已經移除了永久代,新加了一個叫做元數據區的native內存區)

16、垃圾收集算法

GC最基礎的算法有三種: 標記 -清除算法、復制算法、標記-壓縮算法,我們常用的垃圾回收器一般都采用分代收集算法。

  • 標記 -清除算法,“標記-清除”(Mark-Sweep)算法,如它的名字一樣,算法分為“標記”和“清除”兩個階段:首先標記出所有需要回收的對象,在標記完成后統一回收掉所有被標記的對象。
  • 復制算法,“復制”(Copying)的收集算法,它將可用內存按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當這一塊的內存用完了,就將還存活著的對象復制到另外一塊上面,然后再把已使用過的內存空間一次清理掉。
  • 標記-壓縮算法,標記過程仍然與“標記-清除”算法一樣,但后續步驟不是直接對可回收對象進行清理,而是讓所有存活的對象都向一端移動,然后直接清理掉端邊界以外的內存
  • 分代收集算法,“分代收集”(Generational Collection)算法,把Java堆分為新生代和老年代,這樣就可以根據各個年代的特點采用最適當的收集算法。

17、調優命令有哪些?

Sun JDK監控和故障處理命令有jps jstat jmap jhat jstack jinfo

  • jps,JVM Process Status Tool,顯示指定系統內所有的HotSpot虛擬機進程。
  • jstat,JVM statistics Monitoring是用于監視虛擬機運行時狀態信息的命令,它可以顯示出虛擬機進程中的類裝載、內存、垃圾收集、JIT編譯等運行數據。
  • jmap,JVM Memory Map命令用于生成heap dump文件
  • jhat,JVM Heap Analysis Tool命令是與jmap搭配使用,用來分析jmap生成的dump,jhat內置了一個微型的HTTP/HTML服務器,生成dump的分析結果后,可以在瀏覽器中查看
  • jstack,用于生成java虛擬機當前時刻的線程快照。
  • jinfo,JVM Configuration info 這個命令作用是實時查看和調整虛擬機運行參數。

18、調優工具

常用調優工具分為兩類,jdk自帶監控工具:jconsole和jvisualvm,第三方有:MAT(Memory Analyzer Tool)、GChisto。

  • jconsole,Java Monitoring and Management Console是從java5開始,在JDK中自帶的java監控和管理控制臺,用于對JVM中內存,線程和類等的監控
  • jvisualvm,jdk自帶全能工具,可以分析內存快照、線程快照;監控內存變化、GC變化等。
  • MAT,Memory Analyzer Tool,一個基于Eclipse的內存分析工具,是一個快速、功能豐富的Java heap分析工具,它可以幫助我們查找內存泄漏和減少內存消耗
  • GChisto,一款專業分析gc日志的工具

19、Minor GC與Full GC分別在什么時候發生?

新生代內存不夠用時候發生MGC也叫YGC,JVM內存不夠的時候發生FGC

20、你知道哪些JVM性能調優

  • 設定堆內存大小

-Xmx:堆內存最大限制。

  • 設定新生代大小。 新生代不宜太小,否則會有大量對象涌入老年代

-XX:NewSize:新生代大小

-XX:NewRatio 新生代和老生代占比

-XX:SurvivorRatio:伊甸園空間和幸存者空間的占比

  • 設定垃圾回收器 年輕代用 -XX:+UseParNewGC 年老代用-XX:+UseConcMarkSweepGC

多線程&并發篇

1、Java中實現多線程有幾種方法

繼承Thread類;
實現Runnable接口;
實現Callable接口通過FutureTask包裝器來創建Thread線程;
使用ExecutorService、Callable、Future實現有返回結果的多線程(也就是使用了ExecutorService來管理前面的三種方式)。

2、如何停止一個正在運行的線程

1、使用退出標志,使線程正常退出,也就是當run方法完成后線程終止。

2、使用stop方法強行終止,但是不推薦這個方法,因為stop和suspend及resume一樣都是過期作廢的方法。

3、使用interrupt方法中斷線程。

class MyThread extends Thread {volatile boolean stop = false;public void run() {while (!stop) {System.out.println(getName() + " is running");try {sleep(1000);} catch (InterruptedException e) {System.out.println("week up from blcok...");stop = true; // 在異常處理代碼中修改共享變量的狀態}}System.out.println(getName() + " is exiting...");}
}class InterruptThreadDemo3 {public static void main(String[] args) throws InterruptedException {MyThread m1 = new MyThread();System.out.println("Starting thread...");m1.start();Thread.sleep(3000);System.out.println("Interrupt thread...: " + m1.getName());m1.stop = true; // 設置共享變量為truem1.interrupt(); // 阻塞時退出阻塞狀態Thread.sleep(3000); // 主線程休眠3秒以便觀察線程m1的中斷情況System.out.println("Stopping application...");}
}

3、notify()和notifyAll()有什么區別?

notify可能會導致死鎖,而notifyAll則不會

任何時候只有一個線程可以獲得鎖,也就是說只有一個線程可以運行synchronized 中的代碼

使用notifyall,可以喚醒
所有處于wait狀態的線程,使其重新進入鎖的爭奪隊列中,而notify只能喚醒一個。

wait() 應配合while循環使用,不應使用if,務必在wait()調用前后都檢查條件,如果不滿足,必須調用notify()喚醒另外的線程來處理,自己繼續wait()直至條件滿足再往下執行。

notify() 是對notifyAll()的一個優化,但它有很精確的應用場景,并且要求正確使用。不然可能導致死鎖。正確的場景應該是 WaitSet中等待的是相同的條件,喚醒任一個都能正確處理接下來的事項,如果喚醒的線程無法正確處理,務必確保繼續notify()下一個線程,并且自身需要重新回到WaitSet中.

4、sleep()和wait() 有什么區別?

對于sleep()方法,我們首先要知道該方法是屬于Thread類中的。而wait()方法,則是屬于Object類中的。

sleep()方法導致了程序暫停執行指定的時間,讓出cpu該其他線程,但是他的監控狀態依然保持者,當指定的時間到了又會自動恢復運行狀態。在調用sleep()方法的過程中,線程不會釋放對象鎖。

當調用wait()方法的時候,線程會放棄對象鎖,進入等待此對象的等待鎖定池,只有針對此對象調用notify()方法后本線程才進入對象鎖定池準備,獲取對象鎖進入運行狀態。

5、volatile 是什么?可以保證有序性嗎?

一旦一個共享變量(類的成員變量、類的靜態成員變量)被volatile修飾之后,那么就具備了兩層語義:

1)保證了不同線程對這個變量進行操作時的可見性,即一個線程修改了某個變量的值,這新值對其他線程來說是立即可見的,volatile關鍵字會強制將修改的值立即寫入主存。

2)禁止進行指令重排序。

volatile 不是原子性操作

什么叫保證部分有序性?

當程序執行到volatile變量的讀操作或者寫操作時,在其前面的操作的更改肯定全部已經進行,且結果已經對后面的操作可見;在其后面的操作肯定還沒有進行;

x = 2;        //語句1
y = 0;        //語句2
flag = true;  //語句3
x = 4;         //語句4
y = -1;       //語句5

由于flag變量為volatile變量,那么在進行指令重排序的過程的時候,不會將語句3放到語句1、語句2前面,也不會講語句3放到語句4、語句5后面。但是要注意語句1和語句2的順序、語句4和語句5的順序是不作任何保證的。

使用 Volatile 一般用于 狀態標記量 和 單例模式的雙檢鎖

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

start()方法被用來啟動新創建的線程,而且start()內部調用了run()方法,這和直接調用run()方法的效果不一樣。當你調用run()方法的時候,只會是在原來的線程中調用,沒有新的線程啟動,start()方法才會啟動新線程。

7、為什么wait, notify 和 notifyAll這些方法不在thread類里面?

明顯的原因是JAVA提供的鎖是對象級的而不是線程級的,每個對象都有鎖,通過線程獲得。如果線程需要等待某些鎖那么調用對象中的wait()方法就有意義了。如果wait()方法定義在Thread類中,線程正在等待的是哪個鎖就不明顯了。簡單的說,由于wait,notify和notifyAll都是鎖級別的操作,所以把他們定義在Object類中因為鎖屬于對象。

8、為什么wait和notify方法要在同步塊中調用?

  1. 只有在調用線程擁有某個對象的獨占鎖時,才能夠調用該對象的wait(),notify()和notifyAll()方法。
  2. 如果你不這么做,你的代碼會拋出IllegalMonitorStateException異常。
  3. 還有一個原因是為了避免wait和notify之間產生競態條件。

wait()方法強制當前線程釋放對象鎖。這意味著在調用某對象的wait()方法之前,當前線程必須已經獲得該對象的鎖。因此,線程必須在某個對象的同步方法或同步代碼塊中才能調用該對象的wait()方法。

在調用對象的notify()和notifyAll()方法之前,調用線程必須已經得到該對象的鎖。因此,必須在某個對象的同步方法或同步代碼塊中才能調用該對象的notify()或notifyAll()方法。

調用wait()方法的原因通常是,調用線程希望某個特殊的狀態(或變量)被設置之后再繼續執行。調用notify()或notifyAll()方法的原因通常是,調用線程希望告訴其他等待中的線程:“特殊狀態已經被設置”。這個狀態作為線程間通信的通道,它必須是一個可變的共享狀態(或變量)。

9、Java中interrupted 和 isInterruptedd方法的區別?

interrupted() 和 isInterrupted()的主要區別是前者會將中斷狀態清除而后者不會。Java多線程的中斷機制是用內部標識來實現的,調用Thread.interrupt()來中斷一個線程就會設置中斷標識為true。當中斷線程調用靜態方法Thread.interrupted()來檢查中斷狀態時,中斷狀態會被清零。而非靜態方法isInterrupted()用來查詢其它線程的中斷狀態且不會改變中斷狀態標識。簡單的說就是任何拋出InterruptedException異常的方法都會將中斷狀態清零。無論如何,一個線程的中斷狀態有有可能被其它線程調用中斷來改變。

10、Java中synchronized 和 ReentrantLock 有什么不同?

相似點:

這兩種同步方式有很多相似之處,它們都是加鎖方式同步,而且都是阻塞式的同步,也就是說當如果一個線程獲得了對象鎖,進入了同步塊,其他訪問該同步塊的線程都必須阻塞在同步塊外面等待,而進行線程阻塞和喚醒的代價是比較高的.

區別:

這兩種方式最大區別就是對于Synchronized來說,它是java語言的關鍵字,是原生語法層面的互斥,需要jvm實現。而ReentrantLock它是JDK 1.5之后提供的API層面的互斥鎖,需要lock()和unlock()方法配合try/finally語句塊來完成。

Synchronized進過編譯,會在同步塊的前后分別形成monitorenter和monitorexit這個兩個字節碼指令。在執行monitorenter指令時,首先要嘗試獲取對象鎖。如果這個對象沒被鎖定,或者當前線程已經擁有了那個對象鎖,把鎖的計算器加1,相應的,在執行monitorexit指令時會將鎖計算器就減1,當計算器為0時,鎖就被釋放了。如果獲取對象鎖失敗,那當前線程就要阻塞,直到對象鎖被另一個線程釋放為止。

由于ReentrantLock是java.util.concurrent包下提供的一套互斥鎖,相比Synchronized,ReentrantLock類提供了一些高級功能,主要有以下3項:

1.等待可中斷,持有鎖的線程長期不釋放的時候,正在等待的線程可以選擇放棄等待,這相當于Synchronized來說可以避免出現死鎖的情況。

2.公平鎖,多個線程等待同一個鎖時,必須按照申請鎖的時間順序獲得鎖,Synchronized鎖非公平鎖,ReentrantLock默認的構造函數是創建的非公平鎖,可以通過參數true設為公平鎖,但公平鎖表現的性能不是很好。

3.鎖綁定多個條件,一個ReentrantLock對象可以同時綁定對個對象。

11、有三個線程T1,T2,T3,如何保證順序執行?

在多線程中有多種方法讓線程按特定順序執行,你可以用線程類的join()方法在一個線程中啟動另一個線程,另外一個線程完成該線程繼續執行。為了確保三個線程的順序你應該先啟動最后一個(T3調用T2,T2調用T1),這樣T1就會先完成而T3最后完成。

實際上先啟動三個線程中哪一個都行,
因為在每個線程的run方法中用join方法限定了三個線程的執行順序。

public class JoinTest2 {// 1.現在有T1、T2、T3三個線程,你怎樣保證T2在T1執行完后執行,T3在T2執行完后執行public static void main(String[] args) {final Thread t1 = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("t1");}});final Thread t2 = new Thread(new Runnable() {@Overridepublic void run() {try {// 引用t1線程,等待t1線程執行完t1.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("t2");}});Thread t3 = new Thread(new Runnable() {@Overridepublic void run() {try {// 引用t2線程,等待t2線程執行完t2.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("t3");}});t3.start();//這里三個線程的啟動順序可以任意,大家可以試下!t2.start();t1.start();}
}

12、SynchronizedMap和ConcurrentHashMap有什么區別?

SynchronizedMap()和Hashtable一樣,實現上在調用map所有方法時,都對整個map進行同步。而ConcurrentHashMap的實現卻更加精細,它對map中的所有桶加了鎖。所以,只要有一個線程訪問map,其他線程就無法進入map,而如果一個線程在訪問ConcurrentHashMap某個桶時,其他線程,仍然可以對map執行某些操作。

所以,ConcurrentHashMap在性能以及安全性方面,明顯比Collections.synchronizedMap()更加有優勢。同時,同步操作精確控制到桶,這樣,即使在遍歷map時,如果其他線程試圖對map進行數據修改,也不會拋出ConcurrentModificationException。

13、什么是線程安全

線程安全就是說多線程訪問同一代碼,不會產生不確定的結果。

在多線程環境中,當各線程不共享數據的時候,即都是私有(private)成員,那么一定是線程安全的。但這種情況并不多見,在多數情況下需要共享數據,這時就需要進行適當的同步控制了。

線程安全一般都涉及到synchronized, 就是一段代碼同時只能有一個線程來操作 不然中間過程可能會產生不可預制的結果。

如果你的代碼所在的進程中有多個線程在同時運行,而這些線程可能會同時運行這段代碼。如果每次運行結果和單線程運行的結果是一樣的,而且其他的變量的值也和預期的是一樣的,就是線程安全的。

14、Thread類中的yield方法有什么作用?

Yield方法可以暫停當前正在執行的線程對象,讓其它有相同優先級的線程執行。它是一個靜態方法而且只保證當前線程放棄CPU占用而不能保證使其它線程一定能占用CPU,執行yield()的線程有可能在進入到暫停狀態后馬上又被執行。

15、Java線程池中submit() 和 execute()方法有什么區別?

兩個方法都可以向線程池提交任務,execute()方法的返回類型是void,它定義在Executor接口中, 而submit()方法可以返回持有計算結果的Future對象,它定義在ExecutorService接口中,它擴展了Executor接口,其它線程池類像ThreadPoolExecutor和ScheduledThreadPoolExecutor都有這些方法。

16、說一說自己對于 synchronized 關鍵字的了解

synchronized關鍵字解決的是多個線程之間訪問資源的同步性,synchronized關鍵字可以保證被它修飾的方法或者代碼塊在任意時刻只能有一個線程執行。
另外,在 Java 早期版本中,synchronized屬于重量級鎖,效率低下,因為監視器鎖(monitor)是依賴于底層的操作系統的 Mutex Lock 來實現的,Java 的線程是映射到操作系統的原生線程之上的。如果要掛起或者喚醒一個線程,都需要操作系統幫忙完成,而操作系統實現線程之間的切換時需要從用戶態轉換到內核態,這個狀態之間的轉換需要相對比較長的時間,時間成本相對較高,這也是為什么早期的 synchronized 效率低的原因。慶幸的是在 Java 6 之后 Java 官方對從 JVM 層面對synchronized 較大優化,所以現在的 synchronized 鎖效率也優化得很不錯了。JDK1.6對鎖的實現引入了大量的優化,如自旋鎖、適應性自旋鎖、鎖消除、鎖粗化、偏向鎖、輕量級鎖等技術來減少鎖操作的開銷。

17、說說自己是怎么使用 synchronized 關鍵字,在項目中用到了嗎synchronized關鍵字最主要的三種使用方式:

修飾實例方法: 作用于當前對象實例加鎖,進入同步代碼前要獲得當前對象實例的鎖
修飾靜態方法: 也就是給當前類加鎖,會作用于類的所有對象實例,因為靜態成員不屬于任何一個實例對象,是類成員( static 表明這是該類的一個靜態資源,不管new了多少個對象,只有一份)。所以如果一個線程A調用一個實例對象的非靜態 synchronized 方法,而線程B需要調用這個實例對象所屬類的靜態 synchronized 方法,是允許的,不會發生互斥現象,因為訪問靜態 synchronized 方法占用的鎖是當前類的鎖,而訪問非靜態 synchronized 方法占用的鎖是當前實例對象鎖。
修飾代碼塊: 指定加鎖對象,對給定對象加鎖,進入同步代碼庫前要獲得給定對象的鎖。
總結: synchronized 關鍵字加到 static 靜態方法和 synchronized(class)代碼塊上都是是給 Class 類上鎖。synchronized 關鍵字加到實例方法上是給對象實例上鎖。盡量不要使用 synchronized(String a) 因為JVM中,字符串常量池具有緩存功能!

18、什么是線程安全?Vector是一個線程安全類嗎?

如果你的代碼所在的進程中有多個線程在同時運行,而這些線程可能會同時運行這段代碼。如果每次運行結果和單線程運行的結果是一樣的,而且其他的變量 的值也和預期的是一樣的,就是線程安全的。一個線程安全的計數器類的同一個實例對象在被多個線程使用的情況下也不會出現計算失誤。很顯然你可以將集合類分 成兩組,線程安全和非線程安全的。Vector 是用同步方法來實現線程安全的, 而和它相似的ArrayList不是線程安全的。

19、 volatile關鍵字的作用?

一旦一個共享變量(類的成員變量、類的靜態成員變量)被volatile修飾之后,那么就具備了兩層語義:

  • 保證了不同線程對這個變量進行操作時的可見性,即一個線程修改了某個變量的值,這新值對其他線程來說是立即可見的。

  • 禁止進行指令重排序。

  • volatile本質是在告訴jvm當前變量在寄存器(工作內存)中的值是不確定的,需要從主存中讀取;synchronized則是鎖定當前變量,只有當前線程可以訪問該變量,其他線程被阻塞住。

  • volatile僅能使用在變量級別;synchronized則可以使用在變量、方法、和類級別的。

  • volatile僅能實現變量的修改可見性,并不能保證原子性;synchronized則可以保證變量的修改可見性和原子性。

  • volatile不會造成線程的阻塞;synchronized可能會造成線程的阻塞。

volatile標記的變量不會被編譯器優化;synchronized標記的變量可以被編譯器優化。

20、常用的線程池有哪些?

  • newSingleThreadExecutor:創建一個單線程的線程池,此線程池保證所有任務的執行順序按照任務的提交順序執行。
  • newFixedThreadPool:創建固定大小的線程池,每次提交一個任務就創建一個線程,直到線程達到線程池的最大大小。
  • newCachedThreadPool:創建一個可緩存的線程池,此線程池不會對線程池大小做限制,線程池大小完全依賴于操作系統(或者說JVM)能夠創建的最大線程大小。
  • newScheduledThreadPool:創建一個大小無限的線程池,此線程池支持定時以及周期性執行任務的需求。
  • newSingleThreadExecutor:創建一個單線程的線程池。此線程池支持定時以及周期性執行任務的需求。

21、簡述一下你對線程池的理解

(如果問到了這樣的問題,可以展開的說一下線程池如何用、線程池的好處、線程池的啟動策略)合理利用線程池能夠帶來三個好處。

第一:降低資源消耗。通過重復利用已創建的線程降低線程創建和銷毀造成的消耗。

第二:提高響應速度。當任務到達時,任務可以不需要等到線程創建就能立即執行。

第三:提高線程的可管理性。線程是稀缺資源,如果無限制的創建,不僅會消耗系統資源,還會降低系統的穩定性,使用線程池可以進行統一的分配,調優和監控。

22、Java程序是如何執行的

我們日常的工作中都使用開發工具(IntelliJ IDEA 或 Eclipse 等)可以很方便的調試程序,或者是通過打包工具把項目打包成 jar 包或者 war 包,放入 Tomcat 等 Web 容器中就可以正常運行了,但你有沒有想過 Java 程序內部是如何執行的?其實不論是在開發工具中運行還是在 Tomcat 中運行,Java 程序的執行流程基本都是相同的,它的執行流程如下:

  • 先把 Java 代碼編譯成字節碼,也就是把 .java 類型的文件編譯成 .class 類型的文件。這個過程的大致執行流程:Java 源代碼 -> 詞法分析器 -> 語法分析器 -> 語義分析器 -> 字符碼生成器 -> 最終生成字節碼,其中任何一個節點執行失敗就會造成編譯失敗;
  • 把 class 文件放置到 Java 虛擬機,這個虛擬機通常指的是 Oracle 官方自帶的 Hotspot JVM;
  • Java 虛擬機使用類加載器(Class Loader)裝載 class 文件;
  • 類加載完成之后,會進行字節碼效驗,字節碼效驗通過之后 JVM 解釋器會把字節碼翻譯成機器碼交由操作系統執行。但不是所有代碼都是解釋執行的,JVM 對此做了優化,比如,以 Hotspot 虛擬機來說,它本身提供了 JIT(Just In Time)也就是我們通常所說的動態編譯器,它能夠在運行時將熱點代碼編譯為機器碼,這個時候字節碼就變成了編譯執行。Java 程序執行流程圖如下:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-gSxSVD2E-1587954092029)(https://pics7.baidu.com/feed/9922720e0cf3d7caf8b227aecb7be60c6a63a90f.png?token=5bc0a1783459586fb4e21b13579950c9&s=B8A05D32150F65491865D0420300F0F1)]

Spring篇

推薦閱讀: 極客學院Spring Wiki

1、 Spring的IOC和AOP機制?

我們是在使用Spring框架的過程中,其實就是為了使用IOC,依賴注入,和AOP,面向切面編程,這兩個是Spring的靈魂。

主要用到的設計模式有工廠模式和代理模式。

IOC就是典型的工廠模式,通過sessionfactory去注入實例。

AOP就是典型的代理模式的體現。

代理模式是常用的java設計模式,他的特征是代理類與委托類有同樣的接口,代理類主要負責為委托類預處理消息、過濾消息、把消息轉發給委托類,以及事后處理消息等。代理類與委托類之間通常會存在關聯關系,一個代理類的對象與一個委托類的對象關聯,代理類的對象本身并不真正實現服務,而是通過調用委托類的對象的相關方法,來提供特定的服務。

spring的IoC容器是spring的核心,spring AOP是spring框架的重要組成部分。

在傳統的程序設計中,當調用者需要被調用者的協助時,通常由調用者來創建被調用者的實例。但在spring里創建被調用者的工作不再由調用者來完成,因此控制反轉(IoC);創建被調用者實例的工作通常由spring容器來完成,然后注入調用者,因此也被稱為依賴注入(DI),依賴注入和控制反轉是同一個概念。

面向方面編程(AOP)是以另一個角度來考慮程序結構,通過分析程序結構的關注點來完善面向對象編程(OOP)。OOP將應用程序分解成各個層次的對象,而AOP將程序分解成多個切面。spring AOP 只實現了方法級別的連接點,在J2EE應用中,AOP攔截到方法級別的操作就已經足夠。在spring中,未來使IoC方便地使用健壯、靈活的企業服務,需要利用spring AOP實現為IoC和企業服務之間建立聯系。

IOC:控制反轉也叫依賴注入。利用了工廠模式
將對象交給容器管理,你只需要在spring配置文件總配置相應的bean,以及設置相關的屬性,讓spring容器來生成類的實例對象以及管理對象。在spring容器啟動的時候,spring會把你在配置文件中配置的bean都初始化好,然后在你需要調用的時候,就把它已經初始化好的那些bean分配給你需要調用這些bean的類(假設這個類名是A),分配的方法就是調用A的setter方法來注入,而不需要你在A里面new這些bean了。
注意:面試的時候,如果有條件,畫圖,這樣更加顯得你懂了.

spring ioc初始化流程

Spring IOC的初始化過程

AOP:面向切面編程。(Aspect-Oriented Programming)
AOP可以說是對OOP的補充和完善。OOP引入封裝、繼承和多態性等概念來建立一種對象層次結構,用以模擬公共行為的一個集合。當我們需要為分散的對象引入公共行為的時候,OOP則顯得無能為力。也就是說,OOP允許你定義從上到下的關系,但并不適合定義從左到右的關系。例如日志功能。日志代碼往往水平地散布在所有對象層次中,而與它所散布到的對象的核心功能毫無關系。在OOP設計中,它導致了大量代碼的重復,而不利于各個模塊的重用。
將程序中的交叉業務邏輯(比如安全,日志,事務等),封裝成一個切面,然后注入到目標對象(具體業務邏輯)中去。

實現AOP的技術,主要分為兩大類:一是采用動態代理技術,利用截取消息的方式,對該消息進行裝飾,以取代原有對象行為的執行;二是采用靜態織入的方式,引入特定的語法創建“方面”,從而使得編譯器可以在編譯期間織入有關“方面”的代碼.

簡單點解釋,比方說你想在你的biz層所有類中都加上一個打印‘你好’的功能,這時就可以用aop思想來做.你先寫個類寫個類方法,方法經實現打印‘你好’,然后Ioc這個類 ref=“biz.*”讓每個類都注入即可實現。

2、 Spring中Autowired和Resource關鍵字的區別?

@Resource和@Autowired都是做bean的注入時使用,其實@Resource并不是Spring的注解,它的包是javax.annotation.Resource,需要導入,但是Spring支持該注解的注入。

1、共同點

兩者都可以寫在字段和setter方法上。兩者如果都寫在字段上,那么就不需要再寫setter方法。

2、不同點

(1)@Autowired

@Autowired為Spring提供的注解,需要導入包org.springframework.beans.factory.annotation.Autowired;只按照byType注入。

public class TestServiceImpl {// 下面兩種@Autowired只要使用一種即可@Autowiredprivate UserDao userDao; // 用于字段上@Autowiredpublic void setUserDao(UserDao userDao) { // 用于屬性的方法上this.userDao = userDao;}
}

@Autowired注解是按照類型(byType)裝配依賴對象,默認情況下它要求依賴對象必須存在,如果允許null值,可以設置它的required屬性為false。如果我們想使用按照名稱(byName)來裝配,可以結合@Qualifier注解一起使用。如下:

public class TestServiceImpl {@Autowired@Qualifier("userDao")private UserDao userDao; 
}

(2)@Resource

@Resource默認按照ByName自動注入,由J2EE提供,需要導入包javax.annotation.Resource。@Resource有兩個重要的屬性:name和type,而Spring將@Resource注解的name屬性解析為bean的名字,而type屬性則解析為bean的類型。所以,如果使用name屬性,則使用byName的自動注入策略,而使用type屬性時則使用byType自動注入策略。如果既不制定name也不制定type屬性,這時將通過反射機制使用byName自動注入策略。

public class TestServiceImpl {// 下面兩種@Resource只要使用一種即可@Resource(name="userDao")private UserDao userDao; // 用于字段上@Resource(name="userDao")public void setUserDao(UserDao userDao) { // 用于屬性的setter方法上this.userDao = userDao;}
}

注:最好是將@Resource放在setter方法上,因為這樣更符合面向對象的思想,通過set、get去操作屬性,而不是直接去操作屬性。

@Resource裝配順序:

①如果同時指定了name和type,則從Spring上下文中找到唯一匹配的bean進行裝配,找不到則拋出異常。

②如果指定了name,則從上下文中查找名稱(id)匹配的bean進行裝配,找不到則拋出異常。

③如果指定了type,則從上下文中找到類似匹配的唯一bean進行裝配,找不到或是找到多個,都會拋出異常。

④如果既沒有指定name,又沒有指定type,則自動按照byName方式進行裝配;如果沒有匹配,則回退為一個原始類型進行匹配,如果匹配則自動裝配。

@Resource的作用相當于@Autowired,只不過@Autowired按照byType自動注入。

3、依賴注入的方式有幾種,各是什么?

一、構造器注入
將被依賴對象通過構造函數的參數注入給依賴對象,并且在初始化對象的時候注入。

優點:
對象初始化完成后便可獲得可使用的對象。

缺點:
當需要注入的對象很多時,構造器參數列表將會很長;
不夠靈活。若有多種注入方式,每種方式只需注入指定幾個依賴,那么就需要提供多個重載的構造函數,麻煩。

二、setter方法注入
IoC Service Provider通過調用成員變量提供的setter函數將被依賴對象注入給依賴類。

優點:
靈活。可以選擇性地注入需要的對象。

缺點:
依賴對象初始化完成后由于尚未注入被依賴對象,因此還不能使用。

三、接口注入
依賴類必須要實現指定的接口,然后實現該接口中的一個函數,該函數就是用于依賴注入。該函數的參數就是要注入的對象。

優點
接口注入中,接口的名字、函數的名字都不重要,只要保證函數的參數是要注入的對象類型即可。

缺點:
侵入行太強,不建議使用。

PS:什么是侵入行?
如果類A要使用別人提供的一個功能,若為了使用這功能,需要在自己的類中增加額外的代碼,這就是侵入性。

4、講一下什么是Spring

Spring是一個輕量級的IoC和AOP容器框架。是為Java應用程序提供基礎性服務的一套框架,目的是用于簡化企業應用程序的開發,它使得開發者只需要關心業務需求。常見的配置方式有三種:基于XML的配置、基于注解的配置、基于Java的配置。

主要由以下幾個模塊組成:

Spring Core:核心類庫,提供IOC服務;

Spring Context:提供框架式的Bean訪問方式,以及企業級功能(JNDI、定時任務等);

Spring AOP:AOP服務;

Spring DAO:對JDBC的抽象,簡化了數據訪問異常的處理;

Spring ORM:對現有的ORM框架的支持;

Spring Web:提供了基本的面向Web的綜合特性,例如多方文件上傳;

Spring MVC:提供面向Web應用的Model-View-Controller實現。

5、Spring MVC流程

工作原理:

img

1、 用戶發送請求至前端控制器DispatcherServlet。

2、 DispatcherServlet收到請求調用HandlerMapping處理器映射器。

3、 處理器映射器找到具體的處理器(可以根據xml配置、注解進行查找),生成處理器對象及處理器攔截器(如果有則生成)一并返回給DispatcherServlet。

4、 DispatcherServlet調用HandlerAdapter處理器適配器。

5、 HandlerAdapter經過適配調用具體的處理器(Controller,也叫后端控制器)。

6、 Controller執行完成返回ModelAndView。

7、 HandlerAdapter將controller執行結果ModelAndView返回給DispatcherServlet。

8、 DispatcherServlet將ModelAndView傳給ViewReslover視圖解析器。

9、 ViewReslover解析后返回具體View。

10、DispatcherServlet根據View進行渲染視圖(即將模型數據填充至視圖中)。

11、 DispatcherServlet響應用戶。

組件說明:

以下組件通常使用框架提供實現:

DispatcherServlet:作為前端控制器,整個流程控制的中心,控制其它組件執行,統一調度,降低組件之間的耦合性,提高每個組件的擴展性。

HandlerMapping:通過擴展處理器映射器實現不同的映射方式,例如:配置文件方式,實現接口方式,注解方式等。

HandlAdapter:通過擴展處理器適配器,支持更多類型的處理器。

ViewResolver:通過擴展視圖解析器,支持更多類型的視圖解析,例如:jsp、freemarker、pdf、excel等。

組件:
1、前端控制器DispatcherServlet(不需要工程師開發),由框架提供
作用:接收請求,響應結果,相當于轉發器,中央處理器。有了dispatcherServlet減少了其它組件之間的耦合度。
用戶請求到達前端控制器,它就相當于mvc模式中的c,dispatcherServlet是整個流程控制的中心,由它調用其它組件處理用戶的請求,dispatcherServlet的存在降低了組件之間的耦合性。

2、處理器映射器HandlerMapping(不需要工程師開發),由框架提供
作用:根據請求的url查找Handler
HandlerMapping負責根據用戶請求找到Handler即處理器,springmvc提供了不同的映射器實現不同的映射方式,例如:配置文件方式,實現接口方式,注解方式等。

3、處理器適配器HandlerAdapter
作用:按照特定規則(HandlerAdapter要求的規則)去執行Handler
通過HandlerAdapter對處理器進行執行,這是適配器模式的應用,通過擴展適配器可以對更多類型的處理器進行執行。

4、處理器Handler(需要工程師開發)
注意:編寫Handler時按照HandlerAdapter的要求去做,這樣適配器才可以去正確執行Handler
Handler 是繼DispatcherServlet前端控制器的后端控制器,在DispatcherServlet的控制下Handler對具體的用戶請求進行處理。
由于Handler涉及到具體的用戶業務請求,所以一般情況需要工程師根據業務需求開發Handler。

5、視圖解析器View resolver(不需要工程師開發),由框架提供
作用:進行視圖解析,根據邏輯視圖名解析成真正的視圖(view)
View Resolver負責將處理結果生成View視圖,View Resolver首先根據邏輯視圖名解析成物理視圖名即具體的頁面地址,再生成View視圖對象,最后對View進行渲染將處理結果通過頁面展示給用戶。 springmvc框架提供了很多的View視圖類型,包括:jstlView、freemarkerView、pdfView等。
一般情況下需要通過頁面標簽或頁面模版技術將模型數據通過頁面展示給用戶,需要由工程師根據業務需求開發具體的頁面。

6、視圖View(需要工程師開發jsp…)
View是一個接口,實現類支持不同的View類型(jsp、freemarker、pdf…)

核心架構的具體流程步驟如下:
1、首先用戶發送請求——>DispatcherServlet,前端控制器收到請求后自己不進行處理,而是委托給其他的解析器進行處理,作為統一訪問點,進行全局的流程控制;
2、DispatcherServlet——>HandlerMapping, HandlerMapping 將會把請求映射為HandlerExecutionChain 對象(包含一個Handler 處理器(頁面控制器)對象、多個HandlerInterceptor 攔截器)對象,通過這種策略模式,很容易添加新的映射策略;
3、DispatcherServlet——>HandlerAdapter,HandlerAdapter 將會把處理器包裝為適配器,從而支持多種類型的處理器,即適配器設計模式的應用,從而很容易支持很多類型的處理器;
4、HandlerAdapter——>處理器功能處理方法的調用,HandlerAdapter 將會根據適配的結果調用真正的處理器的功能處理方法,完成功能處理;并返回一個ModelAndView 對象(包含模型數據、邏輯視圖名);
5、ModelAndView的邏輯視圖名——> ViewResolver, ViewResolver 將把邏輯視圖名解析為具體的View,通過這種策略模式,很容易更換其他視圖技術;
6、View——>渲染,View會根據傳進來的Model模型數據進行渲染,此處的Model實際是一個Map數據結構,因此很容易支持其他視圖技術;
7、返回控制權給DispatcherServlet,由DispatcherServlet返回響應給用戶,到此一個流程結束。

下邊兩個組件通常情況下需要開發:

Handler:處理器,即后端控制器用controller表示。

View:視圖,即展示給用戶的界面,視圖中通常需要標簽語言展示模型數據。

在講SpringMVC之前我們先來看一下什么是MVC模式

MVC:MVC是一種設計模式

MVC的原理圖:

img

分析:

M-Model 模型(完成業務邏輯:有javaBean構成,service+dao+entity)

V-View 視圖(做界面的展示 jsp,html……)

C-Controller 控制器(接收請求—>調用模型—>根據結果派發頁面)

springMVC是什么:

springMVC是一個MVC的開源框架,springMVC=struts2+spring,springMVC就相當于是Struts2加上sring的整合,但是這里有一個疑惑就是,springMVC和spring是什么樣的關系呢?這個在百度百科上有一個很好的解釋:意思是說,springMVC是spring的一個后續產品,其實就是spring在原有基礎上,又提供了web應用的MVC模塊,可以簡單的把springMVC理解為是spring的一個模塊(類似AOP,IOC這樣的模塊),網絡上經常會說springMVC和spring無縫集成,其實springMVC就是spring的一個子模塊,所以根本不需要同spring進行整合。

SpringMVC的原理圖:

img

看到這個圖大家可能會有很多的疑惑,現在我們來看一下這個圖的步驟:(可以對比MVC的原理圖進行理解)

第一步:用戶發起請求到前端控制器(DispatcherServlet)

第二步:前端控制器請求處理器映射器(HandlerMappering)去查找處理器(Handle):通過xml配置或者注解進行查找

第三步:找到以后處理器映射器(HandlerMappering)像前端控制器返回執行鏈(HandlerExecutionChain)

第四步:前端控制器(DispatcherServlet)調用處理器適配器(HandlerAdapter)去執行處理器(Handler)

第五步:處理器適配器去執行Handler

第六步:Handler執行完給處理器適配器返回ModelAndView

第七步:處理器適配器向前端控制器返回ModelAndView

第八步:前端控制器請求視圖解析器(ViewResolver)去進行視圖解析

第九步:視圖解析器像前端控制器返回View

第十步:前端控制器對視圖進行渲染

第十一步:前端控制器向用戶響應結果

看到這些步驟我相信大家很感覺非常的亂,這是正常的,但是這里主要是要大家理解springMVC中的幾個組件:

前端控制器(DispatcherServlet):接收請求,響應結果,相當于電腦的CPU。

處理器映射器(HandlerMapping):根據URL去查找處理器

處理器(Handler):(需要程序員去寫代碼處理邏輯的)

處理器適配器(HandlerAdapter):會把處理器包裝成適配器,這樣就可以支持多種類型的處理器,類比筆記本的適配器(適配器模式的應用)

視圖解析器(ViewResovler):進行視圖解析,多返回的字符串,進行處理,可以解析成對應的頁面

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

(1)轉發:在返回值前面加"forward:",譬如"forward:user.do?name=method4"

(2)重定向:在返回值前面加"redirect:",譬如"redirect:http://www.baidu.com"

7、 SpringMVC常用的注解有哪些?

@RequestMapping:用于處理請求 url 映射的注解,可用于類或方法上。用于類上,則表示類中的所有響應請求的方法都是以該地址作為父路徑。

@RequestBody:注解實現接收http請求的json數據,將json轉換為java對象。

@ResponseBody:注解實現將conreoller方法返回對象轉化為json對象響應給客戶。

8、 Spring的AOP理解:

OOP面向對象,允許開發者定義縱向的關系,但并適用于定義橫向的關系,導致了大量代碼的重復,而不利于各個模塊的重用。

AOP,一般稱為面向切面,作為面向對象的一種補充,用于將那些與業務無關,但卻對多個對象產生影響的公共行為和邏輯,抽取并封裝為一個可重用的模塊,這個模塊被命名為“切面”(Aspect),減少系統中的重復代碼,降低了模塊間的耦合度,同時提高了系統的可維護性。可用于權限認證、日志、事務處理。

AOP實現的關鍵在于 代理模式,AOP代理主要分為靜態代理和動態代理。靜態代理的代表為AspectJ;動態代理則以Spring AOP為代表。

(1)AspectJ是靜態代理的增強,所謂靜態代理,就是AOP框架會在編譯階段生成AOP代理類,因此也稱為編譯時增強,他會在編譯階段將AspectJ(切面)織入到Java字節碼中,運行的時候就是增強之后的AOP對象。

(2)Spring AOP使用的動態代理,所謂的動態代理就是說AOP框架不會去修改字節碼,而是每次運行時在內存中臨時為方法生成一個AOP對象,這個AOP對象包含了目標對象的全部方法,并且在特定的切點做了增強處理,并回調原對象的方法。

Spring AOP中的動態代理主要有兩種方式,JDK動態代理和CGLIB動態代理:

    ①JDK動態代理只提供接口的代理,不支持類的代理。核心InvocationHandler接口和Proxy類,InvocationHandler 通過invoke()方法反射來調用目標類中的代碼,動態地將橫切邏輯和業務編織在一起;接著,Proxy利用 InvocationHandler動態創建一個符合某一接口的的實例,  生成目標類的代理對象。②如果代理類沒有實現 InvocationHandler 接口,那么Spring AOP會選擇使用CGLIB來動態代理目標類。CGLIB(Code Generation Library),是一個代碼生成的類庫,可以在運行時動態的生成指定類的一個子類對象,并覆蓋其中特定方法并添加增強代碼,從而實現AOP。CGLIB是通過繼承的方式做的動態代理,因此如果某個類被標記為final,那么它是無法使用CGLIB做動態代理的。

(3)靜態代理與動態代理區別在于生成AOP代理對象的時機不同,相對來說AspectJ的靜態代理方式具有更好的性能,但是AspectJ需要特定的編譯器進行處理,而Spring AOP則無需特定的編譯器處理。

9、Spring的IOC理解

(1)IOC就是控制反轉,是指創建對象的控制權的轉移,以前創建對象的主動權和時機是由自己把控的,而現在這種權力轉移到Spring容器中,并由容器根據配置文件去創建實例和管理各個實例之間的依賴關系,對象與對象之間松散耦合,也利于功能的復用。DI依賴注入,和控制反轉是同一個概念的不同角度的描述,即 應用程序在運行時依賴IoC容器來動態注入對象需要的外部資源。

(2)最直觀的表達就是,IOC讓對象的創建不用去new了,可以由spring自動生產,使用java的反射機制,根據配置文件在運行時動態的去創建對象以及管理對象,并調用對象的方法的。

(3)Spring的IOC有三種注入方式 :構造器注入、setter方法注入、根據注解注入。

IoC讓相互協作的組件保持松散的耦合,而AOP編程允許你把遍布于應用各層的功能分離出來形成可重用的功能組件。

10、解釋一下spring bean的生命周期

首先說一下Servlet的生命周期:實例化,初始init,接收請求service,銷毀destroy;

Spring上下文中的Bean生命周期也類似,如下:

(1)實例化Bean:

對于BeanFactory容器,當客戶向容器請求一個尚未初始化的bean時,或初始化bean的時候需要注入另一個尚未初始化的依賴時,容器就會調用createBean進行實例化。對于ApplicationContext容器,當容器啟動結束后,通過獲取BeanDefinition對象中的信息,實例化所有的bean。

(2)設置對象屬性(依賴注入):

實例化后的對象被封裝在BeanWrapper對象中,緊接著,Spring根據BeanDefinition中的信息 以及 通過BeanWrapper提供的設置屬性的接口完成依賴注入。

(3)處理Aware接口:

接著,Spring會檢測該對象是否實現了xxxAware接口,并將相關的xxxAware實例注入給Bean:

①如果這個Bean已經實現了BeanNameAware接口,會調用它實現的setBeanName(String beanId)方法,此處傳遞的就是Spring配置文件中Bean的id值;

②如果這個Bean已經實現了BeanFactoryAware接口,會調用它實現的setBeanFactory()方法,傳遞的是Spring工廠自身。

③如果這個Bean已經實現了ApplicationContextAware接口,會調用setApplicationContext(ApplicationContext)方法,傳入Spring上下文;

(4)BeanPostProcessor:

如果想對Bean進行一些自定義的處理,那么可以讓Bean實現了BeanPostProcessor接口,那將會調用postProcessBeforeInitialization(Object obj, String s)方法。

(5)InitializingBean 與 init-method:

如果Bean在Spring配置文件中配置了 init-method 屬性,則會自動調用其配置的初始化方法。

(6)如果這個Bean實現了BeanPostProcessor接口,將會調用postProcessAfterInitialization(Object obj, String s)方法;由于這個方法是在Bean初始化結束時調用的,所以可以被應用于內存或緩存技術;

以上幾個步驟完成后,Bean就已經被正確創建了,之后就可以使用這個Bean了。

(7)DisposableBean:

當Bean不再需要時,會經過清理階段,如果Bean實現了DisposableBean這個接口,會調用其實現的destroy()方法;

(8)destroy-method:

最后,如果這個Bean的Spring配置中配置了destroy-method屬性,會自動調用其配置的銷毀方法。

11、 解釋Spring支持的幾種bean的作用域。

Spring容器中的bean可以分為5個范圍:

(1)singleton:默認,每個容器中只有一個bean的實例,單例的模式由BeanFactory自身來維護。

(2)prototype:為每一個bean請求提供一個實例。

(3)request:為每一個網絡請求創建一個實例,在請求完成以后,bean會失效并被垃圾回收器回收。

(4)session:與request范圍類似,確保每個session中有一個bean的實例,在session過期后,bean會隨之失效。

(5)global-session:全局作用域,global-session和Portlet應用相關。當你的應用部署在Portlet容器中工作時,它包含很多portlet。如果你想要聲明讓所有的portlet共用全局的存儲變量的話,那么這全局變量需要存儲在global-session中。全局作用域與Servlet中的session作用域效果相同。

12、 Spring基于xml注入bean的幾種方式:

(1)Set方法注入;

(2)構造器注入:①通過index設置參數的位置;②通過type設置參數類型;

(3)靜態工廠注入;

(4)實例工廠;

詳細內容可以閱讀:https://blog.csdn.net/a745233700/article/details/89307518

13、Spring框架中都用到了哪些設計模式?

(1)工廠模式:BeanFactory就是簡單工廠模式的體現,用來創建對象的實例;

(2)單例模式:Bean默認為單例模式。

(3)代理模式:Spring的AOP功能用到了JDK的動態代理和CGLIB字節碼生成技術;

(4)模板方法:用來解決代碼重復的問題。比如. RestTemplate, JmsTemplate, JpaTemplate。

(5)觀察者模式:定義對象鍵一種一對多的依賴關系,當一個對象的狀態發生改變時,所有依賴于它的對象都會得到通知被制動更新,如Spring中listener的實現–ApplicationListener。

MyBatis篇

1、什么是MyBatis

(1)Mybatis是一個半ORM(對象關系映射)框架,它內部封裝了JDBC,開發時只需要關注SQL語句本身,不需要花費精力去處理加載驅動、創建連接、創建statement等繁雜的過程。程序員直接編寫原生態sql,可以嚴格控制sql執行性能,靈活度高。

(2)MyBatis 可以使用 XML 或注解來配置和映射原生信息,將 POJO映射成數據庫中的記錄,避免了幾乎所有的 JDBC 代碼和手動設置參數以及獲取結果集。

(3)通過xml 文件或注解的方式將要執行的各種 statement 配置起來,并通過java對象和 statement中sql的動態參數進行映射生成最終執行的sql語句,最后由mybatis框架執行sql并將結果映射為java對象并返回。(從執行sql到返回result的過程)。

2、MyBatis的優點和缺點

優點:

(1)基于SQL語句編程,相當靈活,不會對應用程序或者數據庫的現有設計造成任何影響,SQL寫在XML里,解除sql與程序代碼的耦合,便于統一管理;提供XML標簽,支持編寫動態SQL語句,并可重用。

(2)與JDBC相比,減少了50%以上的代碼量,消除了JDBC大量冗余的代碼,不需要手動開關連接;

(3)很好的與各種數據庫兼容(因為MyBatis使用JDBC來連接數據庫,所以只要JDBC支持的數據庫MyBatis都支持)。

(4)能夠與Spring很好的集成;

(5)提供映射標簽,支持對象與數據庫的ORM字段關系映射;提供對象關系映射標簽,支持對象關系組件維護。

缺點

(1)SQL語句的編寫工作量較大,尤其當字段多、關聯表多時,對開發人員編寫SQL語句的功底有一定要求。

(2)SQL語句依賴于數據庫,導致數據庫移植性差,不能隨意更換數據庫。

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

#{}是預編譯處理,${}是字符串替換。

Mybatis在處理#{}時,會將sql中的#{}替換為?號,調用PreparedStatement的set方法來賦值;

Mybatis在處理時,就是把{}時,就是把{}替換成變量的值。

使用#{}可以有效的防止SQL注入,提高系統安全性。

4、當實體類中的屬性名和表中的字段名不一樣 ,怎么辦 ?

第1種: 通過在查詢的sql語句中定義字段名的別名,讓字段名的別名和實體類的屬性名一致。

    <select id=”selectorder” parametertype=”int” resultetype=”me.gacl.domain.order”>select order_id id, order_no orderno ,order_price price form orders where order_id=#{id};</select>

第2種: 通過來映射字段名和實體類屬性名的一一對應的關系。

 <select id="getOrder" parameterType="int" resultMap="orderresultmap">select * from orders where order_id=#{id}</select><resultMap type=”me.gacl.domain.order” id=”orderresultmap”><!–用id屬性來映射主鍵字段–><id property=”id” column=”order_id”><!–用result屬性來映射非主鍵字段,property為實體類屬性名,column為數據表中的屬性–><result property = “orderno” column =”order_no”/><result property=”price” column=”order_price” /></reslutMap>

5、Mybatis是如何進行分頁的?分頁插件的原理是什么?

Mybatis使用RowBounds對象進行分頁,它是針對ResultSet結果集執行的內存分頁,而非物理分頁。可以在sql內直接書寫帶有物理分頁的參數來完成物理分頁功能,也可以使用分頁插件來完成物理分頁。

分頁插件的基本原理是使用Mybatis提供的插件接口,實現自定義插件,在插件的攔截方法內攔截待執行的sql,然后重寫sql,根據dialect方言,添加對應的物理分頁語句和物理分頁參數。

6、Mybatis是如何將sql執行結果封裝為目標對象并返回的?都有哪些映射形式?

第一種是使用標簽,逐一定義數據庫列名和對象屬性名之間的映射關系。

第二種是使用sql列的別名功能,將列的別名書寫為對象屬性名。

有了列名與屬性名的映射關系后,Mybatis通過反射創建對象,同時使用反射給對象的屬性逐一賦值并返回,那些找不到映射關系的屬性,是無法完成賦值的。

7、 如何執行批量插入?

首先,創建一個簡單的insert語句:

    <insert id=”insertname”>insert into names (name) values (#{value})</insert>

然后在java代碼中像下面這樣執行批處理插入:

  list<string> names = new arraylist();names.add(“fred”);names.add(“barney”);names.add(“betty”);names.add(“wilma”);// 注意這里 executortype.batchsqlsession sqlsession = sqlsessionfactory.opensession(executortype.batch);try {namemapper mapper = sqlsession.getmapper(namemapper.class);for (string name : names) {mapper.insertname(name);}sqlsession.commit();}catch(Exception e){e.printStackTrace();sqlSession.rollback(); throw e; }finally {sqlsession.close();}

8、Xml映射文件中,除了常見的select|insert|updae|delete標簽之外,還有哪些標簽?

、、、、,加上動態sql的9個標簽,其中為sql片段標簽,通過標簽引入sql片段,為不支持自增的主鍵生成策略標簽。

9、MyBatis實現一對一有幾種方式?具體怎么操作的?

有聯合查詢和嵌套查詢,聯合查詢是幾個表聯合查詢,只查詢一次, 通過在resultMap里面配置association節點配置一對一的類就可以完成;

嵌套查詢是先查一個表,根據這個表里面的結果的 外鍵id,去再另外一個表里面查詢數據,也是通過association配置,但另外一個表的查詢通過select屬性配置。

10、Mybatis是否支持延遲加載?如果支持,它的實現原理是什么?

Mybatis僅支持association關聯對象和collection關聯集合對象的延遲加載,association指的就是一對一,collection指的就是一對多查詢。在Mybatis配置文件中,可以配置是否啟用延遲加載lazyLoadingEnabled=true|false。

它的原理是,使用CGLIB創建目標對象的代理對象,當調用目標方法時,進入攔截器方法,比如調用a.getB().getName(),攔截器invoke()方法發現a.getB()是null值,那么就會單獨發送事先保存好的查詢關聯B對象的sql,把B查詢上來,然后調用a.setB(b),于是a的對象b屬性就有值了,接著完成a.getB().getName()方法的調用。這就是延遲加載的基本原理。

當然了,不光是Mybatis,幾乎所有的包括Hibernate,支持延遲加載的原理都是一樣的。

11、Mybatis的一級、二級緩存:

1)一級緩存: 基于 PerpetualCache 的 HashMap 本地緩存,其存儲作用域為 Session,當 Session flush 或 close 之后,該 Session 中的所有 Cache 就將清空,默認打開一級緩存。

2)二級緩存與一級緩存其機制相同,默認也是采用 PerpetualCache,HashMap 存儲,不同在于其存儲作用域為 Mapper(Namespace),并且可自定義存儲源,如 Ehcache。默認不打開二級緩存,要開啟二級緩存,使用二級緩存屬性類需要實現Serializable序列化接口(可用來保存對象的狀態),可在它的映射文件中配置 ;

3)對于緩存數據更新機制,當某一個作用域(一級緩存 Session/二級緩存Namespaces)的進行了C/U/D 操作后,默認該作用域下所有 select 中的緩存將被 clear 掉并重新更新,如果開啟了二級緩存,則只根據配置判斷是否刷新。

SpringBoot篇

1、什么是SpringBoot?為什么要用SpringBoot

用來簡化spring應用的初始搭建以及開發過程 使用特定的方式來進行配置(properties或yml文件)

創建獨立的spring引用程序 main方法運行

嵌入的Tomcat 無需部署war文件

簡化maven配置

自動配置spring添加對應功能starter自動化配置

spring boot來簡化spring應用開發,約定大于配置,去繁從簡,just run就能創建一個獨立的,產品級別的應用

Spring Boot 優點非常多,如:

一、獨立運行

Spring Boot而且內嵌了各種servlet容器,Tomcat、Jetty等,現在不再需要打成war包部署到容器中,Spring Boot只要打成一個可執行的jar包就能獨立運行,所有的依賴包都在一個jar包內。

二、簡化配置

spring-boot-starter-web啟動器自動依賴其他組件,簡少了maven的配置。
三、自動配置

Spring Boot能根據當前類路徑下的類、jar包來自動配置bean,如添加一個spring-boot-starter-web啟動器就能擁有web的功能,無需其他配置。

四、無代碼生成和XML配置

Spring Boot配置過程中無代碼生成,也無需XML配置文件就能完成所有配置工作,這一切都是借助于條件注解完成的,這也是Spring4.x的核心功能之一。

五、應用監控

Spring Boot提供一系列端點可以監控服務及應用,做健康檢測。

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

啟動類上面的注解是@SpringBootApplication,它也是 Spring Boot 的核心注解,主要組合包含了以下 3 個注解:

@SpringBootConfiguration:組合了 @Configuration 注解,實現配置文件的功能。

@EnableAutoConfiguration:打開自動配置的功能,也可以關閉某個自動配置的選項,如關閉數據源自動配置功能: @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })。

@ComponentScan:Spring組件掃描。

3、運行Spring Boot有哪幾種方式?

1)打包用命令或者放到容器中運行

2)用 Maven/Gradle 插件運行

3)直接執行 main 方法運行

4、如何理解 Spring Boot 中的 Starters?

Starters是什么:

Starters可以理解為啟動器,它包含了一系列可以集成到應用里面的依賴包,你可以一站式集成Spring及其他技術,而不需要到處找示例代碼和依賴包。如你想使用Spring JPA訪問數據庫,只要加入spring-boot-starter-data-jpa啟動器依賴就能使用了。Starters包含了許多項目中需要用到的依賴,它們能快速持續的運行,都是一系列得到支持的管理傳遞性依賴。

Starters命名:

Spring Boot官方的啟動器都是以spring-boot-starter-命名的,代表了一個特定的應用類型。第三方的啟動器不能以spring-boot開頭命名,它們都被Spring Boot官方保留。一般一個第三方的應該這樣命名,像mybatis的mybatis-spring-boot-starter。

Starters分類:

  1. Spring Boot應用類啟動器

img

  1. Spring Boot生產啟動器

img

  1. Spring Boot技術類啟動器

img

  1. 其他第三方啟動器

5、 如何在Spring Boot啟動的時候運行一些特定的代碼?

如果你想在Spring Boot啟動的時候運行一些特定的代碼,你可以實現接口ApplicationRunner或者CommandLineRunner,這兩個接口實現方式一樣,它們都只提供了一個run方法。

CommandLineRunner:啟動獲取命令行參數

6、 Spring Boot 需要獨立的容器運行嗎?

可以不需要,內置了 Tomcat/ Jetty 等容器。

7、 Spring Boot中的監視器是什么?

Spring boot actuator是spring啟動框架中的重要功能之一。Spring boot監視器可幫助您訪問生產環境中正在運行的應用程序的當前狀態。有幾個指標必須在生產環境中進行檢查和監控。即使一些外部應用程序可能正在使用這些服務來向相關人員觸發警報消息。監視器模塊公開了一組可直接作為HTTP URL訪問的REST端點來檢查狀態。

8、 如何使用Spring Boot實現異常處理?

Spring提供了一種使用ControllerAdvice處理異常的非常有用的方法。 我們通過實現一個ControlerAdvice類,來處理控制器類拋出的所有異常。

9、 你如何理解 Spring Boot 中的 Starters?

Starters可以理解為啟動器,它包含了一系列可以集成到應用里面的依賴包,你可以一站式集成 Spring 及其他技術,而不需要到處找示例代碼和依賴包。如你想使用 Spring JPA 訪問數據庫,只要加入 spring-boot-starter-data-jpa 啟動器依賴就能使用了。

10、 springboot常用的starter有哪些

spring-boot-starter-web 嵌入tomcat和web開發需要servlet與jsp支持

spring-boot-starter-data-jpa 數據庫支持

spring-boot-starter-data-redis redis數據庫支持

spring-boot-starter-data-solr solr支持

mybatis-spring-boot-starter 第三方的mybatis集成starter

11、 SpringBoot 實現熱部署有哪幾種方式?

主要有兩種方式:

  • Spring Loaded
  • Spring-boot-devtools

12、 如何理解 Spring Boot 配置加載順序?

在 Spring Boot 里面,可以使用以下幾種方式來加載配置。

1)properties文件;

2)YAML文件;

3)系統環境變量;

4)命令行參數;

等等……

13、 Spring Boot 的核心配置文件有哪幾個?它們的區別是什么?

pring Boot 的核心配置文件是 application 和 bootstrap 配置文件。

application 配置文件這個容易理解,主要用于 Spring Boot 項目的自動化配置。

bootstrap 配置文件有以下幾個應用場景。

  • 使用 Spring Cloud Config 配置中心時,這時需要在 bootstrap 配置文件中添加連接到配置中心的配置屬性來加載外部配置中心的配置信息;
  • 一些固定的不能被覆蓋的屬性;
  • 一些加密/解密的場景;

14、如何集成 Spring Boot 和 ActiveMQ?

對于集成 Spring Boot 和 ActiveMQ,我們使用
spring-boot-starter-activemq
依賴關系。 它只需要很少的配置,并且不需要樣板代碼。

15、如何重新加載Spring Boot上的更改,而無需重新啟動服務器?

這可以使用DEV工具來實現。通過這種依賴關系,您可以節省任何更改,嵌入式tomcat將重新啟動。

Spring Boot有一個開發工具(DevTools)模塊,它有助于提高開發人員的生產力。Java開發人員面臨的一個主要挑戰是將文件更改自動部署到服務器并自動重啟服務器。

開發人員可以重新加載Spring Boot上的更改,而無需重新啟動服務器。這將消除每次手動部署更改的需要。Spring Boot在發布它的第一個版本時沒有這個功能。

這是開發人員最需要的功能。DevTools模塊完全滿足開發人員的需求。該模塊將在生產環境中被禁用。它還提供H2數據庫控制臺以更好地測試應用程序。

org.springframework.boot

spring-boot-devtools

true

16、 Spring Boot、Spring MVC 和 Spring 有什么區別?

1、Spring

Spring最重要的特征是依賴注入。所有 SpringModules 不是依賴注入就是 IOC 控制反轉。

當我們恰當的使用 DI 或者是 IOC 的時候,我們可以開發松耦合應用。松耦合應用的單元測試可以很容易的進行。

2、Spring MVC

Spring MVC 提供了一種分離式的方法來開發 Web 應用。通過運用像 DispatcherServelet,MoudlAndView 和 ViewResolver 等一些簡單的概念,開發 Web 應用將會變的非常簡單。

3、SpringBoot

Spring 和 SpringMVC 的問題在于需要配置大量的參數。

img

Spring Boot 通過一個自動配置和啟動的項來目解決這個問題。為了更快的構建產品就緒應用程序,Spring Boot 提供了一些非功能性特征。

17、 能否舉一個例子來解釋更多 Staters 的內容?

讓我們來思考一個 Stater 的例子 -Spring Boot Stater Web。

如果你想開發一個 web 應用程序或者是公開 REST 服務的應用程序。Spring Boot Start Web 是首選。讓我們使用 Spring Initializr 創建一個 Spring Boot Start Web 的快速項目。

Spring Boot Start Web 的依賴項

img

下面的截圖是添加進我們應用程序的不同的依賴項

img

依賴項可以被分為:

  • Spring - core,beans,context,aop
  • Web MVC - (Spring MVC)
  • Jackson - for JSON Binding
  • Validation - Hibernate,Validation API
  • Enbedded Servlet Container - Tomcat
  • Logging - logback,slf4j

任何經典的 Web 應用程序都會使用所有這些依賴項。Spring Boot Starter Web 預先打包了這些依賴項。

作為一個開發者,我不需要再擔心這些依賴項和它們的兼容版本。

18、 Spring Boot 還提供了其它的哪些 Starter Project Options?

Spring Boot 也提供了其它的啟動器項目包括,包括用于開發特定類型應用程序的典型依賴項。

  • spring-boot-starter-web-services - SOAP Web Services;
  • spring-boot-starter-web - Web 和 RESTful 應用程序;
  • spring-boot-starter-test - 單元測試和集成測試;
  • spring-boot-starter-jdbc - 傳統的 JDBC;
  • spring-boot-starter-hateoas - 為服務添加 HATEOAS 功能;
  • spring-boot-starter-security - 使用 SpringSecurity 進行身份驗證和授權;
  • spring-boot-starter-data-jpa - 帶有 Hibeernate 的 Spring Data JPA;
  • spring-boot-starter-data-rest - 使用 Spring Data REST 公布簡單的 REST 服務;

MySQL篇

1、數據庫的三范式是什么

第一范式:列不可再分
第二范式:行可以唯一區分,主鍵約束
第三范式:表的非主屬性不能依賴與其他表的非主屬性 外鍵約束
且三大范式是一級一級依賴的,第二范式建立在第一范式上,第三范式建立第一第二范式上。

2、數據庫引擎有哪些

如何查看mysql提供的所有存儲引擎

mysql> show engines;

查看MySQL提供的所有存儲引擎

mysql常用引擎包括:MYISAM、Innodb、Memory、MERGE

  • MYISAM:全表鎖,擁有較高的執行速度,不支持事務,不支持外鍵,并發性能差,占用空間相對較小,對事務完整性沒有要求,以select、insert為主的應用基本上可以使用這引擎
  • Innodb:行級鎖,提供了具有提交、回滾和崩潰回復能力的事務安全,支持自動增長列,支持外鍵約束,并發能力強,占用空間是MYISAM的2.5倍,處理效率相對會差一些
  • Memory:全表鎖,存儲在內容中,速度快,但會占用和數據量成正比的內存空間且數據在mysql重啟時會丟失,默認使用HASH索引,檢索效率非常高,但不適用于精確查找,主要用于那些內容變化不頻繁的代碼表
  • MERGE:是一組MYISAM表的組合

3、InnoDB與MyISAM的區別

  1. InnoDB支持事務,MyISAM不支持,對于InnoDB每一條SQL語言都默認封裝成事務,自動提交,這樣會影響速度,所以最好把多條SQL語言放在begin和commit之間,組成一個事務;
  2. InnoDB支持外鍵,而MyISAM不支持。對一個包含外鍵的InnoDB表轉為MYISAM會失敗;
  3. InnoDB是聚集索引,數據文件是和索引綁在一起的,必須要有主鍵,通過主鍵索引效率很高。但是輔助索引需要兩次查詢,先查詢到主鍵,然后再通過主鍵查詢到數據。因此,主鍵不應該過大,因為主鍵太大,其他索引也都會很大。而MyISAM是非聚集索引,數據文件是分離的,索引保存的是數據文件的指針。主鍵索引和輔助索引是獨立的。
  4. InnoDB不保存表的具體行數,執行select count(*) from table時需要全表掃描。而MyISAM用一個變量保存了整個表的行數,執行上述語句時只需要讀出該變量即可,速度很快;
  5. Innodb不支持全文索引,而MyISAM支持全文索引,查詢效率上MyISAM要高;

如何選擇引擎?

如果沒有特別的需求,使用默認的Innodb即可。

MyISAM:以讀寫插入為主的應用程序,比如博客系統、新聞門戶網站。

Innodb:更新(刪除)操作頻率也高,或者要保證數據的完整性;并發量高,支持事務和外鍵。比如OA自動化辦公系統。

4、數據庫的事務

什么是事務?: 多條sql語句,要么全部成功,要么全部失敗。

事務的特性:

數據庫事務特性:原子性(Atomic)、一致性(Consistency)、隔離性(Isolation)、持久性(Durabiliy)。簡稱ACID。

  • 原子性:組成一個事務的多個數據庫操作是一個不可分割的原子單元,只有所有操作都成功,整個事務才會提交。任何一個操作失敗,已經執行的任何操作都必須撤銷,讓數據庫返回初始狀態。
  • 一致性:事務操作成功后,數據庫所處的狀態和它的業務規則是一致的。即數據不會被破壞。如A轉賬100元給B,不管操作是否成功,A和B的賬戶總額是不變的。
  • 隔離性:在并發數據操作時,不同的事務擁有各自的數據空間,它們的操作不會對彼此產生干擾
  • 持久性:一旦事務提交成功,事務中的所有操作都必須持久化到數據庫中。

5、索引問題

索引是對數據庫表中一個或多個列的值進行排序的結構,建立索引有助于快速獲取信息。

你也可以這樣理解:索引就是加快檢索表中數據的方法。數據庫的索引類似于書籍的索引。在書籍中,索引允許用戶不必翻閱完整個書就能迅速地找到所需要的信息。在數據庫中,索引也允許數據庫程序迅速地找到表中的數據,而不必掃描整個數據庫。

mysql有4種不同的索引:

  • 主鍵索引(PRIMARY)

    數據列不允許重復,不允許為NULL,一個表只能有一個主鍵。

  • 唯一索引(UNIQUE)

    數據列不允許重復,允許為NULL值,一個表允許多個列創建唯一索引。

    • 可以通過 ALTER TABLE table_name ADD UNIQUE (column); 創建唯一索引
    • 可以通過 ALTER TABLE table_name ADD UNIQUE (column1,column2); 創建唯一組合索引
  • 普通索引(INDEX)

    • 可以通過ALTER TABLE table_name ADD INDEX index_name (column);創建普通索引
    • 可以通過ALTER TABLE table_name ADD INDEX index_name(column1, column2, column3);創建組合索引
  • 全文索引(FULLTEXT)

    可以通過ALTER TABLE table_name ADD FULLTEXT (column);創建全文索引

索引并非是越多越好,創建索引也需要耗費資源,一是增加了數據庫的存儲空間,二是在插入和刪除時要花費較多的時間維護索引

  • 索引加快數據庫的檢索速度
  • 索引降低了插入、刪除、修改等維護任務的速度
  • 唯一索引可以確保每一行數據的唯一性
  • 通過使用索引,可以在查詢的過程中使用優化隱藏器,提高系統的性能
  • 索引需要占物理和數據空間

6、SQL優化

1、查詢語句中不要使用select *

2、盡量減少子查詢,使用關聯查詢(left join,right join,inner join)替代

3、減少使用IN或者NOT IN ,使用exists,not exists或者關聯查詢語句替代

4、or 的查詢盡量用 union或者union all 代替(在確認沒有重復數據或者不用剔除重復數據時,union all會更好)

5、應盡量避免在 where 子句中使用!=或<>操作符,否則將引擎放棄使用索引而進行全表掃描。

6、應盡量避免在 where 子句中對字段進行 null 值判斷,否則將導致引擎放棄使用索引而進行全表掃描,如: select id from t where num is null 可以在num上設置默認值0,確保表中num列沒有null值,然后這樣查詢: select id from t where num=0

7、簡單說一說drop、delete與truncate的區別

SQL中的drop、delete、truncate都表示刪除,但是三者有一些差別

delete和truncate只刪除表的數據不刪除表的結構
速度,一般來說: drop> truncate >delete
delete語句是dml,這個操作會放到rollback segement中,事務提交之后才生效;
如果有相應的trigger,執行的時候將被觸發. truncate,drop是ddl, 操作立即生效,原數據不放到rollback segment中,不能回滾. 操作不觸發trigger.

8、什么是視圖

視圖是一種虛擬的表,具有和物理表相同的功能。可以對視圖進行增,改,查,操作,試圖通常是有一個表或者多個表的行或列的子集。對視圖的修改不影響基本表。它使得我們獲取數據更容易,相比多表查詢。

9、 什么是內聯接、左外聯接、右外聯接?

  • 內聯接(Inner Join):匹配2張表中相關聯的記錄。
  • 左外聯接(Left Outer Join):除了匹配2張表中相關聯的記錄外,還會匹配左表中剩余的記錄,右表中未匹配到的字段用NULL表示。
  • 右外聯接(Right Outer Join):除了匹配2張表中相關聯的記錄外,還會匹配右表中剩余的記錄,左表中未匹配到的字段用NULL表示。在判定左表和右表時,要根據表名出現在Outer Join的左右位置關系。

10、并發事務帶來哪些問題?

在典型的應用程序中,多個事務并發運行,經常會操作相同的數據來完成各自的任務(多個用戶對同一數據進行操作)。并發雖然是必須的,但可能會導致以下的問題。

  • 臟讀(Dirty read): 當一個事務正在訪問數據并且對數據進行了修改,而這種修改還沒有提交到數據庫中,這時另外一個事務也訪問了這個數據,然后使用了這個數據。因為這個數據是還沒有提交的數據,那么另外一個事務讀到的這個數據是“臟數據”,依據“臟數據”所做的操作可能是不正確的。
  • 丟失修改(Lost to modify): 指在一個事務讀取一個數據時,另外一個事務也訪問了該數據,那么在第一個事務中修改了這個數據后,第二個事務也修改了這個數據。這樣第一個事務內的修改結果就被丟失,因此稱為丟失修改。 例如:事務1讀取某表中的數據A=20,事務2也讀取A=20,事務1修改A=A-1,事務2也修改A=A-1,最終結果A=19,事務1的修改被丟失。
  • 不可重復讀(Unrepeatableread): 指在一個事務內多次讀同一數據。在這個事務還沒有結束時,另一個事務也訪問該數據。那么,在第一個事務中的兩次讀數據之間,由于第二個事務的修改導致第一個事務兩次讀取的數據可能不太一樣。這就發生了在一個事務內兩次讀到的數據是不一樣的情況,因此稱為不可重復讀。
  • 幻讀(Phantom read): 幻讀與不可重復讀類似。它發生在一個事務(T1)讀取了幾行數據,接著另一個并發事務(T2)插入了一些數據時。在隨后的查詢中,第一個事務(T1)就會發現多了一些原本不存在的記錄,就好像發生了幻覺一樣,所以稱為幻讀。

不可重復讀和幻讀區別:

不可重復讀的重點是修改比如多次讀取一條記錄發現其中某些列的值被修改,幻讀的重點在于新增或者刪除比如多次讀取一條記錄發現記錄增多或減少了。

11、事務隔離級別有哪些?MySQL的默認隔離級別是?

SQL 標準定義了四個隔離級別:

  • READ-UNCOMMITTED(讀取未提交): 最低的隔離級別,允許讀取尚未提交的數據變更,可能會導致臟讀、幻讀或不可重復讀
  • READ-COMMITTED(讀取已提交): 允許讀取并發事務已經提交的數據,可以阻止臟讀,但是幻讀或不可重復讀仍有可能發生
  • REPEATABLE-READ(可重復讀): 對同一字段的多次讀取結果都是一致的,除非數據是被本身事務自己所修改,可以阻止臟讀和不可重復讀,但幻讀仍有可能發生
  • SERIALIZABLE(可串行化): 最高的隔離級別,完全服從ACID的隔離級別。所有的事務依次逐個執行,這樣事務之間就完全不可能產生干擾,也就是說,該級別可以防止臟讀、不可重復讀以及幻讀
隔離級別臟讀不可重復讀幻影讀
READ-UNCOMMITTED
READ-COMMITTED×
REPEATABLE-READ××
SERIALIZABLE×××

MySQL InnoDB 存儲引擎的默認支持的隔離級別是 REPEATABLE-READ(可重讀)。我們可以通過SELECT @@tx_isolation;命令來查看

mysql> SELECT @@tx_isolation;
+-----------------+
| @@tx_isolation  |
+-----------------+
| REPEATABLE-READ |
+-----------------+

這里需要注意的是:與 SQL 標準不同的地方在于 InnoDB 存儲引擎在 REPEATABLE-READ(可重讀) 事務隔離級別下使用的是Next-Key Lock 鎖算法,因此可以避免幻讀的產生,這與其他數據庫系統(如 SQL Server) 是不同的。所以說InnoDB 存儲引擎的默認支持的隔離級別是 REPEATABLE-READ(可重讀) 已經可以完全保證事務的隔離性要求,即達到了 SQL標準的 SERIALIZABLE(可串行化) 隔離級別。因為隔離級別越低,事務請求的鎖越少,所以大部分數據庫系統的隔離級別都是 READ-COMMITTED(讀取提交內容) ,但是你要知道的是InnoDB 存儲引擎默認使用 REPEAaTABLE-READ(可重讀) 并不會有任何性能損失。

InnoDB 存儲引擎在 分布式事務 的情況下一般會用到 SERIALIZABLE(可串行化) 隔離級別。

12、大表如何優化?

當MySQL單表記錄數過大時,數據庫的CRUD性能會明顯下降,一些常見的優化措施如下:

1. 限定數據的范圍

務必禁止不帶任何限制數據范圍條件的查詢語句。比如:我們當用戶在查詢訂單歷史的時候,我們可以控制在一個月的范圍內;

2. 讀/寫分離

經典的數據庫拆分方案,主庫負責寫,從庫負責讀;

3. 垂直分區

根據數據庫里面數據表的相關性進行拆分。 例如,用戶表中既有用戶的登錄信息又有用戶的基本信息,可以將用戶表拆分成兩個單獨的表,甚至放到單獨的庫做分庫。

簡單來說垂直拆分是指數據表列的拆分,把一張列比較多的表拆分為多張表。 如下圖所示,這樣來說大家應該就更容易理解了。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-NOi4s0GM-1587954092062)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1583307481617.png)]

  • 垂直拆分的優點: 可以使得列數據變小,在查詢時減少讀取的Block數,減少I/O次數。此外,垂直分區可以簡化表的結構,易于維護。
  • 垂直拆分的缺點: 主鍵會出現冗余,需要管理冗余列,并會引起Join操作,可以通過在應用層進行Join來解決。此外,垂直分區會讓事務變得更加復雜;

4. 水平分區

保持數據表結構不變,通過某種策略存儲數據分片。這樣每一片數據分散到不同的表或者庫中,達到了分布式的目的。 水平拆分可以支撐非常大的數據量。

水平拆分是指數據表行的拆分,表的行數超過200萬行時,就會變慢,這時可以把一張的表的數據拆成多張表來存放。舉個例子:我們可以將用戶信息表拆分成多個用戶信息表,這樣就可以避免單一表數據量過大對性能造成影響。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-VqSH0Kju-1587954092065)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1583308353521.png)]

水平拆分可以支持非常大的數據量。需要注意的一點是:分表僅僅是解決了單一表數據過大的問題,但由于表的數據還是在同一臺機器上,其實對于提升MySQL并發能力沒有什么意義,所以 水平拆分最好分庫

水平拆分能夠 支持非常大的數據量存儲,應用端改造也少,但 分片事務難以解決 ,跨節點Join性能較差,邏輯復雜。《Java工程師修煉之道》的作者推薦 盡量不要對數據進行分片,因為拆分會帶來邏輯、部署、運維的各種復雜度 ,一般的數據表在優化得當的情況下支撐千萬以下的數據量是沒有太大問題的。如果實在要分片,盡量選擇客戶端分片架構,這樣可以減少一次和中間件的網絡I/O。

下面補充一下數據庫分片的兩種常見方案:

  • 客戶端代理: 分片邏輯在應用端,封裝在jar包中,通過修改或者封裝JDBC層來實現。 當當網的 Sharding-JDBC 、阿里的TDDL是兩種比較常用的實現。
  • 中間件代理: 在應用和數據中間加了一個代理層。分片邏輯統一維護在中間件服務中。 我們現在談的 Mycat 、360的Atlas、網易的DDB等等都是這種架構的實現。

詳細內容可以參考: MySQL大表優化方案: https://segmentfault.com/a/1190000006158186

13、分庫分表之后,id 主鍵如何處理?

因為要是分成多個表之后,每個表都是從 1 開始累加,這樣是不對的,我們需要一個全局唯一的 id 來支持。

生成全局 id 有下面這幾種方式:

  • UUID:不適合作為主鍵,因為太長了,并且無序不可讀,查詢效率低。比較適合用于生成唯一的名字的標示比如文件的名字。
  • 數據庫自增 id : 兩臺數據庫分別設置不同步長,生成不重復ID的策略來實現高可用。這種方式生成的 id 有序,但是需要獨立部署數據庫實例,成本高,還會有性能瓶頸。
  • 利用 redis 生成 id : 性能比較好,靈活方便,不依賴于數據庫。但是,引入了新的組件造成系統更加復雜,可用性降低,編碼更加復雜,增加了系統成本。
  • Twitter的snowflake算法 :Github 地址:https://github.com/twitter-archive/snowflake。
  • 美團的Leaf分布式ID生成系統 :Leaf 是美團開源的分布式ID生成器,能保證全局唯一性、趨勢遞增、單調遞增、信息安全,里面也提到了幾種分布式方案的對比,但也需要依賴關系數據庫、Zookeeper等中間件。感覺還不錯。美團技術團隊的一篇文章:https://tech.meituan.com/2017/04/21/mt-leaf.html 。

14、mysql有關權限的表都有哪幾個

MySQL服務器通過權限表來控制用戶對數據庫的訪問,權限表存放在mysql數據庫里,由mysql_install_db腳本初始化。這些權限表分別user,db,table_priv,columns_priv和host。下面分別介紹一下這些表的結構和內容:

  • user權限表:記錄允許連接到服務器的用戶帳號信息,里面的權限是全局級的。

  • db權限表:記錄各個帳號在各個數據庫上的操作權限。

  • table_priv權限表:記錄數據表級的操作權限。

  • columns_priv權限表:記錄數據列級的操作權限。

  • host權限表:配合db權限表對給定主機上數據庫級操作權限作更細致的控制。這個權限表不受GRANT和REVOKE語句的影響。

15、mysql有哪些數據類型

1、整數類型 ,包括TINYINT、SMALLINT、MEDIUMINT、INT、BIGINT,分別表示1字節、2字節、3字節、4字節、8字節整數。任何整數類型都可以加上UNSIGNED屬性,表示數據是無符號的,即非負整數。
長度:整數類型可以被指定長度,例如:INT(11)表示長度為11的INT類型。長度在大多數場景是沒有意義的,它不會限制值的合法范圍,只會影響顯示字符的個數,而且需要和UNSIGNED ZEROFILL屬性配合使用才有意義。
例子,假定類型設定為INT(5),屬性為UNSIGNED ZEROFILL,如果用戶插入的數據為12的話,那么數據庫實際存儲數據為00012。

2、實數類型,包括FLOAT、DOUBLE、DECIMAL。
DECIMAL可以用于存儲比BIGINT還大的整型,能存儲精確的小數。
而FLOAT和DOUBLE是有取值范圍的,并支持使用標準的浮點進行近似計算。
計算時FLOAT和DOUBLE相比DECIMAL效率更高一些,DECIMAL你可以理解成是用字符串進行處理。

3、字符串類型,包括VARCHAR、CHAR、TEXT、BLOB
VARCHAR用于存儲可變長字符串,它比定長類型更節省空間。
VARCHAR使用額外1或2個字節存儲字符串長度。列長度小于255字節時,使用1字節表示,否則使用2字節表示。
VARCHAR存儲的內容超出設置的長度時,內容會被截斷。
CHAR是定長的,根據定義的字符串長度分配足夠的空間。
CHAR會根據需要使用空格進行填充方便比較。
CHAR適合存儲很短的字符串,或者所有值都接近同一個長度。
CHAR存儲的內容超出設置的長度時,內容同樣會被截斷。

使用策略:
對于經常變更的數據來說,CHAR比VARCHAR更好,因為CHAR不容易產生碎片。
對于非常短的列,CHAR比VARCHAR在存儲空間上更有效率。
使用時要注意只分配需要的空間,更長的列排序時會消耗更多內存。
盡量避免使用TEXT/BLOB類型,查詢時會使用臨時表,導致嚴重的性能開銷。

4、枚舉類型(ENUM),把不重復的數據存儲為一個預定義的集合。
有時可以使用ENUM代替常用的字符串類型。
ENUM存儲非常緊湊,會把列表值壓縮到一個或兩個字節。
ENUM在內部存儲時,其實存的是整數。
盡量避免使用數字作為ENUM枚舉的常量,因為容易混亂。
排序是按照內部存儲的整數

5、日期和時間類型,盡量使用timestamp,空間效率高于datetime,
用整數保存時間戳通常不方便處理。
如果需要存儲微妙,可以使用bigint存儲。
看到這里,這道真題是不是就比較容易回答了。

16、創建索引的三種方式,刪除索引

第一種方式:在執行CREATE TABLE時創建索引

CREATE TABLE user_index2 (id INT auto_increment PRIMARY KEY,first_name VARCHAR (16),last_name VARCHAR (16),id_card VARCHAR (18),information text,KEY name (first_name, last_name),FULLTEXT KEY (information),UNIQUE KEY (id_card)
);

第二種方式:使用ALTER TABLE命令去增加索引

ALTER TABLE table_name ADD INDEX index_name (column_list);

ALTER TABLE用來創建普通索引、UNIQUE索引或PRIMARY KEY索引。

其中table_name是要增加索引的表名,column_list指出對哪些列進行索引,多列時各列之間用逗號分隔。

索引名index_name可自己命名,缺省時,MySQL將根據第一個索引列賦一個名稱。另外,ALTER TABLE允許在單個語句中更改多個表,因此可以在同時創建多個索引。

第三種方式:使用CREATE INDEX命令創建

CREATE INDEX index_name ON table_name (column_list);

CREATE INDEX可對表增加普通索引或UNIQUE索引。(但是,不能創建PRIMARY KEY索引)

刪除索引

根據索引名刪除普通索引、唯一索引、全文索引:alter table 表名 drop KEY 索引名

alter table user_index drop KEY name;
alter table user_index drop KEY id_card;
alter table user_index drop KEY information;

刪除主鍵索引:alter table 表名 drop primary key(因為主鍵只有一個)。這里值得注意的是,如果主鍵自增長,那么不能直接執行此操作(自增長依賴于主鍵索引):

img

需要取消自增長再行刪除:

alter table user_index
-- 重新定義字段
MODIFY id int,
drop PRIMARY KEY

但通常不會刪除主鍵,因為設計主鍵一定與業務邏輯無關。

Redis篇

1、Redis持久化機制

Redis是一個支持持久化的內存數據庫,通過持久化機制把內存中的數據同步到硬盤文件來保證數據持久化。當Redis重啟后通過把硬盤文件重新加載到內存,就能達到恢復數據的目的。
實現:單獨創建fork()一個子進程,將當前父進程的數據庫數據復制到子進程的內存中,然后由子進程寫入到臨時文件中,持久化的過程結束了,再用這個臨時文件替換上次的快照文件,然后子進程退出,內存釋放。

RDB是Redis默認的持久化方式。按照一定的時間周期策略把內存的數據以快照的形式保存到硬盤的二進制文件。即Snapshot快照存儲,對應產生的數據文件為dump.rdb,通過配置文件中的save參數來定義快照的周期。( 快照可以是其所表示的數據的一個副本,也可以是數據的一個復制品。)
AOF:Redis會將每一個收到的寫命令都通過Write函數追加到文件最后,類似于MySQL的binlog。當Redis重啟是會通過重新執行文件中保存的寫命令來在內存中重建整個數據庫的內容。
當兩種方式同時開啟時,數據恢復Redis會優先選擇AOF恢復。

2、緩存雪崩、緩存穿透、緩存預熱、緩存更新、緩存降級等問題

一、緩存雪崩

我們可以簡單的理解為:由于原有緩存失效,新緩存未到期間
(例如:我們設置緩存時采用了相同的過期時間,在同一時刻出現大面積的緩存過期),所有原本應該訪問緩存的請求都去查詢數據庫了,而對數據庫CPU和內存造成巨大壓力,嚴重的會造成數據庫宕機。從而形成一系列連鎖反應,造成整個系統崩潰。
解決辦法:
大多數系統設計者考慮用加鎖( 最多的解決方案)或者隊列的方式保證來保證不會有大量的線程對數據庫一次性進行讀寫,從而避免失效時大量的并發請求落到底層存儲系統上。還有一個簡單方案就時講緩存失效時間分散開。

二、緩存穿透
緩存穿透是指用戶查詢數據,在數據庫沒有,自然在緩存中也不會有。這樣就導致用戶查詢的時候,在緩存中找不到,每次都要去數據庫再查詢一遍,然后返回空(相當于進行了兩次無用的查詢)。這樣請求就繞過緩存直接查數據庫,這也是經常提的緩存命中率問題。
解決辦法;
最常見的則是采用布隆過濾器,將所有可能存在的數據哈希到一個足夠大的bitmap中,一個一定不存在的數據會被這個bitmap攔截掉,從而避免了對底層存儲系統的查詢壓力。
另外也有一個更為簡單粗暴的方法,如果一個查詢返回的數據為空(不管是數據不存在,還是系統故障),我們仍然把這個空結果進行緩存,但它的過期時間會很短,最長不超過五分鐘。通過這個直接設置的默認值存放到緩存,這樣第二次到緩沖中獲取就有值了,而不會繼續訪問數據庫,這種辦法最簡單粗暴。
5TB的硬盤上放滿了數據,請寫一個算法將這些數據進行排重。如果這些數據是一些32bit大小的數據該如何解決?如果是64bit的呢?

對于空間的利用到達了一種極致,那就是Bitmap和布隆過濾器(Bloom Filter)。
Bitmap: 典型的就是哈希表
缺點是,Bitmap對于每個元素只能記錄1bit信息,如果還想完成額外的功能,恐怕只能靠犧牲更多的空間、時間來完成了。

布隆過濾器(推薦)
就是引入了k(k>1)k(k>1)個相互獨立的哈希函數,保證在給定的空間、誤判率下,完成元素判重的過程。
它的優點是空間效率和查詢時間都遠遠超過一般的算法,缺點是有一定的誤識別率和刪除困難。
Bloom-Filter算法的核心思想就是利用多個不同的Hash函數來解決“沖突”。
Hash存在一個沖突(碰撞)的問題,用同一個Hash得到的兩個URL的值有可能相同。為了減少沖突,我們可以多引入幾個Hash,如果通過其中的一個Hash值我們得出某元素不在集合中,那么該元素肯定不在集合中。只有在所有的Hash函數告訴我們該元素在集合中時,才能確定該元素存在于集合中。這便是Bloom-Filter的基本思想。
Bloom-Filter一般用于在大數據量的集合中判定某元素是否存在。

三、緩存預熱
緩存預熱這個應該是一個比較常見的概念,相信很多小伙伴都應該可以很容易的理解,緩存預熱就是系統上線后,將相關的緩存數據直接加載到緩存系統。這樣就可以避免在用戶請求的時候,先查詢數據庫,然后再將數據緩存的問題!用戶直接查詢事先被預熱的緩存數據!
解決思路:
1、直接寫個緩存刷新頁面,上線時手工操作下;
2、數據量不大,可以在項目啟動的時候自動進行加載;
3、定時刷新緩存;

四、緩存更新
除了緩存服務器自帶的緩存失效策略之外(Redis默認的有6中策略可供選擇),我們還可以根據具體的業務需求進行自定義的緩存淘汰,常見的策略有兩種:
(1)定時去清理過期的緩存;
(2)當有用戶請求過來時,再判斷這個請求所用到的緩存是否過期,過期的話就去底層系統得到新數據并更新緩存。
兩者各有優劣,第一種的缺點是維護大量緩存的key是比較麻煩的,第二種的缺點就是每次用戶請求過來都要判斷緩存失效,邏輯相對比較復雜!具體用哪種方案,大家可以根據自己的應用場景來權衡。

五、緩存降級
當訪問量劇增、服務出現問題(如響應時間慢或不響應)或非核心服務影響到核心流程的性能時,仍然需要保證服務還是可用的,即使是有損服務。系統可以根據一些關鍵數據進行自動降級,也可以配置開關實現人工降級。
降級的最終目的是保證核心服務可用,即使是有損的。而且有些服務是無法降級的(如加入購物車、結算)。
以參考日志級別設置預案:
(1)一般:比如有些服務偶爾因為網絡抖動或者服務正在上線而超時,可以自動降級;
(2)警告:有些服務在一段時間內成功率有波動(如在95~100%之間),可以自動降級或人工降級,并發送告警;
(3)錯誤:比如可用率低于90%,或者數據庫連接池被打爆了,或者訪問量突然猛增到系統能承受的最大閥值,此時可以根據情況自動降級或者人工降級;
(4)嚴重錯誤:比如因為特殊原因數據錯誤了,此時需要緊急人工降級。

服務降級的目的,是為了防止Redis服務故障,導致數據庫跟著一起發生雪崩問題。因此,對于不重要的緩存數據,可以采取服務降級策略,例如一個比較常見的做法就是,Redis出現問題,不去數據庫查詢,而是直接返回默認值給用戶。

3、熱點數據和冷數據是什么

熱點數據,緩存才有價值
對于冷數據而言,大部分數據可能還沒有再次訪問到就已經被擠出內存,不僅占用內存,而且價值不大。頻繁修改的數據,看情況考慮使用緩存
對于上面兩個例子,壽星列表、導航信息都存在一個特點,就是信息修改頻率不高,讀取通常非常高的場景。
對于熱點數據,比如我們的某IM產品,生日祝福模塊,當天的壽星列表,緩存以后可能讀取數十萬次。再舉個例子,某導航產品,我們將導航信息,緩存以后可能讀取數百萬次。
**數據更新前至少讀取兩次,**緩存才有意義。這個是最基本的策略,如果緩存還沒有起作用就失效了,那就沒有太大價值了。
那存不存在,修改頻率很高,但是又不得不考慮緩存的場景呢?有!比如,這個讀取接口對數據庫的壓力很大,但是又是熱點數據,這個時候就需要考慮通過緩存手段,減少數據庫的壓力,比如我們的某助手產品的,點贊數,收藏數,分享數等是非常典型的熱點數據,但是又不斷變化,此時就需要將數據同步保存到Redis緩存,減少數據庫壓力。

4、Memcache與Redis的區別都有哪些?

1)、存儲方式 Memecache把數據全部存在內存之中,斷電后會掛掉,數據不能超過內存大小。 Redis有部份存在硬盤上,redis可以持久化其數據
2)、數據支持類型 memcached所有的值均是簡單的字符串,redis作為其替代者,支持更為豐富的數據類型 ,提供list,set,zset,hash等數據結構的存儲
3)、使用底層模型不同 它們之間底層實現方式 以及與客戶端之間通信的應用協議不一樣。 Redis直接自己構建了VM 機制 ,因為一般的系統調用系統函數的話,會浪費一定的時間去移動和請求。
4). value 值大小不同:Redis 最大可以達到 1gb;memcache 只有 1mb。
5)redis的速度比memcached快很多
6)Redis支持數據的備份,即master-slave模式的數據備份。

5、單線程的redis為什么這么快

(一)純內存操作
(二)單線程操作,避免了頻繁的上下文切換
(三)采用了非阻塞I/O多路復用機制

6、redis的數據類型,以及每種數據類型的使用場景

回答:一共五種
(一)String
這個其實沒啥好說的,最常規的set/get操作,value可以是String也可以是數字。一般做一些復雜的計數功能的緩存。
(二)hash
這里value存放的是結構化的對象,比較方便的就是操作其中的某個字段。博主在做單點登錄的時候,就是用這種數據結構存儲用戶信息,以cookieId作為key,設置30分鐘為緩存過期時間,能很好的模擬出類似session的效果。
(三)list
使用List的數據結構,可以做簡單的消息隊列的功能。另外還有一個就是,可以利用lrange命令,做基于redis的分頁功能,性能極佳,用戶體驗好。本人還用一個場景,很合適—取行情信息。就也是個生產者和消費者的場景。LIST可以很好的完成排隊,先進先出的原則。
(四)set
因為set堆放的是一堆不重復值的集合。所以可以做全局去重的功能。為什么不用JVM自帶的Set進行去重?因為我們的系統一般都是集群部署,使用JVM自帶的Set,比較麻煩,難道為了一個做一個全局去重,再起一個公共服務,太麻煩了。
另外,就是利用交集、并集、差集等操作,可以計算共同喜好,全部的喜好,自己獨有的喜好等功能。
(五)sorted set
sorted set多了一個權重參數score,集合中的元素能夠按score進行排列。可以做排行榜應用,取TOP N操作。

7、redis的過期策略以及內存淘汰機制

redis采用的是定期刪除+惰性刪除策略。
為什么不用定時刪除策略?
定時刪除,用一個定時器來負責監視key,過期則自動刪除。雖然內存及時釋放,但是十分消耗CPU資源。在大并發請求下,CPU要將時間應用在處理請求,而不是刪除key,因此沒有采用這一策略.
定期刪除+惰性刪除是如何工作的呢?
定期刪除,redis默認每個100ms檢查,是否有過期的key,有過期key則刪除。需要說明的是,redis不是每個100ms將所有的key檢查一次,而是隨機抽取進行檢查(如果每隔100ms,全部key進行檢查,redis豈不是卡死)。因此,如果只采用定期刪除策略,會導致很多key到時間沒有刪除。
于是,惰性刪除派上用場。也就是說在你獲取某個key的時候,redis會檢查一下,這個key如果設置了過期時間那么是否過期了?如果過期了此時就會刪除。
采用定期刪除+惰性刪除就沒其他問題了么?
不是的,如果定期刪除沒刪除key。然后你也沒即時去請求key,也就是說惰性刪除也沒生效。這樣,redis的內存會越來越高。那么就應該采用內存淘汰機制。
在redis.conf中有一行配置

maxmemory-policy volatile-lru

該配置就是配內存淘汰策略的(什么,你沒配過?好好反省一下自己)
volatile-lru:從已設置過期時間的數據集(server.db[i].expires)中挑選最近最少使用的數據淘汰
volatile-ttl:從已設置過期時間的數據集(server.db[i].expires)中挑選將要過期的數據淘汰
volatile-random:從已設置過期時間的數據集(server.db[i].expires)中任意選擇數據淘汰
allkeys-lru:從數據集(server.db[i].dict)中挑選最近最少使用的數據淘汰
allkeys-random:從數據集(server.db[i].dict)中任意選擇數據淘汰
no-enviction(驅逐):禁止驅逐數據,新寫入操作會報錯
ps:如果沒有設置 expire 的key, 不滿足先決條件(prerequisites); 那么 volatile-lru, volatile-random 和 volatile-ttl 策略的行為, 和 noeviction(不刪除) 基本上一致。

8、Redis 為什么是單線程的

官方FAQ表示,因為Redis是基于內存的操作,CPU不是Redis的瓶頸,Redis的瓶頸最有可能是機器內存的大小或者網絡帶寬。既然單線程容易實現,而且CPU不會成為瓶頸,那就順理成章地采用單線程的方案了(畢竟采用多線程會有很多麻煩!)Redis利用隊列技術將并發訪問變為串行訪問
1)絕大部分請求是純粹的內存操作(非常快速)2)采用單線程,避免了不必要的上下文切換和競爭條件
3)非阻塞IO優點:

  • 速度快,因為數據存在內存中,類似于HashMap,HashMap的優勢就是查找和操作的時間復雜度都是O(1)

  • 支持豐富數據類型,支持string,list,set,sorted set,hash

  • 支持事務,操作都是原子性,所謂的原子性就是對數據的更改要么全部執行,要么全部不執行

  • 豐富的特性:可用于緩存,消息,按key設置過期時間,過期后將會自動刪除如何解決redis的并發競爭key問題

同時有多個子系統去set一個key。這個時候要注意什么呢? 不推薦使用redis的事務機制。因為我們的生產環境,基本都是redis集群環境,做了數據分片操作。你一個事務中有涉及到多個key操作的時候,這多個key不一定都存儲在同一個redis-server上。因此,redis的事務機制,十分雞肋。
(1)如果對這個key操作,不要求順序: 準備一個分布式鎖,大家去搶鎖,搶到鎖就做set操作即可
(2)如果對這個key操作,要求順序: 分布式鎖+時間戳。 假設這會系統B先搶到鎖,將key1設置為{valueB 3:05}。接下來系統A搶到鎖,發現自己的valueA的時間戳早于緩存中的時間戳,那就不做set操作了。以此類推。
(3) 利用隊列,將set方法變成串行訪問也可以redis遇到高并發,如果保證讀寫key的一致性
對redis的操作都是具有原子性的,是線程安全的操作,你不用考慮并發問題,redis內部已經幫你處理好并發的問題了。

9、Redis 常見性能問題和解決方案?

(1) Master 最好不要做任何持久化工作,如 RDB 內存快照和 AOF 日志文件
(2) 如果數據比較重要,某個 Slave 開啟 AOF 備份數據,策略設置為每秒同步一次
(3) 為了主從復制的速度和連接的穩定性, Master 和 Slave 最好在同一個局域網內
(4) 盡量避免在壓力很大的主庫上增加從庫
(5) 主從復制不要用圖狀結構,用單向鏈表結構更為穩定,即: Master <- Slave1 <- Slave2 <-Slave3…

10、為什么Redis的操作是原子性的,怎么保證原子性的?

對于Redis而言,命令的原子性指的是:一個操作的不可以再分,操作要么執行,要么不執行。
Redis的操作之所以是原子性的,是因為Redis是單線程的。
Redis本身提供的所有API都是原子操作,Redis中的事務其實是要保證批量操作的原子性。
多個命令在并發中也是原子性的嗎?
不一定, 將get和set改成單命令操作,incr 。使用Redis的事務,或者使用Redis+Lua==的方式實現.

11、Redis事務

Redis事務功能是通過MULTI、EXEC、DISCARD和WATCH 四個原語實現的
Redis會將一個事務中的所有命令序列化,然后按順序執行。
1.redis 不支持回滾“Redis 在事務失敗時不進行回滾,而是繼續執行余下的命令”, 所以 Redis 的內部可以保持簡單且快速。
2.如果在一個事務中的命令出現錯誤,那么所有的命令都不會執行;
3.如果在一個事務中出現運行錯誤,那么正確的命令會被執行。

1)MULTI命令用于開啟一個事務,它總是返回OK。 MULTI執行之后,客戶端可以繼續向服務器發送任意多條命令,這些命令不會立即被執行,而是被放到一個隊列中,當EXEC命令被調用時,所有隊列中的命令才會被執行。
2)EXEC:執行所有事務塊內的命令。返回事務塊內所有命令的返回值,按命令執行的先后順序排列。 當操作被打斷時,返回空值 nil 。
3)通過調用DISCARD,客戶端可以清空事務隊列,并放棄執行事務, 并且客戶端會從事務狀態中退出。
4)WATCH 命令可以為 Redis 事務提供 check-and-set (CAS)行為。 可以監控一個或多個鍵,一旦其中有一個鍵被修改(或刪除),之后的事務就不會執行,監控一直持續到EXEC命令。

SpringCloud篇

1、什么是SpringCloud

Spring cloud 流應用程序啟動器是基于 Spring Boot 的 Spring 集成應用程序,提供與外部系統的集成。Spring cloud Task,一個生命周期短暫的微服務框架,用于快速構建執行有限數據處理的應用程序。

2、什么是微服務

微服務架構是一種架構模式或者說是一種架構風格,它提倡將單一應用程序劃分為一組小的服務,每個服務運行在其獨立的自己的進程中,服務之間相互協調、互相配合,為用戶提供最終價值。服務之間采用輕量級的通信機制互相溝通(通常是基于HTTP的RESTful API),每個服務都圍繞著具體的業務進行構建,并且能夠被獨立的構建在生產環境、類生產環境等。另外,應避免統一的、集中式的服務管理機制,對具體的一個服務而言,應根據業務上下文,選擇合適的語言、工具對其進行構建,可以有一個非常輕量級的集中式管理來協調這些服務,可以使用不同的語言來編寫服務,也可以使用不同的數據存儲。

3、SpringCloud有什么優勢

使用 Spring Boot 開發分布式微服務時,我們面臨以下問題

(1)與分布式系統相關的復雜性-這種開銷包括網絡問題,延遲開銷,帶寬問題,安全問題。

(2)服務發現-服務發現工具管理群集中的流程和服務如何查找和互相交談。它涉及一個服務目錄,在該目錄中注冊服務,然后能夠查找并連接到該目錄中的服務。

(3)冗余-分布式系統中的冗余問題。

(4)負載平衡 --負載平衡改善跨多個計算資源的工作負荷,諸如計算機,計算機集群,網絡鏈路,中央處理單元,或磁盤驅動器的分布。

(5)性能-問題 由于各種運營開銷導致的性能問題。

(6)部署復雜性-Devops 技能的要求。

4、 什么是服務熔斷?什么是服務降級?

熔斷機制是應對雪崩效應的一種微服務鏈路保護機制。當某個微服務不可用或者響應時間太長時,會進行服務降級,進而熔斷該節點微服務的調用,快速返回“錯誤”的響應信息。當檢測到該節點微服務調用響應正常后恢復調用鏈路。在SpringCloud框架里熔斷機制通過Hystrix實現,Hystrix會監控微服務間調用的狀況,當失敗的調用到一定閾值,缺省是5秒內調用20次,如果失敗,就會啟動熔斷機制。

服務降級,一般是從整體負荷考慮。就是當某個服務熔斷之后,服務器將不再被調用,此時客戶端可以自己準備一個本地的fallback回調,返回一個缺省值。這樣做,雖然水平下降,但好歹可用,比直接掛掉強。

Hystrix相關注解
@EnableHystrix:開啟熔斷
@HystrixCommand(fallbackMethod=”XXX”):聲明一個失敗回滾處理函數XXX,當被注解的方法執行超時(默認是1000毫秒),就會執行fallback函數,返回錯誤提示。

a3e7daec2343b9188bfd745b7dfe0a93693.jpg

5、 Eureka和zookeeper都可以提供服務注冊與發現的功能,請說說兩個的區別?

Zookeeper保證了CP(C:一致性,P:分區容錯性),Eureka保證了AP(A:高可用)
1.當向注冊中心查詢服務列表時,我們可以容忍注冊中心返回的是幾分鐘以前的信息,但不能容忍直接down掉不可用。也就是說,服務注冊功能對高可用性要求比較高,但zk會出現這樣一種情況,當master節點因為網絡故障與其他節點失去聯系時,剩余節點會重新選leader。問題在于,選取leader時間過長,30 ~ 120s,且選取期間zk集群都不可用,這樣就會導致選取期間注冊服務癱瘓。在云部署的環境下,因網絡問題使得zk集群失去master節點是較大概率會發生的事,雖然服務能夠恢復,但是漫長的選取時間導致的注冊長期不可用是不能容忍的。

2.Eureka保證了可用性,Eureka各個節點是平等的,幾個節點掛掉不會影響正常節點的工作,剩余的節點仍然可以提供注冊和查詢服務。而Eureka的客戶端向某個Eureka注冊或發現時發生連接失敗,則會自動切換到其他節點,只要有一臺Eureka還在,就能保證注冊服務可用,只是查到的信息可能不是最新的。除此之外,Eureka還有自我保護機制,如果在15分鐘內超過85%的節點沒有正常的心跳,那么Eureka就認為客戶端與注冊中心發生了網絡故障,此時會出現以下幾種情況:
①、Eureka不在從注冊列表中移除因為長時間沒有收到心跳而應該過期的服務。
②、Eureka仍然能夠接受新服務的注冊和查詢請求,但是不會被同步到其他節點上(即保證當前節點仍然可用)
③、當網絡穩定時,當前實例新的注冊信息會被同步到其他節點。

因此,Eureka可以很好的應對因網絡故障導致部分節點失去聯系的情況,而不會像Zookeeper那樣使整個微服務癱瘓

6、SpringBoot和SpringCloud的區別?

SpringBoot專注于快速方便的開發單個個體微服務。

SpringCloud是關注全局的微服務協調整理治理框架,它將SpringBoot開發的一個個單體微服務整合并管理起來,

為各個微服務之間提供,配置管理、服務發現、斷路器、路由、微代理、事件總線、全局鎖、決策競選、分布式會話等等集成服務

SpringBoot可以離開SpringCloud獨立使用開發項目, 但是SpringCloud離不開SpringBoot ,屬于依賴的關系.

SpringBoot專注于快速、方便的開發單個微服務個體,SpringCloud關注全局的服務治理框架。

7、負載平衡的意義什么?

在計算中,負載平衡可以改善跨計算機,計算機集群,網絡鏈接,中央處理單元或磁盤驅動器等多種計算資源的工作負載分布。負載平衡旨在優化資源使用,最大化吞吐量,最小化響應時間并避免任何單一資源
的過載。使用多個組件進行負載平衡而不是單個組件可能會通過冗余來提高可靠性和可用性。負載平衡通常涉及專用軟件或硬件,例如多層交換機或域名系統服務器進程。

8、什么是Hystrix?它如何實現容錯?

Hystrix是一個延遲和容錯庫,旨在隔離遠程系統,服務和第三方庫的訪問點,當出現故障是不可避免的故障時,停止級聯故障并在復雜的分布式系統中實現彈性。

通常對于使用微服務架構開發的系統,涉及到許多微服務。這些微服務彼此協作。

思考以下微服務

img

假設如果上圖中的微服務9失敗了,那么使用傳統方法我們將傳播一個異常。但這仍然會導致整個系統崩潰。

隨著微服務數量的增加,這個問題變得更加復雜。微服務的數量可以高達1000.這是hystrix出現的地方 我們將使用Hystrix在這種情況下的Fallback方法功能。我們有兩個服務employee-consumer使用由employee-consumer公開的服務。

簡化圖如下所示

img

現在假設由于某種原因,employee-producer公開的服務會拋出異常。我們在這種情況下使用Hystrix定義了一個回退方法。這種后備方法應該具有與公開服務相同的返回類型。如果暴露服務中出現異常,則回退方法將返回一些值。

9、什么是Hystrix斷路器?我們需要它嗎?

由于某些原因,employee-consumer公開服務會引發異常。在這種情況下使用Hystrix我們定義了一個回退方法。如果在公開服務中發生異常,則回退方法返回一些默認值。

img

如果firstPage method() 中的異常繼續發生,則Hystrix電路將中斷,并且員工使用者將一起跳過firtsPage方法,并直接調用回退方法。 斷路器的目的是給第一頁方法或第一頁方法可能調用的其他方法留出時間,并導致異常恢復。可能發生的情況是,在負載較小的情況下,導致異常的問題有更好的恢復機會 。

img

10、說說 RPC 的實現原理

首先需要有處理網絡連接通訊的模塊,負責連接建立、管理和消息的傳輸。其次需要有編
解碼的模塊,因為網絡通訊都是傳輸的字節碼,需要將我們使用的對象序列化和反序列
化。剩下的就是客戶端和服務器端的部分,服務器端暴露要開放的服務接口,客戶調用服
務接口的一個代理實現,這個代理實現負責收集數據、編碼并傳輸給服務器然后等待結果
返回。

Nginx篇

1、簡述一下什么是Nginx,它有什么優勢和功能?

Nginx是一個web服務器和方向代理服務器,用于HTTP、HTTPS、SMTP、POP3和IMAP協議。因它的穩定性、豐富的功能集、示例配置文件和低系統資源的消耗而聞名。

Nginx—Ngine X,是一款免費的、自由的、開源的、高性能HTTP服務器和反向代理服務器;也是一個IMAP、POP3、SMTP代理服務器;Nginx以其高性能、穩定性、豐富的功能、簡單的配置和低資源消耗而聞名。

也就是說Nginx本身就可以托管網站(類似于Tomcat一樣),進行Http服務處理,也可以作為反向代理服務器 、負載均衡器和HTTP緩存。

Nginx 解決了服務器的C10K(就是在一秒之內連接客戶端的數目為10k即1萬)問題。它的設計不像傳統的服務器那樣使用線程處理請求,而是一個更加高級的機制—事件驅動機制,是一種異步事件驅動結構。

優點:
(1)更快
這表現在兩個方面:一方面,在正常情況下,單次請求會得到更快的響應;另一方面,在高峰期(如有數以萬計的并發請求),Nginx可以比其他Web服務器更快地響應請求。
(2)高擴展性,跨平臺
Nginx的設計極具擴展性,它完全是由多個不同功能、不同層次、不同類型且耦合度極低的模塊組成。因此,當對某一個模塊修復Bug或進行升級時,可以專注于模塊自身,無須在意其他。而且在HTTP模塊中,還設計了HTTP過濾器模塊:一個正常的HTTP模塊在處理完請求后,會有一串HTTP過濾器模塊對請求的結果進行再處理。這樣,當我們開發一個新的HTTP模塊時,不但可以使用諸如HTTP核心模塊、events模塊、log模塊等不同層次或者不同類型的模塊,還可以原封不動地復用大量已有的HTTP過濾器模塊。這種低耦合度的優秀設計,造就了Nginx龐大的第三方模塊,當然,公開的第三方模塊也如官方發布的模塊一樣容易使用。
Nginx的模塊都是嵌入到二進制文件中執行的,無論官方發布的模塊還是第三方模塊都是如此。這使得第三方模塊一樣具備極其優秀的性能,充分利用Nginx的高并發特性,因此,許多高流量的網站都傾向于開發符合自己業務特性的定制模塊。
(3)高可靠性:用于反向代理,宕機的概率微乎其微
高可靠性是我們選擇Nginx的最基本條件,因為Nginx的可靠性是大家有目共睹的,很多家高流量網站都在核心服務器上大規模使用Nginx。Nginx的高可靠性來自于其核心框架代碼的優秀設計、模塊設計的簡單性;另外,官方提供的常用模塊都非常穩定,每個worker進程相對獨立,master進程在1個worker進程出錯時可以快速“拉起”新的worker子進程提供服務。

(4)低內存消耗
一般情況下,10 000個非活躍的HTTP Keep-Alive連接在Nginx中僅消耗2.5MB的內存,這是Nginx支持高并發連接的基礎。
(5)單機支持10萬以上的并發連接
這是一個非常重要的特性!隨著互聯網的迅猛發展和互聯網用戶數量的成倍增長,各大公司、網站都需要應付海量并發請求,一個能夠在峰值期頂住10萬以上并發請求的Server,無疑會得到大家的青睞。理論上,Nginx支持的并發連接上限取決于內存,10萬遠未封頂。當然,能夠及時地處理更多的并發請求,是與業務特點緊密相關的。
(6)熱部署
master管理進程與worker工作進程的分離設計,使得Nginx能夠提供熱部署功能,即可以在7×24小時不間斷服務的前提下,升級Nginx的可執行文件。當然,它也支持不停止服務就更新配置項、更換日志文件等功能。
(7)最自由的BSD許可協議
這是Nginx可以快速發展的強大動力。BSD許可協議不只是允許用戶免費使用Nginx,它還允許用戶在自己的項目中直接使用或修改Nginx源碼,然后發布。這吸引了無數開發者繼續為Nginx貢獻自己的智慧。
以上7個特點當然不是Nginx的全部,擁有無數個官方功能模塊、第三方功能模塊使得Nginx能夠滿足絕大部分應用場景,這些功能模塊間可以疊加以實現更加強大、復雜的功能,有些模塊還支持Nginx與Perl、Lua等腳本語言集成工作,大大提高了開發效率。這些特點促使用戶在尋找一個Web服務器時更多考慮Nginx。
選擇Nginx的核心理由還是它能在支持高并發請求的同時保持高效的服務

2、Nginx是如何處理一個HTTP請求的呢?

Nginx 是一個高性能的 Web 服務器,能夠同時處理大量的并發請求。它結合多進程機制和異步機制 ,異步機制使用的是異步非阻塞方式 ,接下來就給大家介紹一下 Nginx 的多線程機制和異步非阻塞機制 。

1、多進程機制

服務器每當收到一個客戶端時,就有 服務器主進程 ( master process )生成一個 子進程( worker process )出來和客戶端建立連接進行交互,直到連接斷開,該子進程就結束了。

使用進程的好處是各個進程之間相互獨立,不需要加鎖,減少了使用鎖對性能造成影響,同時降低編程的復雜度,降低開發成本。其次,采用獨立的進程,可以讓進程互相之間不會影響 ,如果一個進程發生異常退出時,其它進程正常工作, master 進程則很快啟動新的 worker 進程,確保服務不會中斷,從而將風險降到最低。

缺點是操作系統生成一個子進程需要進行 內存復制等操作,在資源和時間上會產生一定的開銷。當有大量請求時,會導致系統性能下降 。

2、異步非阻塞機制

每個工作進程 使用 異步非阻塞方式 ,可以處理 多個客戶端請求 。

當某個 工作進程 接收到客戶端的請求以后,調用 IO 進行處理,如果不能立即得到結果,就去 處理其他請求 (即為 非阻塞 );而 客戶端 在此期間也 無需等待響應 ,可以去處理其他事情(即為 異步 )。

當 IO 返回時,就會通知此 工作進程 ;該進程得到通知,暫時 掛起 當前處理的事務去 響應客戶端請求 。

3、列舉一些Nginx的特性

  1. Nginx服務器的特性包括:
  2. 反向代理/L7負載均衡器
  3. 嵌入式Perl解釋器
  4. 動態二進制升級
  5. 可用于重新編寫URL,具有非常好的PCRE支持

4、請列舉Nginx和Apache 之間的不同點

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-PTgrX24M-1587954092090)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1583476168205.png)]

5、在Nginx中,如何使用未定義的服務器名稱來阻止處理請求?

只需將請求刪除的服務器就可以定義為:

Server{listen 80;server_name "";return 444;
}

這里,服務器名被保留為一個空字符串,它將在沒有“主機”頭字段的情況下匹配請求,而一個特殊的Nginx的非標準代碼444被返回,從而終止連接。

一般推薦 worker 進程數與CPU內核數一致,這樣一來不存在大量的子進程生成和管理任務,避免了進程之間競爭CPU 資源和進程切換的開銷。而且 Nginx 為了更好的利用 多核特性 ,提供了 CPU 親緣性的綁定選項,我們可以將某一個進程綁定在某一個核上,這樣就不會因為進程的切換帶來 Cache 的失效。

對于每個請求,有且只有一個工作進程 對其處理。首先,每個 worker 進程都是從 master進程 fork 過來。在 master 進程里面,先建立好需要 listen 的 socket(listenfd) 之后,然后再 fork 出多個 worker 進程。

所有 worker 進程的 listenfd 會在新連接到來時變得可讀 ,為保證只有一個進程處理該連接,所有 worker 進程在注冊 listenfd 讀事件前搶占 accept_mutex ,搶到互斥鎖的那個進程注冊 listenfd 讀事件 ,在讀事件里調用 accept 接受該連接。

當一個 worker 進程在 accept 這個連接之后,就開始讀取請求、解析請求、處理請求,產生數據后,再返回給客戶端 ,最后才斷開連接。這樣一個完整的請求就是這樣的了。我們可以看到,一個請求,完全由 worker 進程來處理,而且只在一個 worker 進程中處理。

640?

在 Nginx 服務器的運行過程中, 主進程和工作進程 需要進程交互。交互依賴于 Socket 實現的管道來實現。

6、請解釋Nginx服務器上的Master和Worker進程分別是什么?

  • 主程序 Master process 啟動后,通過一個 for 循環來 接收 和 處理外部信號 ;
  • 主進程通過 fork() 函數產生 worker 子進程 ,每個子進程執行一個 for循環來實現Nginx服務器對事件的接收和處理 。

640?

7、請解釋代理中的正向代理和反向代理

首先,代理服務器一般指局域網內部的機器通過代理服務器發送請求到互聯網上的服務器,代理服務器一般作用在客戶端。例如:GoAgent翻墻軟件。我們的客戶端在進行翻墻操作的時候,我們使用的正是正向代理,通過正向代理的方式,在我們的客戶端運行一個軟件,將我們的HTTP請求轉發到其他不同的服務器端,實現請求的分發。

640?

反向代理服務器作用在服務器端,它在服務器端接收客戶端的請求,然后將請求分發給具體的服務器進行處理,然后再將服務器的相應結果反饋給客戶端。Nginx就是一個反向代理服務器軟件。

640?

從上圖可以看出:客戶端必須設置正向代理服務器,當然前提是要知道正向代理服務器的IP地址,還有代理程序的端口。
反向代理正好與正向代理相反,對于客戶端而言代理服務器就像是原始服務器,并且客戶端不需要進行任何特別的設置。客戶端向反向代理的命名空間(name-space)中的內容發送普通請求,接著反向代理將判斷向何處(原始服務器)轉交請求,并將獲得的內容返回給客戶端。

640?

8、解釋Nginx用途

Nginx服務器的最佳用法是在網絡上部署動態HTTP內容,使用SCGI、WSGI應用程序服務器、用于腳本的FastCGI處理程序。它還可以作為負載均衡器。

MQ篇

1、為什么使用MQ

核心:解耦,異步,削峰

**1)解耦:**A 系統發送數據到 BCD 三個系統,通過接口調用發送。如果 E 系統也要這個數據呢?那如果 C 系統現在不需要了呢?A 系統負責人幾乎崩潰…A 系統跟其它各種亂七八糟的系統嚴重耦合,A 系統產生一條比較關鍵的數據,很多系統都需要 A 系統將這個數據發送過來。如果使用 MQ,A 系統產生一條數據,發送到 MQ 里面去,哪個系統需要數據自己去 MQ 里面消費。如果新系統需要數據,直接從 MQ 里消費即可;如果某個系統不需要這條數據了,就取消對 MQ 消息的消費即可。這樣下來,A 系統壓根兒不需要去考慮要給誰發送數據,不需要維護這個代碼,也不需要考慮人家是否調用成功、失敗超時等情況。

就是一個系統或者一個模塊,調用了多個系統或者模塊,互相之間的調用很復雜,維護起來很麻煩。但是其實這個調用是不需要直接同步調用接口的,如果用 MQ 給它異步化解耦。

**(2)異步:**A 系統接收一個請求,需要在自己本地寫庫,還需要在 BCD 三個系統寫庫,自己本地寫庫要 3ms,BCD 三個系統分別寫庫要 300ms、450ms、200ms。最終請求總延時是 3 + 300 + 450 + 200 = 953ms,接近 1s,用戶感覺搞個什么東西,慢死了慢死了。用戶通過瀏覽器發起請求。如果使用 MQ,那么 A 系統連續發送 3 條消息到 MQ 隊列中,假如耗時 5ms,A 系統從接受一個請求到返回響應給用戶,總時長是 3 + 5 = 8ms。

**(3)削峰:**減少高峰時期對服務器壓力。

2、MQ優缺點

優點上面已經說了,就是在特殊場景下有其對應的好處,解耦、異步、削峰。

缺點有以下幾個:

系統可用性降低
系統引入的外部依賴越多,越容易掛掉。萬一 MQ 掛了,MQ 一掛,整套系統崩潰,你不就完了?

系統復雜度提高
硬生生加個 MQ 進來,你怎么保證消息沒有重復消費?怎么處理消息丟失的情況?怎么保證消息傳遞的順序性?問題一大堆。

一致性問題
A 系統處理完了直接返回成功了,人都以為你這個請求就成功了;但是問題是,要是 BCD 三個系統那里,BD 兩個系統寫庫成功了,結果 C 系統寫庫失敗了,咋整?你這數據就不一致了。

3、Kafka、ActiveMQ、RabbitMQ、RocketMQ 都有什么區別?

對于吞吐量來說kafka和RocketMQ支撐高吞吐,ActiveMQ和RabbitMQ比他們低一個數量級。對于延遲量來說RabbitMQ是最低的。

1.從社區活躍度

按照目前網絡上的資料,RabbitMQ 、activeM 、ZeroMQ 三者中,綜合來看,RabbitMQ 是首選。

2.持久化消息比較

ActiveMq 和RabbitMq 都支持。持久化消息主要是指我們機器在不可抗力因素等情況下掛掉了,消息不會丟失的機制。

3.綜合技術實現

可靠性、靈活的路由、集群、事務、高可用的隊列、消息排序、問題追蹤、可視化管理工具、插件系統等等。

RabbitMq / Kafka 最好,ActiveMq 次之,ZeroMq 最差。當然ZeroMq 也可以做到,不過自己必須手動寫代碼實現,代碼量不小。尤其是可靠性中的:持久性、投遞確認、發布者證實和高可用性。

4.高并發

毋庸置疑,RabbitMQ 最高,原因是它的實現語言是天生具備高并發高可用的erlang 語言。

5.比較關注的比較, RabbitMQ 和 Kafka

RabbitMq 比Kafka 成熟,在可用性上,穩定性上,可靠性上, RabbitMq 勝于 Kafka (理論上)。

另外,Kafka 的定位主要在日志等方面, 因為Kafka 設計的初衷就是處理日志的,可以看做是一個日志(消息)系統一個重要組件,針對性很強,所以 如果業務方面還是建議選擇 RabbitMq 。

還有就是,Kafka 的性能(吞吐量、TPS )比RabbitMq 要高出來很多。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-oKnb2oih-1587954092114)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1583479010214.png)]

4、如何保證高可用的?

RabbitMQ 是比較有代表性的,因為是基于主從(非分布式)做高可用性的,我們就以 RabbitMQ 為例子講解第一種 MQ 的高可用性怎么實現。RabbitMQ 有三種模式:單機模式、普通集群模式、鏡像集群模式。

單機模式,就是 Demo 級別的,一般就是你本地啟動了玩玩兒的?,沒人生產用單機模式

普通集群模式,意思就是在多臺機器上啟動多個 RabbitMQ 實例,每個機器啟動一個。你創建的 queue,只會放在一個 RabbitMQ 實例上,但是每個實例都同步 queue 的元數據(元數據可以認為是 queue 的一些配置信息,通過元數據,可以找到 queue 所在實例)。你消費的時候,實際上如果連接到了另外一個實例,那么那個實例會從 queue 所在實例上拉取數據過來。這方案主要是提高吞吐量的,就是說讓集群中多個節點來服務某個 queue 的讀寫操作。

鏡像集群模式:這種模式,才是所謂的 RabbitMQ 的高可用模式。跟普通集群模式不一樣的是,在鏡像集群模式下,你創建的 queue,無論元數據還是 queue 里的消息都會存在于多個實例上,就是說,每個 RabbitMQ 節點都有這個 queue 的一個完整鏡像,包含 queue 的全部數據的意思。然后每次你寫消息到 queue 的時候,都會自動把消息同步到多個實例的 queue 上。RabbitMQ 有很好的管理控制臺,就是在后臺新增一個策略,這個策略是鏡像集群模式的策略,指定的時候是可以要求數據同步到所有節點的,也可以要求同步到指定數量的節點,再次創建 queue 的時候,應用這個策略,就會自動將數據同步到其他的節點上去了。這樣的話,好處在于,你任何一個機器宕機了,沒事兒,其它機器(節點)還包含了這個 queue 的完整數據,別的 consumer 都可以到其它節點上去消費數據。壞處在于,第一,這個性能開銷也太大了吧,消息需要同步到所有機器上,導致網絡帶寬壓力和消耗很重!RabbitMQ 一個 queue 的數據都是放在一個節點里的,鏡像集群下,也是每個節點都放這個 queue 的完整數據。

Kafka 一個最基本的架構認識:由多個 broker 組成,每個 broker 是一個節點;你創建一個 topic,這個 topic 可以劃分為多個 partition,每個 partition 可以存在于不同的 broker 上,每個 partition 就放一部分數據。這就是天然的分布式消息隊列,就是說一個 topic 的數據,是分散放在多個機器上的,每個機器就放一部分數據。Kafka 0.8 以后,提供了 HA 機制,就是 replica(復制品) 副本機制。每個 partition 的數據都會同步到其它機器上,形成自己的多個 replica 副本。所有 replica 會選舉一個 leader 出來,那么生產和消費都跟這個 leader 打交道,然后其他 replica 就是 follower。寫的時候,leader 會負責把數據同步到所有 follower 上去,讀的時候就直接讀 leader 上的數據即可。只能讀寫 leader?很簡單,要是你可以隨意讀寫每個 follower,那么就要 care 數據一致性的問題,系統復雜度太高,很容易出問題。Kafka 會均勻地將一個 partition 的所有 replica 分布在不同的機器上,這樣才可以提高容錯性。因為如果某個 broker 宕機了,沒事兒,那個 broker上面的 partition 在其他機器上都有副本的,如果這上面有某個 partition 的 leader,那么此時會從 follower 中重新選舉一個新的 leader 出來,大家繼續讀寫那個新的 leader 即可。這就有所謂的高可用性了。寫數據的時候,生產者就寫 leader,然后 leader 將數據落地寫本地磁盤,接著其他 follower 自己主動從 leader 來 pull 數據。一旦所有 follower 同步好數據了,就會發送 ack 給 leader,leader 收到所有 follower 的 ack 之后,就會返回寫成功的消息給生產者。(當然,這只是其中一種模式,還可以適當調整這個行為)消費的時候,只會從 leader 去讀,但是只有當一個消息已經被所有 follower 都同步成功返回 ack 的時候,這個消息才會被消費者讀到。

5、如何保證消息的可靠傳輸?如果消息丟了怎么辦

數據的丟失問題,可能出現在生產者、MQ、消費者中

生產者丟失:生產者將數據發送到 RabbitMQ 的時候,可能數據就在半路給搞丟了,因為網絡問題啥的,都有可能。此時可以選擇用 RabbitMQ 提供的事務功能,就是生產者發送數據之前開啟 RabbitMQ 事務channel.txSelect,然后發送消息,如果消息沒有成功被 RabbitMQ 接收到,那么生產者會收到異常報錯,此時就可以回滾事務channel.txRollback,然后重試發送消息;如果收到了消息,那么可以提交事務channel.txCommit。吞吐量會下來,因為太耗性能。所以一般來說,如果你要確保說寫 RabbitMQ 的消息別丟,可以開啟confirm模式,在生產者那里設置開啟confirm模式之后,你每次寫的消息都會分配一個唯一的 id,然后如果寫入了 RabbitMQ 中,RabbitMQ 會給你回傳一個ack消息,告訴你說這個消息 ok 了。如果 RabbitMQ 沒能處理這個消息,會回調你一個nack接口,告訴你這個消息接收失敗,你可以重試。而且你可以結合這個機制自己在內存里維護每個消息 id 的狀態,如果超過一定時間還沒接收到這個消息的回調,那么你可以重發。事務機制和cnofirm機制最大的不同在于,事務機制是同步的,你提交一個事務之后會阻塞在那兒,但是confirm機制是異步的,你發送個消息之后就可以發送下一個消息,然后那個消息RabbitMQ 接收了之后會異步回調你一個接口通知你這個消息接收到了。所以一般在生產者這塊避免數據丟失,都是用confirm機制的。

MQ中丟失:就是 RabbitMQ 自己弄丟了數據,這個你必須開啟 RabbitMQ 的持久化,就是消息寫入之后會持久化到磁盤,哪怕是 RabbitMQ 自己掛了,恢復之后會自動讀取之前存儲的數據,一般數據不會丟。設置持久化有兩個步驟:創建 queue 的時候將其設置為持久化,這樣就可以保證 RabbitMQ 持久化 queue 的元數據,但是不會持久化 queue 里的數據。第二個是發送消息的時候將消息的 deliveryMode 設置為 2,就是將消息設置為持久化的,此時 RabbitMQ 就會將消息持久化到磁盤上去。必須要同時設置這兩個持久化才行,RabbitMQ 哪怕是掛了,再次重啟,也會從磁盤上重啟恢復 queue,恢復這個 queue 里的數據。持久化可以跟生產者那邊的confirm機制配合起來,只有消息被持久化到磁盤之后,才會通知生產者ack了,所以哪怕是在持久化到磁盤之前,RabbitMQ 掛了,數據丟了,生產者收不到ack,你也是可以自己重發的。注意,哪怕是你給 RabbitMQ 開啟了持久化機制,也有一種可能,就是這個消息寫到了 RabbitMQ 中,但是還沒來得及持久化到磁盤上,結果不巧,此時 RabbitMQ 掛了,就會導致內存里的一點點數據丟失。

消費端丟失:你消費的時候,剛消費到,還沒處理,結果進程掛了,比如重啟了,那么就尷尬了,RabbitMQ 認為你都消費了,這數據就丟了。這個時候得用 RabbitMQ 提供的ack機制,簡單來說,就是你關閉 RabbitMQ 的自動ack,可以通過一個 api 來調用就行,然后每次你自己代碼里確保處理完的時候,再在程序里ack一把。這樣的話,如果你還沒處理完,不就沒有ack?那 RabbitMQ 就認為你還沒處理完,這個時候 RabbitMQ 會把這個消費分配給別的 consumer 去處理,消息是不會丟的。
img

6、如何保證消息的順序性

先看看順序會錯亂的場景:RabbitMQ:一個 queue,多個 consumer,這不明顯亂了;

img

解決:

img

7、 如何解決消息隊列的延時以及過期失效問題?消息隊列滿了以后該怎么處理?有幾百萬消息持續積壓幾小時,說說怎么解決?

消息積壓處理辦法:臨時緊急擴容:

先修復 consumer 的問題,確保其恢復消費速度,然后將現有 cnosumer 都停掉。
新建一個 topic,partition 是原來的 10 倍,臨時建立好原先 10 倍的 queue 數量。
然后寫一個臨時的分發數據的 consumer 程序,這個程序部署上去消費積壓的數據,消費之后不做耗時的處理,直接均勻輪詢寫入臨時建立好的 10 倍數量的 queue。
接著臨時征用 10 倍的機器來部署 consumer,每一批 consumer 消費一個臨時 queue 的數據。這種做法相當于是臨時將 queue 資源和 consumer 資源擴大 10 倍,以正常的 10 倍速度來消費數據。
等快速消費完積壓數據之后,得恢復原先部署的架構,重新用原先的 consumer 機器來消費消息。
MQ中消息失效:假設你用的是 RabbitMQ,RabbtiMQ 是可以設置過期時間的,也就是 TTL。如果消息在 queue 中積壓超過一定的時間就會被 RabbitMQ 給清理掉,這個數據就沒了。那這就是第二個坑了。這就不是說數據會大量積壓在 mq 里,而是大量的數據會直接搞丟。我們可以采取一個方案,就是批量重導,這個我們之前線上也有類似的場景干過。就是大量積壓的時候,我們當時就直接丟棄數據了,然后等過了高峰期以后,比如大家一起喝咖啡熬夜到晚上12點以后,用戶都睡覺了。這個時候我們就開始寫程序,將丟失的那批數據,寫個臨時程序,一點一點的查出來,然后重新灌入 mq 里面去,把白天丟的數據給他補回來。也只能是這樣了。假設 1 萬個訂單積壓在 mq 里面,沒有處理,其中 1000 個訂單都丟了,你只能手動寫程序把那 1000 個訂單給查出來,手動發到 mq 里去再補一次。

mq消息隊列塊滿了:如果消息積壓在 mq 里,你很長時間都沒有處理掉,此時導致 mq 都快寫滿了,咋辦?這個還有別的辦法嗎?沒有,誰讓你第一個方案執行的太慢了,你臨時寫程序,接入數據來消費,消費一個丟棄一個,都不要了,快速消費掉所有的消息。然后走第二個方案,到了晚上再補數據吧。

8、設計MQ的思路

比如說這個消息隊列系統,我們從以下幾個角度來考慮一下:

首先這個 mq 得支持可伸縮性吧,就是需要的時候快速擴容,就可以增加吞吐量和容量,那怎么搞?設計個分布式的系統唄,參照一下 kafka 的設計理念,broker -> topic -> partition,每個 partition 放一個機器,就存一部分數據。如果現在資源不夠了,簡單啊,給 topic 增加 partition,然后做數據遷移,增加機器,不就可以存放更多數據,提供更高的吞吐量了?

其次你得考慮一下這個 mq 的數據要不要落地磁盤吧?那肯定要了,落磁盤才能保證別進程掛了數據就丟了。那落磁盤的時候怎么落啊?順序寫,這樣就沒有磁盤隨機讀寫的尋址開銷,磁盤順序讀寫的性能是很高的,這就是 kafka 的思路。

其次你考慮一下你的 mq 的可用性啊?這個事兒,具體參考之前可用性那個環節講解的 kafka 的高可用保障機制。多副本 -> leader & follower -> broker 掛了重新選舉 leader 即可對外服務。

能不能支持數據 0 丟失啊?可以的,參考我們之前說的那個 kafka 數據零丟失方案。

數據結構與算法篇

1、常用的數據結構

原文:The top data structures you should know for your next coding interview

譯者:Fundebug

我們首先列出最常用的數據結構

  • 數組
  • 堆棧
  • 隊列
  • 鏈表
  • 字典樹
  • 哈希表
1. 數組

**數組(Array)**大概是最簡單,也是最常用的數據結構了。其他數據結構,比如棧和隊列都是由數組衍生出來的。

下圖展示了 1 個數組,它有 4 個元素:

img

每一個數組元素的位置由數字編號,稱為下標或者索引(index)。大多數編程語言的數組第一個元素的下標是 0。

根據維度區分,有 2 種不同的數組:

  • 一維數組(如上圖所示)
  • 多維數組(數組的元素為數組)

數組的基本操作

  • Insert - 在某個索引處插入元素
  • Get - 讀取某個索引處的元素
  • Delete - 刪除某個索引處的元素
  • Size - 獲取數組的長度

常見數組代碼面試題

  • 查找數組中第二小的元素:https://www.geeksforgeeks.org/to-find-smallest-and-second-smallest-element-in-an-array/
  • 查找第一個沒有重復的數組元素: https://www.geeksforgeeks.org/non-repeating-element/
  • 合并 2 個排序好的數組:https://www.geeksforgeeks.org/merge-two-sorted-arrays/
  • 重新排列數組中的正數和負數: https://www.geeksforgeeks.org/rearrange-positive-and-negative-numbers-publish/
2. 棧

撤回,即 Ctrl+Z,是我們最常見的操作之一,大多數應用都會支持這個功能。你知道它是怎么實現的嗎?答案是這樣的:把之前的應用狀態(限制個數)保存到內存中,最近的狀態放到第一個。這時,我們需要**棧(stack)**來實現這個功能。

棧中的元素采用 LIFO (Last In First Out),即后進先出

下圖的棧有 3 個元素,3 在最上面,因此它會被第一個移除:

img

棧的基本操作

  • Push?—? 在棧的最上方插入元素
  • Pop?— 返回棧最上方的元素,并將其刪除
  • isEmpty?—? 查詢棧是否為空
  • Top?—? 返回棧最上方的元素,并不刪除

常見的棧代碼面試題

  • 使用棧計算后綴表達式:https://www.geeksforgeeks.org/stack-set-4-evaluation-postfix-expression/
  • 使用棧為棧中的元素排序:https://www.geeksforgeeks.org/sort-stack-using-temporary-stack/
  • 檢查字符串中的括號是否匹配正確:https://www.geeksforgeeks.org/check-for-balanced-parentheses-in-an-expression/
3. 隊列

隊列(Queue)與棧類似,都是采用線性結構存儲數據。它們的區別在于,棧采用 LIFO 方式,而隊列采用先進先出,即FIFO(First in First Out)

下圖展示了一個隊列,1 是最上面的元素,它會被第一個移除:

img

隊列的基本操作

  • Enqueue?—? 在隊列末尾插入元素
  • Dequeue?—? 將隊列第一個元素刪除
  • isEmpty?—? 查詢隊列是否為空
  • Top?—? 返回隊列的第一個元素

常見的隊列代碼面試題

  • 使用隊列實現棧:https://www.geeksforgeeks.org/implement-stack-using-queue/
  • 倒轉隊列的前 K 個元素:https://www.geeksforgeeks.org/reversing-first-k-elements-queue/
  • 使用隊列將 1 到 n 轉換為二進制: https://www.geeksforgeeks.org/interesting-method-generate-binary-numbers-1-n/
4. 鏈表

**鏈表(Linked List)**也是線性結構,它與數組看起來非常像,但是它們的內存分配方式、內部結構和插入刪除操作方式都不一樣。

鏈表是一系列節點組成的鏈,每一個節點保存了數據以及指向下一個節點的指針。鏈表頭指針指向第一個節點,如果鏈表為空,則頭指針為空或者為 null。

鏈表可以用來實現文件系統、哈希表和鄰接表。

下圖展示了一個鏈表,它有 3 個節點:

img

鏈表分為 2 種:

  • 單向鏈表
  • 雙向鏈表

鏈表的基本操作

  • InsertAtEnd?—? 在鏈表結尾插入元素
  • InsertAtHead?—? 在鏈表開頭插入元素
  • Delete?—? 刪除鏈表的指定元素
  • DeleteAtHead?—? 刪除鏈表第一個元素
  • Search?—? 在鏈表中查詢指定元素
  • isEmpty?—? 查詢鏈表是否為空

常見的隊列代碼面試題

  • 倒轉 1 個鏈表:https://www.geeksforgeeks.org/reverse-a-linked-list/
  • 檢查鏈表中是否存在循環:https://www.geeksforgeeks.org/detect-loop-in-a-linked-list/
  • 返回鏈表倒數第 N 個元素:https://www.geeksforgeeks.org/nth-node-from-the-end-of-a-linked-list/
  • 移除鏈表中的重復元素:https://www.geeksforgeeks.org/remove-duplicates-from-an-unsorted-linked-list/
5. 圖

圖(graph)由多個節點(vertex)構成,節點之間闊以互相連接組成一個網絡。(x, y)表示一條邊(edge),它表示節點 x 與 y 相連。邊可能會有權值(weight/cost)

img

圖分為兩種:

  • 無向圖
  • 有向圖

在編程語言中,圖有可能有以下兩種形式表示:

  • 鄰接矩陣(Adjacency Matrix)
  • 鄰接表(Adjacency List)

遍歷圖有兩周算法

  • 廣度優先搜索(Breadth First Search)
  • 深度優先搜索(Depth First Search)

常見的圖代碼面試題

  • 實現廣度優先搜索: https://www.geeksforgeeks.org/breadth-first-search-or-bfs-for-a-graph/
  • 實現深度優先搜索: https://www.geeksforgeeks.org/depth-first-search-or-dfs-for-a-graph/
  • 檢查圖是否為樹: https://www.geeksforgeeks.org/check-given-graph-tree/
  • 統計圖中邊的個數:https://www.geeksforgeeks.org/count-number-edges-undirected-graph/
  • 使用 Dijkstra 算法查找兩個節點之間的最短距離: https://www.geeksforgeeks.org/dijkstras-shortest-path-algorithm-greedy-algo-7/
6. 樹

**樹(Tree)**是一個分層的數據結構,由節點和連接節點的邊組成。樹是一種特殊的圖,它與圖最大的區別是沒有循環。

樹被廣泛應用在人工智能和一些復雜算法中,用來提供高效的存儲結構。

下圖是一個簡單的樹以及與樹相關的術語:

img

樹有很多分類:

  • N 叉樹(N-ary Tree)
  • 平衡樹(Balanced Tree)
  • 二叉樹(Binary Tree)
  • 二叉查找樹(Binary Search Tree)
  • 平衡二叉樹(AVL Tree)
  • 紅黑樹(Red Black Tree)
  • 2-3 樹(2–3 Tree)

其中,二叉樹和二叉查找樹是最常用的樹。

常見的樹代碼面試題

  • 計算樹的高度:https://www.geeksforgeeks.org/write-a-c-program-to-find-the-maximum-depth-or-height-of-a-tree/
  • 查找二叉平衡樹中第 K 大的元素:https://www.geeksforgeeks.org/kth-largest-element-in-bst-when-modification-to-bst-is-not-allowed/
  • 查找樹中與根節點距離為 k 的節點:https://www.geeksforgeeks.org/print-nodes-at-k-distance-from-root/
  • 查找二叉樹中某個節點所有祖先節點:https://www.geeksforgeeks.org/print-ancestors-of-a-given-node-in-binary-tree/
7. 前綴樹

**前綴樹(Prefix Trees 或者 Trie)**與樹類似,用于處理字符串相關的問題時非常高效。它可以實現快速檢索,常用于字典中的單詞查詢,搜索引擎的自動補全甚至 IP 路由。

下圖展示了“top”, “thus”和“their”三個單詞在前綴樹中如何存儲的:

img

單詞是按照字母從上往下存儲,“p”, “s”和“r”節點分別表示“top”, “thus”和“their”的單詞結尾。

常見的樹代碼面試題

  • 統計前綴樹表示的單詞個數:https://www.geeksforgeeks.org/counting-number-words-trie/
  • 使用前綴樹為字符串數組排序: https://www.geeksforgeeks.org/sorting-array-strings-words-using-trie/
8. 哈希表

哈希(Hash)將某個對象變換為唯一標識符,該標識符通常用一個短的隨機字母和數字組成的字符串來代表。哈希可以用來實現各種數據結構,其中最常用的就是哈希表(hash table)

哈希表通常由數組實現。

哈希表的性能取決于 3 個指標:

  • 哈希函數
  • 哈希表的大小
  • 哈希沖突處理方式

下圖展示了有數組實現的哈希表,數組的下標即為哈希值,由哈希函數計算,作為哈希表的鍵(key),而數組中保存的數據即為值(value)

img

常見的哈希表代碼面試題

  • 查找數組中對稱的組合: https://www.geeksforgeseks.org/given-an-array-of-pairs-find-all-symmetric-pairs-in-it/
  • 確認某個數組的元素是否為另一個數組元素的子集: https://www.geeksforgeeks.org/find-whether-an-array-is-subset-of-another-array-set-1/
  • 確認給定的數組是否互斥: https://www.geeksforgeeks.org/check-two-given-sets-disjoint/

2、 數據里有{1,2,3,4,5,6,7,8,9},請隨機打亂順序,生成一個新的數組(請以代碼實現)

import java.util.Arrays;//打亂數組
public class Demo1 {//隨機打亂public static int[] srand(int[] a) {int[] b = new int[a.length];for(int i = 0; i < a.length;i++) {//隨機獲取下標int tmp = (int)(Math.random()*(a.length - i)); //隨機數:[ 0 ,a.length - i )  b[i] = a[tmp];//將此時a[tmp]的下標移動到靠后的位置int change = a[a.length - i - 1];a[a.length - i - 1] = a[tmp];a[tmp] = change;}return b;}public static void main(String[] args) {int[] a = {1,2,3,4,5,6,7,8,9};System.out.println(Arrays.toString(srand(a)));}
}

3、 寫出代碼判斷一個整數是不是2的階次方(請代碼實現,謝絕調用API方法)

import java.util.Scanner;//判斷整數是不是2的階次方
public class Demo2 {public static boolean check(int sum) {boolean flag = true; //判斷標志while(sum > 1) {if (sum % 2 == 0) {sum = sum/2;} else {flag = false;break;}}return flag;}public static void main(String[] args) {Scanner scanner = new Scanner(System.in);System.out.println("請輸入一個整數:");int sum = scanner.nextInt();System.out.println(sum + " 是不是2的階次方:" + check(sum));}
}

4、 假設今日是2015年3月1日,星期日,請算出13個月零6天后是星期幾,距離現在多少天(請用代碼實現,謝絕調用API方法)

import java.util.Scanner;//算出星期幾
public class Demo4 {public static String[] week = {"星期日","星期一","星期二","星期三","星期四","星期五","星期六"};public static int i = 0;public static int[] monthday1 = {0,31,28,31,30,31,30,31,31,30,31,30,31};public static int[] monthday2 = {0,31,29,31,30,31,30,31,31,30,31,30,31};//查看距離當前天數的差值public static String distance(int year,int month,int day,int newMonth,int newDay) {int sum = 0; //設定初始距離天數if (month + newMonth >= 12) {if (((year + 1) % 4 == 0 && (year + 1) % 100 != 0)||(year + 1) % 400 == 0) {sum += 366 + newDay;for(int i = 0;i < newMonth - 12;i++) {sum += monthday1[month + i];}} else {sum += 365 + newDay;for(int i = 0;i < newMonth - 12;i++) {sum += monthday1[month + i];}}} else {for(int i = 0;i < newMonth;i++) {sum += monthday1[month + i];}sum += newDay;}return week[sum%7];}public static void main(String[] args) {Scanner scanner = new Scanner(System.in);System.out.println("請輸入當前年份");int year = scanner.nextInt();System.out.println("請輸入當前月份");int month = scanner.nextInt();System.out.println("請輸入當前天數");int day = scanner.nextInt();System.out.println("請輸入當前是星期幾:以數字表示,如:星期天 為 0");int index = scanner.nextInt();System.out.println("今天是:" + year + "-" + month + "-" + day + "  " + week[index]);System.err.println("請輸入相隔月份");int newMonth = scanner.nextInt();System.out.println("請輸入剩余天數");int newDay = scanner.nextInt();System.out.println("經過" + newMonth + "月" + newDay + "天后,是" + distance(year,month,day,newMonth,newDay));}
}

5、 有兩個籃子,分別為A 和 B,籃子A里裝有雞蛋,籃子B里裝有蘋果,請用面向對象的思想實現兩個籃子里的物品交換(請用代碼實現)

//面向對象思想實現籃子物品交換
public class Demo5 {public static void main(String[] args) {//創建籃子Basket A = new Basket("A");Basket B = new Basket("B");//裝載物品A.load("雞蛋");B.load("蘋果");//交換物品A.change(B);A.show();B.show();}
}class Basket{public String name; //籃子名稱private Goods goods; //籃子中所裝物品public Basket(String name) {// TODO Auto-generated constructor stubthis.name = name;System.out.println(name + "籃子被創建");}//裝物品函數public void load(String name) {goods = new Goods(name);System.out.println(this.name + "裝載了" + name + "物品");}public void change(Basket B) {System.out.println(this.name + " 和 " + B.name + "中的物品發生了交換");String tmp = this.goods.getName();this.goods.setName(B.goods.getName());B.goods.setName(tmp);}public void show() {System.out.println(this.name + "中有" + goods.getName() + "物品");}
}class Goods{private String name; //物品名稱public String getName() {return name;}public void setName(String name) {this.name = name;}public Goods(String name) {// TODO Auto-generated constructor stubthis.name = name;}
}

6、更多算法練習

更多算法練習題,請訪問 https://leetcode-cn.com/problemset/algorithms/

Linux篇

1、 絕對路徑用什么符號表示?當前目錄、上層目錄用什么表示?主目錄用什么表示? 切換目錄用什么命令?

絕對路徑: 如/etc/init.d

當前目錄和上層目錄: ./ …/

主目錄: ~/

切換目錄: cd

2、 怎么查看當前進程?怎么執行退出?怎么查看當前路徑?

查看當前進程: ps

ps -l 列出與本次登錄有關的進程信息;
ps -aux 查詢內存中進程信息;
ps -aux | grep *** 查詢***進程的詳細信息;
top 查看內存中進程的動態信息;
kill -9 pid 殺死進程。

執行退出: exit

查看當前路徑: pwd

3、查看文件有哪些命令

vi 文件名 #編輯方式查看,可修改cat 文件名 #顯示全部文件內容more 文件名 #分頁顯示文件內容less 文件名 #與 more 相似,更好的是可以往前翻頁tail 文件名 #僅查看尾部,還可以指定行數head 文件名 #僅查看頭部,還可以指定行數

4、列舉幾個常用的Linux命令

  • 列出文件列表:ls【參數 -a -l】
  • 創建目錄和移除目錄:mkdir rmdir
  • 用于顯示文件后幾行內容:tail,例如: tail -n 1000:顯示最后1000行
  • 打包:tar -xvf
  • 打包并壓縮:tar -zcvf
  • 查找字符串:grep
  • 顯示當前所在目錄:pwd創建空文件:touch
  • 編輯器:vim vi

5、你平時是怎么查看日志的?

Linux查看日志的命令有多種: tail、cat、tac、head、echo等,本文只介紹幾種常用的方法。

1、tail

最常用的一種查看方式

命令格式: tail[必要參數][選擇參數][文件]

-f 循環讀取
-q 不顯示處理信息
-v 顯示詳細的處理信息
-c<數目> 顯示的字節數
-n<行數> 顯示行數
-q, --quiet, --silent 從不輸出給出文件名的首部
-s, --sleep-interval=S 與-f合用,表示在每次反復的間隔休眠S秒

例如:

tail -n 10 test.log 查詢日志尾部最后10行的日志;
tail -n +10 test.log 查詢10行之后的所有日志;
tail -fn 10 test.log 循環實時查看最后1000行記錄(最常用的)

一般還會配合著grep搜索用,例如 :

tail -fn 1000 test.log | grep '關鍵字'

如果一次性查詢的數據量太大,可以進行翻頁查看,例如:

tail -n 4700 aa.log |more -1000 可以進行多屏顯示(ctrl + f 或者 空格鍵可以快捷鍵)

2、head

跟tail是相反的head是看前多少行日志

head -n 10 test.log 查詢日志文件中的頭10行日志;
head -n -10 test.log 查詢日志文件除了最后10行的其他所有日志; 

head其他參數參考tail

3、cat

cat 是由第一行到最后一行連續顯示在屏幕上

一次顯示整個文件 :

$ cat filename

從鍵盤創建一個文件 :

$cat > filename

將幾個文件合并為一個文件:

$cat file1 file2 > file 只能創建新文件,不能編輯已有文件

將一個日志文件的內容追加到另外一個 :

$cat -n textfile1 > textfile2

清空一個日志文件:

$cat : >textfile2

注意:> 意思是創建,>>是追加。千萬不要弄混了。

cat其他參數參考tail

4、more

more命令是一個基于vi編輯器文本過濾器,它以全屏幕的方式按頁顯示文本文件的內容,支持vi中的關鍵字定位操作。more名單中內置了若干快捷鍵,常用的有H(獲得幫助信息),Enter(向下翻滾一行),空格(向下滾動一屏),Q(退出命令)。more命令從前向后讀取文件,因此在啟動時就加載整個文件。

該命令一次顯示一屏文本,滿屏后停下來,并且在屏幕的底部出現一個提示信息,給出至今己顯示的該文件的百分比:–More–(XX%)

  • more的語法:more 文件名
  • Enter 向下n行,需要定義,默認為1行
  • Ctrl f 向下滾動一屏
  • 空格鍵 向下滾動一屏
  • Ctrl b 返回上一屏
  • = 輸出當前行的行號
  • :f 輸出文件名和當前行的行號
  • v 調用vi編輯器
  • !命令 調用Shell,并執行命令
  • q退出more

5、sed

這個命令可以查找日志文件特定的一段 , 根據時間的一個范圍查詢,可以按照行號和時間范圍查詢

按照行號

sed -n '5,10p' filename 這樣你就可以只查看文件的第5行到第10行。

按照時間段

sed -n '/2014-12-17 16:17:20/,/2014-12-17 16:17:36/p' test.log

6、less

less命令在查詢日志時,一般流程是這樣的

less log.logshift + G 命令到文件尾部 然后輸入 ?加上你要搜索的關鍵字例如 ?1213按 n 向上查找關鍵字shift+n 反向查找關鍵字
less與more類似,使用less可以隨意瀏覽文件,而more僅能向前移動,不能向后移動,而且 less 在查看之前不會加載整個文件。
less log2013.log 查看文件
ps -ef | less ps查看進程信息并通過less分頁顯示
history | less 查看命令歷史使用記錄并通過less分頁顯示
less log2013.log log2014.log 瀏覽多個文件

常用命令參數:

less與more類似,使用less可以隨意瀏覽文件,而more僅能向前移動,不能向后移動,而且 less 在查看之前不會加載整個文件。
less log2013.log 查看文件
ps -ef | less ps查看進程信息并通過less分頁顯示
history | less 查看命令歷史使用記錄并通過less分頁顯示
less log2013.log log2014.log 瀏覽多個文件
常用命令參數:
-b <緩沖區大小> 設置緩沖區的大小
-g 只標志最后搜索的關鍵詞
-i 忽略搜索時的大小寫
-m 顯示類似more命令的百分比
-N 顯示每行的行號
-o <文件名> 將less 輸出的內容在指定文件中保存起來
-Q 不使用警告音
-s 顯示連續空行為一行
/字符串:向下搜索"字符串"的功能
?字符串:向上搜索"字符串"的功能
n:重復前一個搜索(與 / 或 ? 有關)
N:反向重復前一個搜索(與 / 或 ? 有關)
b 向后翻一頁
h 顯示幫助界面
q 退出less 命令

一般本人查日志配合應用的其他命令

history // 所有的歷史記錄history | grep XXX // 歷史記錄中包含某些指令的記錄history | more // 分頁查看記錄history -c // 清空所有的歷史記錄!! 重復執行上一個命令查詢出來記錄后選中 : !323

簡歷篇

原文: https://www.cnblogs.com/QQ12538552/p/12332620.html

本篇文章除了教大家用Markdown如何寫一份程序員專屬的簡歷,后面還會給大家推薦一些不錯的用來寫Markdown簡歷的軟件或者網站,以及如何優雅的將Markdown格式轉變為PDF格式或者其他格式。

推薦大家使用Markdown語法寫簡歷,然后再將Markdown格式轉換為PDF格式后進行簡歷投遞。

如果你對Markdown語法不太了解的話,可以花半個小時簡單看一下Markdown語法說明: http://www.markdown.cn 。

為什么說簡歷很重要?

一份好的簡歷可以在整個申請面試以及面試過程中起到非常好的作用。 在不夸大自己能力的情況下,寫出一份好的簡歷也是一項很棒的能力。為什么說簡歷很重要呢?

先從面試來說

假如你是網申,你的簡歷必然會經過HR的篩選,一張簡歷HR可能也就花費10秒鐘看一下,然后HR就會決定你這一關是Fail還是Pass。

假如你是內推,如果你的簡歷沒有什么優勢的話,就算是內推你的人再用心,也無能為力。

另外,就算你通過了篩選,后面的面試中,面試官也會根據你的簡歷來判斷你究竟是否值得他花費很多時間去面試。

所以,簡歷就像是我們的一個門面一樣,它在很大程度上決定了你能否進入到下一輪的面試中。

再從面試說起

我發現大家比較喜歡看面經 ,這點無可厚非,但是大部分面經都沒告訴你很多問題都是在特定條件下才問的。舉個簡單的例子:一般情況下你的簡歷上注明你會的東西才會被問到(Java、數據結構、網絡、算法這些基礎是每個人必問的),比如寫了你會 redis,那面試官就很大概率會問你 redis 的一些問題。比如:redis的常見數據類型及應用場景、redis是單線程為什么還這么快、 redis 和 memcached 的區別、redis 內存淘汰機制等等。

所以,首先,你要明確的一點是:你不會的東西就不要寫在簡歷上。另外,你要考慮你該如何才能讓你的亮點在簡歷中凸顯出來,比如:你在某某項目做了什么事情解決了什么問題(只要有項目就一定有要解決的問題)、你的某一個項目里使用了什么技術后整體性能和并發量提升了很多等等。

面試和工作是兩回事,聰明的人會把面試官往自己擅長的領域領,其他人則被面試官牽著鼻子走。雖說面試和工作是兩回事,但是你要想要獲得自己滿意的 offer ,你自身的實力必須要強。

必知必會的幾點

大部分公司的HR都說我們不看重學歷(騙你的!),但是如果你的學校不出眾的話,很難在一堆簡歷中脫穎而出,除非你的簡歷上有特別的亮點,比如:某某大廠的實習經歷、獲得了某某大賽的獎等等。

大部分應屆生找工作的硬傷是沒有工作經驗或實習經歷,所以如果你是應屆生就不要錯過秋招和春招。一旦錯過,你后面就極大可能會面臨社招,這個時候沒有工作經驗的你可能就會面臨各種碰壁,導致找不到一個好的工作

寫在簡歷上的東西一定要慎重,這是面試官大量提問的地方;

將自己的項目經歷完美的展示出來非常重要。

必須了解的兩大法則

STAR法則(Situation Task Action Result)

  • Situation: 事情是在什么情況下發生;
  • Task:: 你是如何明確你的任務的;
  • Action: 針對這樣的情況分析,你采用了什么行動方式;
  • Result: 結果怎樣,在這樣的情況下你學習到了什么。

簡而言之,STAR法則,就是一種講述自己故事的方式,或者說,是一個清晰、條理的作文模板。不管是什么,合理熟練運用此法則,可以輕松的對面試官描述事物的邏輯方式,表現出自己分析闡述問題的清晰性、條理性和邏輯性。

FAB 法則(Feature Advantage Benefit)

  • Feature: 是什么;
  • Advantage: 比別人好在哪些地方;
  • Benefit: 如果雇傭你,招聘方會得到什么好處。

簡單來說,這個法則主要是讓你的面試官知道你的優勢、招了你之后對公司有什么幫助。

項目經歷怎么寫

簡歷上有一兩個項目經歷很正常,但是真正能把項目經歷很好的展示給面試官的非常少。對于項目經歷大家可以考慮從如下幾點來寫:

  1. 對項目整體設計的一個感受
  2. 在這個項目中你負責了什么、做了什么、擔任了什么角色
  3. 從這個項目中你學會了那些東西,使用到了那些技術,學會了那些新技術的使用
  4. 另外項目描述中,最好可以體現自己的綜合素質,比如你是如何協調項目組成員協同開發的或者在遇到某一個棘手的問題的時候你是如何解決的又或者說你在這個項目用了什么技術實現了什么功能比如:用redis做緩存提高訪問速度和并發量、使用消息隊列削峰和降流等等。

專業技能怎么寫

先問一下你自己會什么,然后看看你意向的公司需要什么。一般HR可能并不太懂技術,所以他在篩選簡歷的時候可能就盯著你專業技能的關鍵詞來看。對于公司有要求而你不會的技能,你可以花幾天時間學習一下,然后在簡歷上可以寫上自己了解這個技能。比如你可以這樣寫(下面這部分內容摘自我的簡歷,大家可以根據自己的情況做一些修改和完善):

  • 計算機網絡、數據結構、算法、操作系統等課內基礎知識:掌握
  • Java 基礎知識:掌握
  • JVM 虛擬機(Java內存區域、虛擬機垃圾算法、虛擬垃圾收集器、JVM內存管理):掌握
  • 高并發、高可用、高性能系統開發:掌握
  • Struts2、Spring、Hibernate、Ajax、Mybatis、JQuery :掌握
  • SSH 整合、SSM 整合、 SOA 架構:掌握
  • Dubbo: 掌握
  • Zookeeper: 掌握
  • 常見消息隊列: 掌握
  • Linux:掌握
  • MySQL常見優化手段:掌握
  • Spring Boot +Spring Cloud +Docker:了解
  • Hadoop 生態相關技術中的 HDFS、Storm、MapReduce、Hive、Hbase :了解
  • Python 基礎、一些常見第三方庫比如OpenCV、wxpy、wordcloud、matplotlib:熟悉

排版注意事項

  1. 盡量簡潔,不要太花里胡哨;
  2. 一些技術名詞不要弄錯了大小寫比如MySQL不要寫成mysql,Java不要寫成java。這個在我看來還是比較忌諱的,所以一定要注意這個細節;
  3. 中文和數字英文之間加上空格的話看起來會舒服一點;

其他一些小tips

  1. 盡量避免主觀表述,少一點語義模糊的形容詞,盡量要簡潔明了,邏輯結構清晰。
  2. 如果自己有博客或者個人技術棧點的話,寫上去會為你加分很多。
  3. 如果自己的Github比較活躍的話,寫上去也會為你加分很多。
  4. 注意簡歷真實性,一定不要寫自己不會的東西,或者帶有欺騙性的內容
  5. 項目經歷建議以時間倒序排序,另外項目經歷不在于多,而在于有亮點。
  6. 如果內容過多的話,不需要非把內容壓縮到一頁,保持排版干凈整潔就可以了。
  7. 簡歷最后最好能加上:“感謝您花時間閱讀我的簡歷,期待能有機會和您共事。”這句話,顯的你會很有禮貌。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/450291.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/450291.shtml
英文地址,請注明出處:http://en.pswp.cn/news/450291.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

第四次軟件工程作業

關于 石墨文檔客戶端 的案例分析 作業地址&#xff1a; https://edu.cnblogs.com/campus/nenu/2016CS/homework/2505 第一部分 調研&#xff0c; 評測 1.下載并使用&#xff0c;按照描述的bug定義&#xff0c;找3~5個功能性的比較嚴重的bug。請用專業的語言描述&#xff08;每個…

深入剖析C++中的string類

一&#xff0c;C語言的字符串 在C語言里&#xff0c;對字符串的處理一項都是一件比較痛苦的事情&#xff0c;因為通常在實現字符串的操作的時候都會用到最不容易駕馭的類型——指針。 比如下面這個例子&#xff1a; //example 1: char str[12] "Hello"; char *…

Apple System: Error: ENFILE: file table overflow

2019獨角獸企業重金招聘Python工程師標準>>> 在MAC上跑nodejs&#xff0c;遇到了一個問題&#xff1a;file table overflow 主要意思就是說文件打開太多了&#xff0c;超過了限制&#xff0c;產生這個問題主要是蘋果操作系統的限制。 echo kern.maxfiles65536 | sud…

springboot的緩存技術

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 我門知道一個程序的瓶頸在于數據庫&#xff0c;我門也知道內存的速度是大大快于硬盤的速度的。當我門需要重復的獲取相同的數據的時候&a…

深度優先遍歷解決連通域求解問題-python實現

問題描述 在一個矩形網格中每一個格子的顏色或者為白色或者為黑色。任意或上、或下、或左、或右相鄰同為黑色的格子組成一個家族。家族中所有格子的數量反映家族的大小。要求找出最大家族的家族大小&#xff08;組成最大家族的格子的數量&#xff09;并統計出哪些點屬于哪一族。…

字符串進階

C風格字符串 1、字符串是用字符型數組存儲的&#xff0c;字符串要求其尾部以’\0’作為結束標志。如&#xff1a; char string[ ]”C programming language”; 用sizeof來測string長度為25個字節&#xff0c;而實際串本身長度(含空格)為24個字節&#xff0c;多出來的一個就是…

flask上傳excel文件,無須存儲,直接讀取內容

運行環境python3.6 import xlrd from flask import Flask, requestapp Flask(__name__)app.route("/", methods[POST, GET]) def filelist1():print(request.files)file request.files[file]print(file, type(file), file)print(file.filename) # 打印文件名f …

分布式 ID的 9 種生成方式

一、為什么要用分布式 ID&#xff1f; 在說分布式 ID 的具體實現之前&#xff0c;我們來簡單分析一下為什么用分布式 ID&#xff1f;分布式 ID 應該滿足哪些特征&#xff1f; 1、什么是分布式 ID&#xff1f; 拿 MySQL 數據庫舉個栗子&#xff1a; 在我們業務數據量不大的時…

spring boot Redis集成—RedisTemplate

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 Spring boot 基于Spring, Redis集成與Spring大同小異。 文章示例代碼均以前篇筆記為基礎增加修改&#xff0c;直接上代碼&#xff1a;…

QtCreator無法編輯源文件

在Qt Creator中新建工程&#xff0c;添加現有C源文件&#xff0c;有的源文件可以編輯&#xff0c;有的源文件編輯不了&#xff0c;發現無法編輯的源文件有一個共同特點&#xff0c;即其中都包含中文&#xff0c;且中文出現亂碼&#xff0c;于是&#xff0c;點擊Qt Creator菜單欄…

Unicode簡介和使用

一、Unicode簡介 在第一章中&#xff0c;我已經預告&#xff0c;C語言中在Microsoft Windows程序設計中扮演著重要角色的任何部分都會講述到&#xff0c;您也許在傳統文字模式程序設計中還尚未遇到過這些問題。寬字符集和Unicode差不多就是這樣的問題。 簡單地說&#xff0c;…

webpack4.x 模塊化淺析-CommonJS

先看下webpack官方文檔中對模塊的描述&#xff1a; 在模塊化編程中&#xff0c;開發者將程序分解成離散功能塊(discrete chunks of functionality)&#xff0c;并稱之為模塊。每個模塊具有比完整程序更小的接觸面&#xff0c;使得校驗、調試、測試輕而易舉。 精心編寫的模塊提供…

設計模式--抽象工廠(個人筆記)

一、抽象工廠的應用場景以及優缺點 1 應用場景&#xff1a; 如果系統需要多套的代碼解決方案&#xff0c;并且每套的代碼解決方案中又有很多相互關聯的產品類型&#xff0c;并且在系統中我們可以相互替換的使用一套產品的時候可以使用該模式&#xff0c;客戶端不需要依賴具體的…

利用阿里云OSS對文件進行存儲,上傳等操作

--pom.xml加入阿里OSS存儲依賴 <!--阿里云OSS存儲--> <dependency><groupId>com.aliyun.oss</groupId><artifactId>aliyun-sdk-oss</artifactId><version>2.8.3</version> </dependency> --配置阿里云oss相關常量參數 /…

Java并發編程之ThreadGroup

ThreadGroup是Java提供的一種對線程進行分組管理的手段&#xff0c;可以對所有線程以組為單位進行操作&#xff0c;如設置優先級、守護線程等。 線程組也有父子的概念&#xff0c;如下圖&#xff1a; 線程組的創建 1 public class ThreadGroupCreator {2 3 public static v…

springboot 緩存ehcache的簡單使用

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 步驟&#xff1a; 1. pom文件中加 maven jar包&#xff1a; <!-- ehcache 緩存 --><dependency><groupId>net.sf.eh…

Spring boot + mybatis plus 快速構建項目,生成基本業務操作代碼。

---進行業務建表&#xff0c;這邊根據個人業務分析&#xff0c;不具體操作 --加入mybatis plus pom依賴 <!-- mybatis-plus 3.0.5--> <dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId>&l…

給手機瀏覽器減負 輕裝上陣才能速度制勝

隨著手機瀏覽器的發展&#xff0c;瀏覽器已經變得臃腫不堪&#xff0c;各種“功能”系于一身&#xff0c;有廣告、社區、樂園等等&#xff0c;我們真的需要它們嗎&#xff1f;如何才能讓瀏覽器做到輕裝上陣&#xff0c;又能高效滿足我們需求呢&#xff1f; 過多“功能”的瀏覽器…

653. Two Sum IV - Input is a BST

題目來源&#xff1a; 自我感覺難度/真實難度&#xff1a; 題意&#xff1a; 分析&#xff1a; 自己的代碼&#xff1a; class Solution(object):def findTarget(self, root, k):""":type root: TreeNode:type k: int:rtype: bool"""Allself.InO…

解決 dubbo問題:Forbid consumer 192.xx.xx.1 access service com.xx.xx.xx.rpc.api.xx from registry 116.xx1

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 我的情況是&#xff1a; 原本我把服務放在A工程中&#xff0c;后來改到B工程中了&#xff0c;所以原來的服務不存在了&#xff0c;查不…