自己經(jīng)驗有限,篇幅也有限笋婿,這里只是記錄一些比較容易混淆或有難度和一些易忘的技術(shù)知識點,里面有一些也是面試阿里經(jīng)常會被問到的問題顿颅,但是不保證答案全部正確缸濒,有錯誤的地方望大家指正
JVM相關(guān)
- JVM內(nèi)存是如何分配的?
堆:占用內(nèi)存最大的區(qū)塊粱腻,主要存放new出來的對象庇配,線程共享,主要設(shè)置大小參數(shù)名是-Xms
和-Xmx
棧(以前概括都叫棧绍些,具體說其實是非堆內(nèi)存):一般是線程私有- 寄存器:即是程序計數(shù)器讨永,存放當(dāng)前正要執(zhí)行的下一條指令地址
- 本地方法棧(不同jvm的實現(xiàn)可能不同,比如平常所用的sun的實現(xiàn)中方法棧和虛擬機棧是一個):線程私有遇革,存儲比如
Object
的hashCode()
;虛擬機棧中用于存儲局部變量表揭糕、動態(tài)鏈接萝快、操作數(shù)、方法出口等信息 - 方法區(qū):所有線程共享著角,用于存放加載類信息揪漩,比如常量、靜態(tài)常量吏口,需要注意的是1.8以后將靜態(tài)常量放在了堆里
- GC
垃圾回收的算法基礎(chǔ)是標(biāo)記和復(fù)制算法奄容,標(biāo)記一般是樹形結(jié)構(gòu),采用根搜索算法产徊,標(biāo)記可以回收的對象昂勒,一般是對象搜索不到根節(jié)點即可以回收,有2次機會舟铜,在第一次被標(biāo)記回收后可以重新被掛靠根節(jié)點(也即是被重新引用戈盈,涉及的的方法是finalize()
),如果沒有下一次判定對象死亡;基礎(chǔ)的復(fù)制算法我舉個例子說明塘娶,將一塊內(nèi)存分成2份归斤,運行時只使用其中一塊,GC時將活的對象復(fù)制到另一塊內(nèi)存刁岸,然后清除前一塊所有內(nèi)存空間脏里,類似于給U盤格式化,這樣比一個一個釋放內(nèi)存要快得多虹曙,相信大家做格式化的時候體會過迫横,現(xiàn)在jvm gc使用的的復(fù)制算法是結(jié)果改良的,不是平均的分成2份根吁,默認(rèn)比例好像是1/8员淫,即平常見到的新生代、老年代击敌、持久代等介返。具體的算法大家看資料文檔吧,這種東西不是說說就能清楚的沃斤。 - 內(nèi)存泄漏和內(nèi)存溢出
內(nèi)存泄漏圣蝎,當(dāng)一個對象不會被使用但占著內(nèi)存即會導(dǎo)致內(nèi)存泄漏,比如
Object o1 = new Object();
Object o2 = new Object();
o1 = o2; // 這時2個對象的引用地址是一樣的衡瓶,但是o1申請的內(nèi)存就沒有被使用
內(nèi)存泄漏積累多了徘公,內(nèi)存不斷被無用對象占用,新的對象申請不到足夠的空間就會產(chǎn)生內(nèi)存溢出哮针。
架構(gòu)相關(guān)
- springMVC的處理流程
- 一個http請求
- 經(jīng)過一些過濾器或攔截器到達(dá)
DispatcherServlet
將請求轉(zhuǎn)發(fā)給對應(yīng)的@Controller
和@RequestMapping
- 參數(shù)封裝关面,請求頭判定等等
- 調(diào)用業(yè)務(wù)方法獲得
Model
等 - 返回
ModelAndView
查找ViewResolver
返回對應(yīng)的View
,可能是需要渲染的jsp十厢,可能是json等太,可能是文件流等等。
- 說說
redis
里的bitmap
bitmap
一般用于計數(shù)或top計算蛮放,比如統(tǒng)計網(wǎng)站當(dāng)前在線人數(shù)缩抡,假設(shè)用戶id是遞增的整數(shù),當(dāng)用戶上線時將用戶id存進(jìn)bitmap包颁,比如id是4瞻想,則bitmap就是00001000
,id為8的用戶上線娩嚼,bitmap的值變成10001000
蘑险,對bitmap做count計算得出是2,而1KB=1024B=8196b且位運算是計算機最快的待锈,這樣做的好處是速度快漠其,還能知道是誰上下線的。同理如果要按月統(tǒng)計某個操作,只需要用每天做key值和屎,然后做并集得到新的bitset計數(shù)就可拴驮。 - Ioc和AOP分別使用了哪些設(shè)計模式?
工廠模式和代理模式柴信,細(xì)一點還有單例套啤、模版、原型随常,這里說一下代理模式潜沦,常用的一般是動態(tài)代理模式,jdk中提供了InvocationHandler
接口可以方便實現(xiàn)動態(tài)代理:
public interface IService {
void service();
}
public class MyService implements IService {
@Override
public void service() {
System.out.println("service...");
}
}
public class MyProxy implements InvocationHandler {
private IService service;
public MyProxy(IService service) {
this.service = service;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before service");
Object invoke = method.invoke(service, args);
System.out.println("after service ");
return invoke;
}
}
public class Main {
public static void main(String[] args) {
MyService myService = new MyService();
MyProxy myProxy = new MyProxy(myService);
// 動態(tài)生成代理對象
IService instance = (IService) Proxy.newProxyInstance(MyService.class.getClassLoader(), MyService.class.getInterfaces(), myProxy);
instance.service();// 利用代理對象調(diào)用service方法
}
}
- MySQL數(shù)據(jù)庫的鎖
MySQL常見的有2種引擎绪氛,MyISAM采用的是表級鎖唆鸡,給整表加鎖,速度快枣察,不會死鎖争占,但是但是鎖競爭激烈,效率低序目,lock table_name read
orwrite
臂痕;InnoDB采用的是行級鎖,只鎖行猿涨,效率高握童,但是會死鎖,另外MySQL的行級鎖采用鎖索引實現(xiàn)叛赚,所以只有通過索引檢索數(shù)據(jù)才能使用行級鎖澡绩,否則會使用表級鎖 - 冪等性
冪等性是數(shù)學(xué)里的一個概念,我并不是很精通俺附,簡單來說就是N次變換和1次變換的結(jié)果應(yīng)該保持一致英古,計算機里他是一種Http協(xié)議中提到的性質(zhì),注意冪等性本身并不是協(xié)議昙读,沒有辦法通過規(guī)范一致化操作,多用于分布式系統(tǒng)膨桥,用于保證分布式系統(tǒng)中數(shù)據(jù)的一致性操作蛮浑,類似于分布式事務(wù),但是分布式事務(wù)中間件一般較重且效率有很大虧損只嚣,對于要求高性能的分布式場景中沮稚,冪等設(shè)計可能是唯一的選擇。實現(xiàn)場景簡單舉例來說册舞,假設(shè)微信支付后端是分布式的(肯定是的)蕴掏,我發(fā)起了一個支付,如果服務(wù)器端已經(jīng)處理完成但是我的手機沒網(wǎng)了,我會誤以為支付失敗盛杰,重新支付挽荡,冪等設(shè)計在此類場景中一般會這樣設(shè)計,在發(fā)起支付操作前會先向服務(wù)端申請一個ticket即供,這個ticket會關(guān)聯(lián)此次支付的操作定拟,這個ticket只能增長一次,這樣在我重新發(fā)起支付的時候逗嫡,服務(wù)器就可以正確返回支付成功且保證我只支付了一次青自。
java基礎(chǔ)
- ConcurrentHashMap
ConcurrentHashMap
是線程安全的集合類,功能類似于Hashtable
驱证,但是Hashtable
雖然也是線程安全的延窜,但是Hashtable
只有一把同步鎖,并發(fā)性能不高抹锄,ConcurrentHashMap
則是利用了鎖分段技術(shù)逆瑞,簡單來說就是,多個類似的HashTable
祈远,單獨維護(hù)自己的鎖呆万,這樣多線程操作的時候減少了競爭鎖的等待,在多線程應(yīng)用里是最常用的線程安全集合類车份。查看源碼可以知道谋减,ConcurrentHashMap
內(nèi)部主要成員是Segment
和Node
,Segment
充當(dāng)鎖扫沼,繼承ReentrantLock
出爹,Node
相當(dāng)于一個Map.Entry
,其他大部分成員變量都是volatile
的缎除,因為happen before
的存在严就,volatile
字段的寫入操作先于讀操作,這也是用volatile替換鎖的經(jīng)典應(yīng)用場景器罐。 - ThreadLocal
ThreadLocal梢为,利用線程局部變量來實現(xiàn)線程安全的方式,使用時需要小心應(yīng)對轰坊,因為線程局部變量一旦使用完沒有被釋放就會導(dǎo)致內(nèi)存泄漏铸董。 - 有沒有可能兩個不相等的對象有相同的 hashcode?
hashcode并不是唯一的肴沫,只是重復(fù)概率非常小而已粟害,但是相等的對象hashcode一定是一樣的。 - 編寫多線程程序的時候你需要注意哪些颤芬?
- 盡量使用
volatile
替換同步鎖 - 給線程取個name
- 使用并發(fā)集合而不是讓集合同步
- 合理創(chuàng)建線程數(shù)悲幅,一般而言是CPU的核心數(shù)*2+1
- 給需要同步的代碼同步套鹅,而不是圖簡單給整個方法或類加同步
- 盡量使用
- DateFormat的所有實現(xiàn)都不是線程安全的,如果一定要在多線程中使用可以利用
ThreadLocal
- 對稱加密和非對稱加密
對稱加密:需要同一把密鑰來解密汰具,速度快卓鹿,一般用于需要加密大量數(shù)據(jù)時使用,常見用于對稱加密的算法有DES郁副、3DES减牺、RC系、AES等存谎。
非對稱加密:需要2把密鑰才能解密拔疚,分作公鑰和私鑰,如果用公開密鑰對數(shù)據(jù)進(jìn)行加密既荚,只有用對應(yīng)的私有密鑰才能解密稚失;如果用私有密鑰對數(shù)據(jù)進(jìn)行加密,那么只有用對應(yīng)的公開密鑰才能解密恰聘;常見的https協(xié)議里的證書機制就是采用的這種方式句各,常用于非對稱加密算法的有RSA、ECC晴叨、Elgamal凿宾。 - NIO和普通IO的區(qū)別?
最主要的區(qū)別在于非阻塞與阻塞兼蕊,NIO是先寫入緩沖區(qū)在再讀出操作初厚,是非阻塞的,而普通IO操作主要是針對流的孙技,一個線程讀寫流時是不能做其他操作的产禾,就好比如下載文件有些軟件可以斷點續(xù)傳,有些不可以牵啦。
問題
- 統(tǒng)計log文件里所有出現(xiàn)的單詞以及出現(xiàn)的次數(shù)并且按照次數(shù)排序找出最頻繁的單詞亚情?
步驟其實很簡單:- 讀取文件
- 排序
這里直接提供代碼,分別是jdk 1.7和jdk 1.8的2個版本
1.7:
import java.io.*;
import java.util.*;
import java.util.stream.Collectors;
public class Test {
/**
* 根據(jù)map的value進(jìn)行排序
* @param map
* @param <K>
* @param <V>
* @return
*/
public static <K, V extends Comparable<? super V>> Map<K, V> sortByValue(Map<K, V> map) {
List<Map.Entry<K, V>> list = new LinkedList<Map.Entry<K, V>>(map.entrySet());
// 先將map轉(zhuǎn)換成List便于使用sort排序
Collections.sort(list, new Comparator<Map.Entry<K, V>>() {
public int compare(Map.Entry<K, V> o1, Map.Entry<K, V> o2) {
return (o2.getValue()).compareTo(o1.getValue());
}
});
Map<K, V> result = new LinkedHashMap<K, V>();
for (Map.Entry<K, V> entry : list) {
result.put(entry.getKey(), entry.getValue());
}
return result;
}
public static void main(String[] args) {
FileInputStream fileInputStream = null;
String fileName = "ngen.log";
TreeMap<String, Integer> map = new TreeMap<>();
// SortedMap<String, Integer> map = new
try {
fileInputStream = new FileInputStream(fileName);
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(fileInputStream));
String line = null;
// 按行讀取文件分解單詞
while ((line = bufferedReader.readLine()) != null) {
String[] ss = line.split(" ");
for (int i = 0; i < ss.length; i++) {
String s = ss[i];
if (s != null && s.matches("\\w+")) {
// 如果map中有此單詞就將次數(shù)+1
// 否則此單詞第一次出現(xiàn)
if (map.containsKey(s)) {
map.put(s, map.get(s) + 1);
} else {
map.put(s, 1);
}
}
}
}
Map<String, Integer> sortedMap = sortByValue(map);
for (Map.Entry<String, Integer> entry : sortedMap.entrySet()) {
String key = entry.getKey();
Integer value = entry.getValue();
System.out.println(key + ":" + value);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
1.8:
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class Main {
/**
* 根據(jù)map的value進(jìn)行排序
* @param map
* @param <K>
* @param <V>
* @return
*/
public static <K, V extends Comparable<? super V>> Map<K, V> sortByValue(Map<K, V> map) {
return map.entrySet()
.stream()
.sorted(Map.Entry.comparingByValue(Collections.reverseOrder())) // 逆序
.collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue,
(e1, e2) -> e1,
LinkedHashMap::new
));
}
public static void main(String[] args) {
FileInputStream fileInputStream = null;
String fileName = "D:/app.log";
TreeMap<String, Integer> map = new TreeMap<>();
try {
fileInputStream = new FileInputStream(fileName);
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(fileInputStream));
bufferedReader.lines()
.flatMap(line -> Stream.of(line.split(" ")))
.filter(word -> word.matches("\\w+"))
.forEach(s -> { // Stream語法不太熟悉哈雏,不知道有木有更方便的方法楞件?
if (map.containsKey(s)) {
map.put(s, map.get(s) + 1);
} else {
map.put(s, 1);
}
})
;
Map<String, Integer> sortedMap = sortByValue(map);
sortedMap.forEach((k, v) -> System.out.println(k + "," + v));
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
從日志文件中讀取的是字節(jié)還是字符?
乍一看這問題不要太簡單裳瘪,但是我挺佩服問這問題的面試官的履因,這問題向后衍生無論是廣度還是深度都無可挑剔。如何回答這問題要從兩方面出發(fā)盹愚,首先一點,所有操作系統(tǒng)存放在磁盤的任何數(shù)據(jù)肯定都是字節(jié)的站故,那么讀出來肯定也是字節(jié)的皆怕;第二點毅舆,通常日志文件操作寫代碼的時候讀出來的肯定是字符,不然你如何操作呢愈腾?只是java提供的方便的I/O操作方法而已憋活,其實里面是將字節(jié)轉(zhuǎn)成了字符而已。向后延伸就會問比如java的IO操作注意事項虱黄、編碼等等問題悦即,還有操作系統(tǒng)底層如何處理等等的問題,這道題很簡單橱乱,但是切記不要盲目作答辜梳。-
秒殺系統(tǒng)設(shè)計
自己并沒有實際秒殺系統(tǒng)設(shè)計經(jīng)驗,這里從朋友以及網(wǎng)絡(luò)總結(jié)幾點:- 高并發(fā)泳叠,總的來說肯定是Nginx做負(fù)載均衡作瞄,后臺做服務(wù)集群
- 秒殺計時前的靜態(tài)頁面使用cdn
- 秒殺計時不需要做高并發(fā)處理,因為new一個Date返回給前臺危纫,任何語言支持個幾億并發(fā)都是沒問題的
- 先緩存再查庫宗挥,保證低延遲
- 秒殺系統(tǒng)單獨設(shè)計,不要與已有業(yè)務(wù)混淆种蝶,不然一旦阻塞會全盤崩潰
- 庫存要保持事務(wù)唯一契耿,數(shù)據(jù)庫最好另建表,不要與日常業(yè)務(wù)沖突
- 預(yù)估請求處理最大量螃征,當(dāng)請求過多時攔截并直接返回等待
- 預(yù)防惡意刷單搪桂,比如同一個IP只能有一個請求
- 動態(tài)加載js來激活秒殺按鈕,避免秒殺沒有開始時被惡意操作
- 并發(fā)請求隊列
本文已在版權(quán)印備案会傲,如需轉(zhuǎn)載請訪問版權(quán)印锅棕。40142943