JDK8的新特性
1、default關鍵字:在java裏面,我們通常都是認爲接口裏面是只能有抽象方法,不能有任何方法的實現的,那麼在jdk1.8裏面打破了這個規定,引入了新的關鍵字default,通過使用default修飾方法,可以讓我們在接口裏面定義具體的方法實現,如下。
public interface NewCharacter {
public void test1();
public default void test2(){
System.out.println("我是新特性1");
}
}
其實這麼定義一個方法的主要意義是定義一個默認方法,也就是說這個接口的實現類實現了這個接口之後,不用管這個default修飾的方法,也可以直接調用,如下。
public class NewCharacterImpl implements NewCharacter{
@Override
public void test1() {
}
public static void main(String[] args) {
NewCharacter nca = new NewCharacterImpl();
nca.test2();
}
}
Lambda表達式是jdk1.8裏面的一個重要的更新,這意味着java也開始承認了函數式編程,並且嘗試引入其中。簡單的來說就是,函數也是一等公民了,在java裏面一等公民有變量,對象,那麼函數式編程語言裏面函數也可以跟變量,對象一樣使用了,也就是說函數既可以作爲參數,也可以作爲返回值了,看一下下面這個例子。
//這是常規的Collections的排序的寫法,需要對接口方法重寫
public void test1(){
List<String> list =Arrays.asList("aaa","fsa","ser","eere");
Collections.sort(list, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o2.compareTo(o1);
}
});
for (String string : list) {
System.out.println(string);
}
}
//這是帶參數類型的Lambda的寫法
public void testLamda1(){
List<String> list =Arrays.asList("aaa","fsa","ser","eere");
Collections.sort(list, (Comparator<? super String>) (String a,String b)->{
return b.compareTo(a);
}
);
for (String string : list) {
System.out.println(string);
}
}
//這是不帶參數的lambda的寫法
public void testLamda2(){
List<String> list =Arrays.asList("aaa","fsa","ser","eere");
Collections.sort(list, (a,b)->b.compareTo(a)
);
for (String string : list) {
System.out.println(string);
}
Date Api更新 :1.8之前JDK自帶的日期處理類非常不方便,我們處理的時候經常是使用的第三方工具包,比如commons-lang包等。不過1.8出現之後這個改觀了很多,比如日期時間的創建、比較、調整、格式化、時間間隔等。這些類都在java.time包下。比原來實用了很多。
流:定義:流是Java API的新成員,它允許我們以聲明性方式處理數據集合(我們可以把它們看成遍歷數據集的高級迭代器。此外,流還可以透明地並行處理,也就是說我們不用寫多線程代碼了。Stream 不是集合元素,它不是數據結構並不保存數據,它是有關算法和計算的,它更像一個高級版本的 Iterator。原始版本的 Iterator,用戶只能顯式地一個一個遍歷元素並對其執行某些操作;高級版本的 Stream,用戶只要給出需要對其包含的元素執行什麼操作,比如 “過濾掉長度大於 10 的字符串”、“獲取每個字符串的首字母”等,Stream 會隱式地在內部進行遍歷,做出相應的數據轉換。
// 1. Individual values
Stream stream = Stream.of("a", "b", "c");
// 2. Arrays
String [] strArray = new String[] {"a", "b", "c"};
stream = Stream.of(strArray);
stream = Arrays.stream(strArray);
// 3. Collections
List<String> list = Arrays.asList(strArray);
stream = list.stream();
SpringMVC中的組件,請求的處理過程。
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響應用戶
Spring的Controller的實現是:在整個spring的MVC啓動的時候將這個方法名加載到的handerAdapt當中去。
1掃描整個項目(Spring在啓動的時候就已經掃描好了)
2拿到所有的controller的註解類
3 遍歷類裏面所有的對象
4 判斷方法時候爲@RequestMapping的註解
5 把@ResquestMapping 註解的vale的值。作爲map集合的key給put進去。把menthod的方法對象作爲一個value的值
6根據用戶的發送請求 拿到請求中的的URi地址
7 通過uri作爲map中的key 去查看的有返回值
8通過反射來調用這個的方法對象。
講幾個Spring中的註解,@RequestMapping用在類上的作用,@ResponseBodyde的作用。
你可以使用@RequestMapping註解來將請求URL映射到整個類上。或某個特定的方法上,即@RequestMapping 既可以定義在類上,也可以定義方法上,一般來說,類級別的註解負責將一個特定(或符合某種模式)的請求,路徑映射到一個控制器上,同時通過方法級別的註解來細化映射,即根據特定的HTTP請求方法(GET、POST 方法等)、HTTP請求中是否攜帶特定參數等條件,將請求映射到匹配的方法上
@ResponseBody的作用其實是將java對象轉爲json格式的數據。
BeanFactory 和ApplicationContext區別。
是Spring裏面最低層的接口,提供了最簡單的容器的功能,只提供了實例化對象和拿對象的功能;
應用上下文,繼承BeanFactory接口,它是Spring的一各更高級的容器,提供了更多的有用的功能;
BeanFactory:BeanFactory在啓動的時候不會去實例化Bean,中有從容器中拿Bean的時候纔會去實例化;
ApplicationContext:ApplicationContext在啓動的時候就把所有的Bean全部實例化了。它還可以爲Bean配置lazy-init=true來讓Bean延遲實例化;
Spring Bean的作用域
設計模式。
工廠模式 單例模式 抽象工廠模式 雙親委派模式 觀察者模式 動態代理模式
觀察者模式在Spring中的應用。
觀察者模式實用範圍:一個事件觸發了一系列的事件。如果將這一系列的事件寫在一個方法裏面顯然不是一個最好的方式,最好的模式就是採用觀察者模式,將各個事件分而治之。
// 一段僞代碼展示常規寫法,請不要在意業務邏輯
// 缺點就是代碼高度耦合,將來一旦業務發生變更或者需要新增加一個事件都需要進行代碼變更
@RequestMapping(value="/buy.do")
public void buy(HttpServletRequest request, HttpServletResponse response){
//用戶購買成功
boolean bool=buy();
//發送郵件
sendEmail();
//發送短信
sendMsg();
}
以下是通過一個觀察者設計模式進行代碼變更:
- 建立一個事件觸發對象,比如登錄成功事件
- 建立一個消息傳遞通道,在spring中採用 applicationContext即可
- 建立一個監聽機制,監聽登錄事件。
@Autowired
ApplicationContext applicationContext;
@RequestMapping(value="/loginHome.do")
public void getLoginHome(HttpServletRequest request, HttpServletResponse response,
@RequestParam Map<String, String> paramsMap){
ShiroUser user = (ShiroUser)SecurityUtils.getSubject().getPrincipal();
try {
List list=userService.getUserRightList("32");
applicationContext.publishEvent(new UserLoginEvent(user));
JsonUtil.returnListJson(list,response);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
import org.springframework.context.ApplicationEvent;
public class UserLoginEvent extends ApplicationEvent{
public UserLoginEvent(Object source) {
super(source);
// TODO Auto-generated constructor stub
}
/**
*
*/
private static final long serialVersionUID = 1L;
}
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Service;
import com.github.xupei.simple.shiro.ShiroUser;
@Service
public class EmailListener implements ApplicationListener<UserLoginEvent>{
@Override
public void onApplicationEvent(UserLoginEvent event) {
// TODO Auto-generated method stub
ShiroUser user = (ShiroUser) event.getSource();
System.out.println(user.getLoginName()+"------------郵件");
}
}
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Service;
import com.github.xupei.simple.shiro.ShiroUser;
@Service
public class MsgListener implements ApplicationListener<UserLoginEvent>{
@Override
public void onApplicationEvent(UserLoginEvent event) {
// TODO Auto-generated method stub
ShiroUser user = (ShiroUser) event.getSource();
System.out.println(user.getLoginName()+"------------短信");
}
}
聊了挺久Oracle數據庫,和SQL優化。
字段:
儘量使用TINYINT、SMALLINT、MEDIUM_INT作爲整數類型而非INT,如果非負則加上UNSIGNED
VARCHAR的長度只分配真正需要的空間
使用枚舉或整數代替字符串類型
儘量使用TIMESTAMP而非DATETIME,
單表不要有太多字段,建議在20以內
避免使用NULL字段,很難查詢優化且佔用額外索引空間
用整型來存IP
索引:
索引並不是越多越好,要根據查詢有針對性的創建,考慮在WHERE和ORDER BY命令上涉及的列建立索引,可根據EXPLAIN來查看是否用了索引還是全表掃描
應儘量避免在WHERE子句中對字段進行NULL值判斷,否則將導致引擎放棄使用索引而進行全表掃描
值分佈很稀少的字段不適合建索引,例如"性別"這種只有兩三個值的字段
字符字段只建前綴索引
字符字段最好不要做主鍵
不用外鍵,由程序保證約束
儘量不用UNIQUE,由程序保證約束
使用多列索引時主意順序和查詢條件保持一致,同時刪除不必要的單列索引
查詢SQL
可通過開啓慢查詢日誌來找出較慢的SQL
不做列運算:SELECT id WHERE age + 1 = 10,任何對列的操作都將導致表掃描,它包括數據庫教程函數、計算表達式等等,查詢時要儘可能將操作移至等號右邊
sql語句儘可能簡單:一條sql只能在一個cpu運算;大語句拆小語句,減少鎖時間;一條大sql可以堵死整個庫
不用`SELECT *``
OR改寫成IN:OR的效率是n級別,IN的效率是log(n)級別,in的個數建議控制在200以內
不用函數和觸發器,在應用程序實現
避免%xxx式查詢
少用JOIN
使用同類型進行比較,比如用'123'和'123'比,123和123比
儘量避免在WHERE子句中使用!=或<>操作符,否則將引擎放棄使用索引而進行全表掃描
對於連續數值,使用BETWEEN不用IN:SELECT id FROM t WHERE num BETWEEN 1 AND 5
列表數據不要拿全表,要使用LIMIT來分頁,每頁數量也不要太大
引擎目前廣泛使用的是MyISAM和InnoDB兩種引擎:
MyISAM:MyISAM引擎是MySQL 5.1及之前版本的默認引擎,它的特點是:
1不支持行鎖,讀取時對需要讀到的所有表加鎖,寫入時則對錶加排它鎖
2不支持事務
3不支持外鍵
4不支持崩潰後的安全恢復
5在表有讀取查詢的同時,支持往表中插入新紀錄
6支持BLOB和TEXT的前500個字符索引,支持全文索引
7支持延遲更新索引,極大提升寫入性能
8對於不會進行修改的表,支持壓縮表,極大減少磁盤空間佔用
InnoDB:InnoDB在MySQL 5.5後成爲默認索引,它的特點是:
1支持行鎖,採用MVCC來支持高併發
2支持事務
3支持外鍵
4支持崩潰後的安全恢復
5不支持全文索引 總體來講,MyISAM適合SELECT密集型的表,而InnoDB適合INSERT和UPDATE密集型的表
升級硬件
Scale up,這個不多說了,根據MySQL是CPU密集型還是I/O密集型,通過提升CPU和內存、使用SSD,都能顯著提升MySQL性能
讀寫分離
也是目前常用的優化,從庫讀主庫寫,一般不要採用雙主或多主引入很多複雜性,儘量採用文中的其他方案來提高性能。同時目前很多拆分的解決方案同時也兼顧考慮了讀寫分離。
緩存
緩存可以發生在這些層次:
MySQL內部:在系統調優參數介紹了相關設置
數據訪問層:比如MyBatis針對SQL語句做緩存,而Hibernate可以精確到單個記錄,這裏緩存的對象主要是持久化對象Persistence Object
應用服務層:這裏可以通過編程手段對緩存做到更精準的控制和更多的實現策略,這裏緩存的對象是數據傳輸對象Data Transfer Object
Web層:針對web頁面做緩存
瀏覽器客戶端:用戶端的緩存
緩存實現,目前主要有兩種方式:
直寫式(Write Through):在數據寫入數據庫後,同時更新緩存,維持數據庫與緩存的一致性。這也是當前大多數應用緩存框架如Spring Cache的工作方式。這種實現非常簡單,同步好,但效率一般。
回寫式(Write Back):當有數據要寫入數據庫時,只會更新緩存,然後異步批量的將緩存數據同步到數據庫上。這種實現比較複雜,需要較多的應用邏輯,同時可能會產生數據庫與緩存的不同步,但效率非常高。
表分區
MySQL在5.1版引入的分區是一種簡單的水平拆分,用戶需要在建表的時候加上分區參數,對應用是透明的無需修改代碼。
對用戶來說,分區表是一個獨立的邏輯表,但是底層由多個物理子表組成,實現分區的代碼實際上是通過對一組底層表的對象封裝,但對SQL層來說是一個完全封裝底層的黑盒子。MySQL實現分區的方式也意味着索引也是按照分區的子表定義,沒有全局索引。
用戶的SQL語句是需要針對分區表做優化,SQL條件中要帶上分區條件的列,從而使查詢定位到少量的分區上,否則就會掃描全部分區,可以通過EXPLAIN PARTITIONS來查看某條SQL語句會落在那些分區上,從而進行SQL優化,如下圖5條記錄落在兩個分區上:
分區的好處是:
可以讓單表存儲更多的數據
分區表的數據更容易維護,可以通過清楚整個分區批量刪除大量數據,也可以增加新的分區來支持新插入的數據。另外,還可以對一個獨立分區進行優化、檢查、修復等操作
部分查詢能夠從查詢條件確定只落在少數分區上,速度會很快
分區表的數據還可以分佈在不同的物理設備上,從而利用多個硬件設備
可以使用分區表賴避免某些特殊瓶頸,例如InnoDB單個索引的互斥訪問、ext3文件系統的inode鎖競爭
可以備份和恢復單個分區
分區的限制和缺點:
一個表最多只能有1024個分區
如果分區字段中有主鍵或者唯一索引的列,那麼所有主鍵列和唯一索引列都必須包含進來
分區表無法使用外鍵約束
NULL值會使分區過濾無效
所有分區必須使用相同的存儲引擎
垂直拆分
垂直分庫是根據數據庫裏面的數據表的相關性進行拆分,比如:一個數據庫裏面既存在用戶數據,又存在訂單數據,那麼垂直拆分可以把用戶數據放到用戶庫、把訂單數據放到訂單庫。垂直分表是對數據表進行垂直拆分的一種方式,常見的是把一個多字段的大表按常用字段和非常用字段進行拆分,每個表裏面的數據記錄數一般情況下是相同的,只是字段不一樣,使用主鍵關聯
垂直拆分的優點是:
可以使得行數據變小,一個數據塊(Block)就能存放更多的數據,在查詢時就會減少I/O次數(每次查詢時讀取的Block 就少)
可以達到最大化利用Cache的目的,具體在垂直拆分的時候可以將不常變的字段放一起,將經常改變的放一起
數據維護簡單
垂直拆分缺點是:
主鍵出現冗餘,需要管理冗餘列
會引起表連接JOIN操作(增加CPU開銷)可以通過在業務服務器上進行join減少數據庫壓力
依然存在單表數據量過大的問題(需要水平拆分)
事務處理複雜
水平拆分
水平拆分是通過某種策略將數據分片來存儲,分庫內分表和分庫兩部分,每片數據會分散到不同的MySQL表或庫,達到分佈式的效果,能夠支持非常大的數據量。前面的表分區本質上也是一種特殊的庫內分表 庫內分表,僅僅是單純的解決了單一表數據過大的問題,由於沒有把表的數據分佈到不同的機器上,因此對於減輕MySQL服務器的壓力來說,並沒有太大的作用,大家還是競爭同一個物理機上的IO、CPU、網絡,這個就要通過分庫來解決
水平拆分的優點是:
不存在單庫大數據和高併發的性能瓶頸
應用端改造較少
提高了系統的穩定性和負載能力
缺點是:
分片事務一致性難以解決
跨節點Join性能差,邏輯複雜
數據多次擴展難度跟維護量極大
分片原則
能不分就不分,參考單表優化
分片數量儘量少,分片儘量均勻分佈在多個數據結點上,因爲一個查詢SQL跨分片越多,則總體性能越差,雖然要好於所有數據在一個分片的結果,在必要的時候進行擴容,增加分片數量
分片規則需要慎重選擇做好提前規劃,分片規則的選擇,需要考慮數據的增長模式,數據的訪問模式,分片關聯性問題,以及分片擴容問題,最近的分片策略爲範圍分片,枚舉分片,一致性Hash分片,這幾種分片都有利於擴容
儘量不要在一個事務中的SQL跨越多個分片,分佈式事務一直是個不好處理的問題
查詢條件儘量優化,儘量避免Select * 的方式,大量數據結果集下,會消耗大量帶寬和CPU資源,查詢儘量避免返回大量結果集,並且儘量爲頻繁使用的查詢語句建立索引。
通過數據冗餘和表分區賴降低跨庫Join的可能。
這裏特別強調一下分片規則的選擇問題,如果某個表的數據有明顯的時間特徵,比如訂單、交易記錄等,則他們通常比較合適用時間範圍分片,因爲具有時效性的數據,我們往往關注其近期的數據,查詢條件中往往帶有時間字段進行過濾,比較好的方案是,當前活躍的數據,採用跨度比較短的時間段進行分片,而歷史性的數據,則採用比較長的跨度存儲。
總體上來說,分片的選擇是取決於最頻繁的查詢SQL的條件,因爲不帶任何Where語句的查詢SQL,會遍歷所有的分片,性能相對最差,因此這種SQL越多,對系統的影響越大,所以我們要儘量避免這種SQL的產生。
解決方案
由於水平拆分牽涉的邏輯比較複雜,當前也有了不少比較成熟的解決方案。這些方案分爲兩大類:客戶端架構和代理架構。
客戶端架構。通過修改數據訪問層,如JDBC、Data Source、MyBatis,通過配置來管理多個數據源,直連數據庫,並在模塊內完成數據的分片整合,一般以Jar包的方式呈現 這是一個客戶端架構的例子:
可以看到分片的實現是和應用服務器在一起的,通過修改Spring JDBC層來實現
客戶端架構的優點是:
應用直連數據庫,降低外圍系統依賴所帶來的宕機風險
集成成本低,無需額外運維的組件
缺點是:
限於只能在數據庫訪問層上做文章,擴展性一般,對於比較複雜的系統可能會力不從心
將分片邏輯的壓力放在應用服務器上,造成額外風險
代理架構
通過獨立的中間件來統一管理所有數據源和數據分片整合,後端數據庫集羣對前端應用程序透明,需要獨立部署和運維代理組件
代理組件爲了分流和防止單點,一般以集羣形式存在,同時可能需要Zookeeper之類的服務組件來管理
代理架構的優點是:
能夠處理非常複雜的需求,不受數據庫訪問層原來實現的限制,擴展性強
對於應用服務器透明且沒有增加任何額外負載
缺點是:
需部署和運維獨立的代理中間件,成本高
應用需經過代理來連接數據庫,網絡上多了一跳,性能有損失且有額外風險。
Object類裏有哪些方法。
wait和sleep的區別,是否需要捕獲異常。
1. sleep和wait都是用來進行線程控制,他們最大本質的區別是:
sleep()不釋放同步鎖,wait()釋放同步鎖.
sleep(milliseconds)可以用時間指定來使他自動醒過來,如果時間不到你只能調用interreput()來強行打斷;
wait()可以用notify()直接喚起.
sleep的作用是讓線程休眠制定的時間,在時間到達時恢復,也就是說sleep將在接到時間到達事件事恢復線程 執行,例如:
try{
System.out.println("I'm going to bed");
Thread.sleep(1000);
System.out.println("I wake up");
}
catch(IntrruptedException e) {
}
wait是Object的方法,也就是說可以對任意一個對象調用wait方法,調用wait方法將會將調用者的線程掛起,直到 其他線程調用同一個對象的notify方法纔會重新激活調用者,例如:
try{
obj.wait }
catch(InterrputedException e) {
}
3. sleep()是讓某個線程暫停運行一段時間,其控制範圍是由當前線程決定, wait()是由某個確定的對象來調用的。
sleep和wait的區別有:
1,這兩個方法來自不同的類分別是Thread和Object
2,最主要是sleep方法沒有釋放鎖,而wait方法釋放了鎖,使得其他線程可以使用同步控制塊或者方法。
3,wait,notify和notifyAll只能在同步控制方法或者同步控制塊裏面使用,而sleep可以在
任何地方使用
synchronized(x){
x.notify()
//或者wait()
}
4,sleep必須捕獲異常,而wait,notify和notifyAll不需要捕獲異常
HashMap、HashTable、ConcurrentHashMap區別。
hashmap是線程不安全的 JDK1.7是採用的是數組加鏈表1.8採用的是數組+鏈表+紅黑樹
- 底層數組+鏈表實現,可以存儲null鍵和null值,線程不安全
- 初始size爲16,擴容:newsize = oldsize*2,size一定爲2的n次冪
- 擴容針對整個Map,每次擴容時,原來數組中的元素依次重新計算存放位置,並重新插入
- 插入元素後才判斷該不該擴容,有可能無效擴容(插入後如果擴容,如果沒有再次插入,就會產生無效擴容)
- 當Map中元素總數超過Entry數組的75%,觸發擴容操作,爲了減少鏈表長度,元素分配更均勻
- 計算index方法:index = hash & (tab.length – 1)
hashtable是線程安全的採用是數組+鏈表 但是採用了synchronize的關鍵字保證了線程安全
- 底層數組+鏈表實現,無論key還是value都不能爲null,線程安全,實現線程安全的方式是在修改數據時鎖住整個HashTable,效率低,ConcurrentHashMap做了相關優化
- 初始size爲11,擴容:newsize = olesize*2+1
- 計算index的方法:index = (hash & 0x7FFFFFFF) % tab.length
Concurrenthashmap是的採用的分段鎖的機制實現線程安全的。
- 底層採用分段的數組+鏈表實現,線程安全
- 通過把整個Map分爲N個Segment,可以提供相同的線程安全,但是效率提升N倍,默認提升16倍。(讀操作不加鎖,由於HashEntry的value變量是 volatile的,也能保證讀取到最新的值。)
- Hashtable的synchronized是針對整張Hash表的,即每次鎖住整張表讓線程獨佔,ConcurrentHashMap允許多個修改操作併發進行,其關鍵在於使用了鎖分離技術
- 有些方法需要跨段,比如size()和containsValue(),它們可能需要鎖定整個表而而不僅僅是某個段,這需要按順序鎖定所有段,操作完畢後,又按順序釋放所有段的鎖
- 擴容:段內擴容(段內元素超過該段對應Entry數組長度的75%觸發擴容,不會對整個Map進行擴容),插入前檢測需不需要擴容,有效避免無效擴容
鎖分段技術:首先將數據分成一段一段的存儲,然後給每一段數據配一把鎖,當一個線程佔用鎖訪問其中一個段數據的時候,其他段的數據也能被其他線程訪問。
ConcurrentHashMap提供了與Hashtable和SynchronizedMap不同的鎖機制。Hashtable中採用的鎖機制是一次鎖住整個hash表,從而在同一時刻只能由一個線程對其進行操作;而ConcurrentHashMap中則是一次鎖住一個桶。
ConcurrentHashMap默認將hash表分爲16個桶,諸如get、put、remove等常用操作只鎖住當前需要用到的桶。這樣,原來只能一個線程進入,現在卻能同時有16個寫線程執行,併發性能的提升是顯而易見的。