一、前情提要
今天請假面試,上午兩家,下午三家(暫定兩點鐘A、三點半B、四點C),全軍出擊的趕腳,有一家公司感覺還可以,來這家面試還真是一波三折:
① 一個不認識的獵頭推薦的C(今天上午11點給我打的電話),我還覺得挺靠譜的;
② 本來約的是下午四點,結果因為下午兩點的面試A結束的比較早,離這家公司還比較近,我就賤呲呲的兩點半就到了樓下;
③ 到樓下了,這個不靠譜的獵頭連公司具體的樓層房間号都沒給我發,隻給了我一個電話(電話打了兩遍還打不通,因為人家在開會);
④ 我都已經走了,因為找不到公司具體位置,結果獵頭聯系公司的人,下來接我;
⑤上樓,等面試,半個小時過去了,仍然在開會;
⑥ 三點半的面試B,我看好的那個,馬上就要到點了,我就擅自離開了,去參加了我以為靠譜的B,結果在B的面試中,一問三不知,(【原理篇】);
⑦ 此時,電話震動了,C開完會了,我心灰意冷的來到了C公司,面試的人很親切,我一點都沒緊張,聊了聊公司的情況,技術問題也沒問太多,我覺得這個上司挺好的,希望能成。
流水賬就記到這,不是專業的,不想看的可以略過,勿噴。
沒通過,白期待了!我有情,他無意!哎...
二、cookie、session、token
1、session機制
session是服務端存儲的一個對象,主要用來存儲所有通路過該服務端的用戶端的使用者資訊(也可以存儲其他資訊),進而實作保持使用者會話狀态。但是伺服器重新開機時,記憶體會被銷毀,存儲的使用者資訊也就消失了。
不同的使用者通路服務端的時候會在session對象中存儲鍵值對,“鍵”用來存儲開啟這個使用者資訊的“鑰匙”,在登入成功後,“鑰匙”通過cookie傳回給用戶端,用戶端存儲為sessionId記錄在cookie中。當用戶端再次通路時,會預設攜帶cookie中的sessionId來實作會話機制。
(1)session是基于cookie的。
- cookie的資料4k左右;
- cookie存儲資料的格式:字元串key=value
- cookie存儲有效期:可以自行通過expires進行具體的日期設定,如果沒設定,預設是關閉浏覽器時失效。
- cookie有效範圍:目前域名下有效。是以session這種會話存儲方式方式隻适用于用戶端代碼和服務端代碼運作在同一台伺服器上(前後端項目協定、域名、端口号都一緻,即在一個項目下)
(2)session持久化
用于解決重新開機伺服器後session消失的問題。在資料庫中存儲session,而不是存儲在記憶體中。通過包:express-mysql-session。
當用戶端存儲的cookie失效後,服務端的session不會立即銷毀,會有一個延時,服務端會定期清理無效session,不會造成無效資料占用存儲空間的問題。
2、token機制
适用于前後端分離的項目(前後端代碼運作在不同的伺服器下)
請求登入時,token和sessionid原理相同,是對key和key對應的使用者資訊進行加密後的加密字元,登入成功後,會在響應主體中将{token:“字元串”}傳回給用戶端。
用戶端通過cookie都可以進行存儲。再次請求時不會預設攜帶,需要在請求攔截器位置給請求頭中添加認證字段Authorization攜帶token資訊,伺服器就可以通過token資訊查找使用者登入狀态。
三、如何設計資料庫
1、資料庫設計最起碼要占用這個項目開發的40%以上的時間
2、資料庫設計不僅僅停留在頁面demo的表面
頁面内容所需字段,在資料庫設計中隻是一部分,還有系統運轉、子產品互動、中轉資料、表之間的聯系等等所需要的字段,是以資料庫設計絕對不是簡單的基本資料存儲,還有邏輯資料存儲。
3、資料庫設計完成後,項目80%的設計開發都要存在你的腦海中
每個字段的設計都要有他存在的意義,要清楚的知道程式中如何去運用這些字段,多張表的聯系在程式中是如何展現的。
4、資料庫設計時就要考慮效率和優化問題
資料量大的表示粗粒度的,會備援一些必要字段,達到用最少的表,最弱的表關系去存儲海量的資料。大資料的表要建立索引,友善查詢。對于含有計算、資料互動、統計這類需求時,還有考慮是否有必要采用存儲過程。
5、添加必要的備援字段
像建立時間、修改時間、操作使用者IP、備注這些字段,在每張表中最好都有,一些備援的字段便于日後維護、分析、拓展而添加。
6、設計合理的表關聯
若兩張表之間的關系複雜,建議采用第三張映射表來關聯維護兩張表之間的關系,以降低表之間的直接耦合度。
7、設計表時不加主外鍵等限制關聯,系統編碼階段完成後再添加限制性關聯
8、選擇合适的主鍵生成政策
資料庫的設計難度其實比單純的技術實作難很多,他充分展現了一個人的全局設計能力和掌控能力,最後說一句,資料庫設計,很重要,很複雜。
四、性别是否适合做索引
區分度不高的字段不适合做索引,因為索引頁是需要有開銷的,需要存儲的,不過這類字段可以做聯合索引的一部分。
五、如何查詢重複的資料
1、查詢重複的單個字段(group by)
select 重複字段A, count(*) from 表 group by 重複字段A having count(*) > 1
2、查詢重複的多個字段(group by)
select 重複字段A, 重複字段B, count(*) from 表 group by 重複字段A, 重複字段B having count(*) > 1
3、删除所有重複的資料
-- 慎重考慮後執行,後悔記得及時復原。
delete from table group by 重複字段 having count(重複字段) > 1
六、查詢網站線上人數
通過監聽session對象的方式來實作線上人數的統計和線上人資訊展示,并且讓逾時的自動銷毀。
對session對象實作監聽,首先必須繼承HttpSessionListener類,該程式的基本原理就是當浏覽器通路頁面的時候必定會産生一個session對象,當關閉該頁面的時候必然會删除session對象。是以每當産生一個新的session對象就讓線上人數+1,當删除一個session對象就讓線上人數-1。
還要繼承一個HttpSessionAttributeListener,來實作對其屬性的監聽。分别實作attributeAdded方法,attributeReplace方法以及attributeRemove方法。
sessionCreated//建立一個會話的時候觸發,也可以說是用戶端第一次喝伺服器互動時觸發。
sessionDestroyed//銷毀會話的時候,一般來說隻有某個按鈕觸發進行銷毀,或者配置定時銷毀。
HttpSessionAttributeListener有三個方法需要實作
- attributeAdded//在session中添加對象時觸發此操作 籠統的說就是調用setAttribute這個方法時候會觸發的
- attributeRemoved//修改、删除session中添加對象時觸發此操作 籠統的說就是調用 removeAttribute這個方法時候會觸發的
- attributeReplaced//在Session屬性被重新設定時
七、Redis取值存值問題
1、先把Redis的連接配接池拿出來
JedisPool pool = new JedisPool(new JedisPoolConfig(),"127.0.0.1");
Jedis jedis = pool.getResource();
2、存取值
jedis.set("key","value");
jedis.get("key");
jedis.del("key");
//給一個key疊加value
jedis.append("key","value2");//此時key的值就是value + value2;
//同時給多個key進行指派:
jedis.mset("key1","value1","key2","value2");
3、對map進行操作
Map<String,String> user = new HashMap();
user.put("key1","value1");
user.put("key2","value2");
user.put("key3","value3");
//存入
jedis.hmset("user",user);
//取出user中key1
List<String> nameMap = jedis.hmget("user","key1");
//删除其中一個鍵值
jedis.hdel("user","key2");
//是否存在一個鍵
jedis.exists("user");
//取出所有的Map中的值:
Iterator<String> iter = jedis.hkeys("user").iterator();
while(iter.next()){
jedis.hmget("user",iter.next());
}
八、Spring Boot常用注解
Spring Boot常用注解(絕對經典)
九、mybatis一級緩存、二級緩存
1、一級緩存:指的是mybatis中sqlSession對象的緩存,當我們執行查詢以後,查詢的結果會同時存入sqlSession中,再次查詢的時候,先去sqlSession中查詢,有的話直接拿出,當sqlSession消失時,mybatis的一級緩存也就消失了,當調用sqlSession的修改、添加、删除、commit()、close()等方法時,會清空一級緩存。
2、二級緩存:指的是mybatis中的sqlSessionFactory對象的緩存,由同一個sqlSessionFactory對象建立的sqlSession共享其緩存,但是其中緩存的是資料而不是對象。當命中二級緩存時,通過存儲的資料構造成對象傳回。查詢資料的時候,查詢的流程是二級緩存 > 一級緩存 > 資料庫。
3、如果開啟了二級緩存,sqlSession進行close()後,才會把sqlSession一級緩存中的資料添加到二級緩存中,為了将緩存資料取出執行反序列化,還需要将要緩存的pojo實作Serializable接口,因為二級緩存資料存儲媒體多種多樣,不一定隻存在記憶體中,也可能存在硬碟中。
4、mybatis架構主要是圍繞sqlSessionFactory進行的,具體的步驟:
- 定義一個configuration對象,其中包含資料源、事務、mapper檔案資源以及影響資料庫行為屬性設定settings。
- 通過配置對象,則可以建立一個sqlSessionFactoryBuilder對象。
- 通過sqlSessionFactoryBuilder獲得sqlSessionFactory執行個體。
- 通過sqlSessionFactory執行個體建立qlSession執行個體,通過sqlSession對資料庫進行操作。
5、代碼執行個體
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 加載類路徑下的屬性檔案 -->
<properties resource="db.properties"/>
<!-- 設定類型别名 -->
<typeAliases>
<typeAlias type="cn.itcast.javaee.mybatis.app04.Student" alias="student"/>
</typeAliases>
<!-- 設定一個預設的連接配接環境資訊 -->
<environments default="mysql_developer">
<!-- 連接配接環境資訊,取一個任意唯一的名字 -->
<environment id="mysql_developer">
<!-- mybatis使用jdbc事務管理方式 -->
<transactionManager type="jdbc"/>
<!-- mybatis使用連接配接池方式來擷取連接配接 -->
<dataSource type="pooled">
<!-- 配置與資料庫互動的4個必要屬性 -->
<property name="driver" value="${mysql.driver}"/>
<property name="url" value="${mysql.url}"/>
<property name="username" value="${mysql.username}"/>
<property name="password" value="${mysql.password}"/>
</dataSource>
</environment>
<!-- 連接配接環境資訊,取一個任意唯一的名字 -->
<environment id="oracle_developer">
<!-- mybatis使用jdbc事務管理方式 -->
<transactionManager type="jdbc"/>
<!-- mybatis使用連接配接池方式來擷取連接配接 -->
<dataSource type="pooled">
<!-- 配置與資料庫互動的4個必要屬性 -->
<property name="driver" value="${oracle.driver}"/>
<property name="url" value="${oracle.url}"/>
<property name="username" value="${oracle.username}"/>
<property name="password" value="${oracle.password}"/>
</dataSource>
</environment>
</environments>
<!-- 加載映射檔案-->
<mappers>
<mapper resource="cn/itcast/javaee/mybatis/app14/StudentMapper.xml"/>
</mappers>
</configuration>
public class MyBatisTest {
public static void main(String[] args) {
try {
//讀取mybatis-config.xml檔案
InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
//初始化mybatis,建立SqlSessionFactory類的執行個體
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
//建立session執行個體
SqlSession session = sqlSessionFactory.openSession();
/*
* 接下來在這裡做很多事情,到目前為止,目的已經達到得到了SqlSession對象.通過調用SqlSession裡面的方法,
* 可以測試MyBatis和Dao層接口方法之間的正确性,當然也可以做别的很多事情,在這裡就不列舉了
*/
//插入資料
User user = new User();
user.setC_password("123");
user.setC_username("123");
user.setC_salt("123");
//第一個參數為方法的完全限定名:位置資訊+映射檔案當中的id
session.insert("com.cn.dao.UserMapping.insertUserInformation", user);
//送出事務
session.commit();
//關閉session
session.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
十、mybatis如何防止sql注入
【MyBatis 基礎知識總結 1】SQL注入
十一、concurrentHashMap和HashTable有什麼差別
concurrentHashMap融合了hashmap和hashtable的優勢,hashmap是不同步的,但是單線程情況下效率高,hashtable是同步的同步情況下保證程式執行的正确性。
但hashtable每次同步執行的時候都要鎖住整個結構,如下圖:
concurrentHashMap鎖的方式是細粒度的。concurrentHashMap将hash分為16個桶(預設值),諸如get、put、remove等常用操作隻鎖住目前需要用到的桶。
concurrentHashMap的讀取并發,因為讀取的大多數時候都沒有鎖定,是以讀取操作幾乎是完全的并發操作,隻是在求size時才需要鎖定整個hash。
而且在疊代時,concurrentHashMap使用了不同于傳統集合的快速失敗疊代器的另一種疊代方式,弱一緻疊代器。在這種方式中,當iterator被建立後集合再發生改變就不會抛出ConcurrentModificationException,取而代之的是在改變時new新的資料而不是影響原來的資料,iterator完成後再講頭指針替代為新的資料,這樣iterator時使用的是原來的資料。
十二、hashmap存儲的資料結構
數組結構中有數組和連結清單,實作對資料的存儲。
1、數組
數組存儲區間是連續的,占用記憶體嚴重,故空間複雜度大,但數組的二分查找時間複雜度小,為O(1);
數組的特點是:查找容易,插入和删除困難。
2、連結清單
連結清單存儲區間離散,暫用記憶體比較寬松,故空間複雜度小,但時間複雜度大,達到O(N)。
連結清單的特點:查找困難,插入和删除容易。
3、哈希表
哈希表有多種不同的實作方法,現在介紹一種最常用的方法---拉鍊法,可以簡單的了解為“連結清單的數組”,如圖:
4、HashMap其實就是一個線性的數組
首先HashMap中有一個靜态内部類Entry,其重要的屬性有key、value、next,key、value構成Entry[],next的指針指向下一個元素的引用,也就構成了連結清單。
十三、HasmMap和HashSet的差別
1、先了解一下HashCode
Java中的集合有兩類,一類是List,一類是Set。
List:元素有序,可以重複;
Set:元素無序,不可重複;
要想保證元素的不重複,拿什麼來判斷呢?這就是Object.equals方法了。如果元素有很多,增加一個元素,就要判斷n次嗎?
顯然不現實,于是,Java采用了哈希表的原理。雜湊演算法也稱為雜湊演算法,是将資料依特定算法直接指定到一根位址上,初學者可以簡單的了解為,HashCode方法傳回的就是對象存儲的實體位置(實際上并不是)。
這樣一來,當集合添加新的元素時,先調用這個元素的hashcode()方法,就一下子能定位到他應該放置的實體位置上。如果這個位置上沒有元素,他就可以直接存儲在這個位置上,不用再進行任何比較了。如果這個位置上有元素,就調用它的equals方法與新元素進行比較,想同的話就不存了,不相同就散列其它的位址。是以這裡存在一個沖突解決的問題。這樣一來實際上調用equals方法的次數就大大降低了,幾乎隻需要一兩次。
簡而言之,在集合查找時,hashcode能大大降低對象比較次數,提高查找效率。
Java對象的equals方法和hashCode方法時這樣規定的:
- 相等的對象就必須具有相等的hashcode。
- 如果兩個對象的hashcode相同,他們并不一定相同。
如果兩個對象的hashcode相同,他們并不一定相同。
如果兩個Java對象A和B,A和B不相等,但是A和B的哈希碼相等,将A和B都存入HashMap時會發生哈希沖突,也就是A和B存放在HashMap内部數組的位置索引相同,這時HashMap會在該位置建立一個連結表,将A和B串起來放在該位置,顯然,該情況不違反HashMap的使用規則,是允許的。當然,哈希沖突越少越好,盡量采用好的雜湊演算法避免哈希沖突。
equals()相等的兩個對象,hashcode()一定相等;equals()不相等的兩個對象,卻并不能證明他們的hashcode()不相等。
2、HashMap和HashSet的差別
HashMap | HashSet |
實作了Map接口 | 實作了Set接口 |
存儲鍵值對 | 存儲對象 |
調用put()添加元素 | 調用add()添加元素 |
HashMap使用key計算HashCode | HashSet使用成員對象計算hashcode值,對于兩個對象來說hashcode可能相同,是以equals()方法用來判斷對象的相等性,如果兩個對象不同的話,那麼傳回false。 |
HashMap比HashSet快 | HashSet比HashMap慢 |
十四、synerchronized原理
1、偏向鎖
系統發現不存線上程競争時,會使用偏向鎖,實際上就是在對象頭上标記目前線程id。
2、自旋鎖(也叫輕量級鎖,無鎖,樂觀鎖)
自旋鎖的原理是CAS操作,CAS是原子性的,梓軒的線程會嘗試着去擷取值,如果擷取到了,就會執行代碼邏輯得到新值,再比較之前讀到的值和目前的值是否一緻,一緻的話就把新值寫到記憶體裡面;不一緻說明值被其他線程修改了,放棄目前修改操作,重新嘗試着去擷取值,再進行處理、比較、交換操作。
3、重量級鎖
預設自旋10次更新為重量級鎖。
十五、CAS
1、CAS實作原理
CAS是Compare And Swap的縮寫,意思就是比較并交換。
它是無鎖化的實作,是經典的樂觀鎖。
synchronized是一種悲觀鎖,會導緻其它所有需要鎖的線程挂起,等待持有鎖的線程釋放鎖。
樂觀鎖就是不加鎖而是假設沒有沖突而去完成某項操作,如果因為沖突失敗就重試,直到成功為止。樂觀鎖的機制就是CAS。
CAS操作很簡單,它包含三個操作數:記憶體位址V、預期原值A、新值B。先比較記憶體位址V處的值和預期原值A是否相等,如果相等就将記憶體位址V處更新為新值B。在配合循環使用時,若CAS操作失敗,會循環執行或到達某個終止處。此操作配合循環使用時,又稱為自旋鎖的實作方式。
2、CAS存在的問題
(1)ABA問題
解決辦法:
- 加時間戳
- 加版本号
(2)循環開銷大
CAS是樂觀鎖,如果線程比較多,資源搶占激烈,命中率低的情況下,不斷的循環會不斷的消耗資源。實際上,可以設定最大循環數,達到最大循環數還沒有占有資源就自動放棄,避免無限的循環。
(3)隻能保證一個共享變量的原子操作。
十六、Java常見的線程池
Java常見的線程池
上一篇:如果當時這15道題能答好,現在應該已經被錄取了(記一次面試的親身經曆 2020-7-20)
下一篇:超詳細的springBoot學習筆記