即將到來金三銀四人才招聘的高峰期,渴望跳槽的朋友肯定跟我一樣四處找以往的面試題,但又感覺找的又不完整,在這里我將把我所見到的題目做一總結(jié),并盡力將答案術(shù)語化、標(biāo)準(zhǔn)化。預(yù)祝大家面試順利。
另:如果覺得本文有用,歡迎點好看或者分享出去!
術(shù)語會讓你的面試更有說服力,讓你感覺更踏實,建議大家多記背點術(shù)語。
術(shù)語:操作系統(tǒng)指令集、屏蔽系統(tǒng)之間的差異
由于各種操作系統(tǒng)所支持的指令集不是完全一致,所以在操作系統(tǒng)之上加個虛擬機可以來提供統(tǒng)一接口,屏蔽系統(tǒng)之間的差異。
有八種基本數(shù)據(jù)類型。
數(shù)據(jù)類型 | 字節(jié) | 默認(rèn)值 |
---|---|---|
byte | 1 | 0 |
short | 2 | 0 |
int | 4 | 0 |
long | 8 | 0 |
float | 4 | 0.0f |
double | 8 | 0.0d |
char | 2 | '\u0000' |
boolean | 4 | false |
各自占用幾字節(jié)也記一下。
面向?qū)ο蟮木幊陶Z言有封裝、繼承 、抽象、多態(tài)等4個主要的特征。
封裝: 把描述一個對象的屬性和行為的代碼封裝在一個模塊中,也就是一個類中,屬性用變量定義,行為用方法進(jìn)行定義,方法可以直接訪問同一個對象中的屬性。
抽象: 把現(xiàn)實生活中的對象抽象為類。分為過程抽象和數(shù)據(jù)抽象
數(shù)據(jù)抽象 -->鳥有翅膀,羽毛等(類的屬性)
過程抽象 -->鳥會飛,會叫(類的方法)
繼承:子類繼承父類的特征和行為。子類可以有父類的方法,屬性(非private)。子類也可以對父類進(jìn)行擴展,也可以重寫父類的方法。缺點就是提高代碼之間的耦合性。
多態(tài): 多態(tài)是指程序中定義的引用變量所指向的具體類型和通過該引用變量發(fā)出的方法調(diào)用在編程時并不確定,而是在程序運行期間才確定(比如:向上轉(zhuǎn)型,只有運行才能確定其對象屬性)。方法覆蓋和重載體現(xiàn)了多態(tài)性。
術(shù)語:讓基本類型也具有對象的特征
基本類型 | 包裝器類型 |
---|---|
boolean | Boolean |
char | Character |
int | Integer |
byte | Byte |
short | Short |
long | Long |
float | Float |
double | Double |
為了讓基本類型也具有對象的特征,就出現(xiàn)了包裝類型(如我們在使用集合類型Collection時就一定要使用包裝類型而非基本類型)因為容器都是裝object的,這是就需要這些基本類型的包裝器類了。
自動裝箱:new Integer(6);
,底層調(diào)用:Integer.valueOf(6)
自動拆箱: int i = new Integer(6);
,底層調(diào)用i.intValue();
方法實現(xiàn)。
Integer i = 6;
Integer j = 6;
System.out.println(i==j);
答案在下面這段代碼中找:
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
聲明方式不同:基本類型不使用new關(guān)鍵字,而包裝類型需要使用new關(guān)鍵字來在堆中分配存儲空間;
存儲方式及位置不同:基本類型是直接將變量值存儲在棧中,而包裝類型是將對象放在堆中,然后通過引用來使用;
初始值不同:基本類型的初始值如int為0,boolean為false,而包裝類型的初始值為null;
使用方式不同:基本類型直接賦值直接使用就好,而包裝類型在集合如Collection、Map時會使用到。
==
較的是兩個引用在內(nèi)存中指向的是不是同一對象(即同一內(nèi)存空間),也就是說在內(nèi)存空間中的存儲位置是否一致。如果兩個對象的引用相同時(指向同一對象時),“==”操作符返回true,否則返回flase。
equals
用來比較某些特征是否一樣。我們平時用的String類等的equals方法都是重寫后的,實現(xiàn)比較兩個對象的內(nèi)容是否相等。
我們來看看String重寫的equals方法:
它不止判斷了內(nèi)存地址,還增加了字符串是否相同的比較。
public boolean equals(Object anObject) {
//判斷內(nèi)存地址是否相同
if (this == anObject) {
return true;
}
// 判斷參數(shù)類型是否是String類型
if (anObject instanceof String) {
// 強轉(zhuǎn)
String anotherString = (String)anObject;
int n = value.length;
// 判斷兩個字符串長度是否相等
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
// 一一比較 字符是否相同
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
java中String、StringBuffer、StringBuilder是編程中經(jīng)常使用的字符串類,他們之間的區(qū)別也是經(jīng)常在面試中會問到的問題?,F(xiàn)在總結(jié)一下,看看他們的不同與相同。
String
底層使用一個不可變的字符數(shù)組private final char value[];
所以它內(nèi)容不可變。
StringBuffer
和StringBuilder
都繼承了AbstractStringBuilder
底層使用的是可變字符數(shù)組:char[] value;
StringBuilder
是線程不安全的,效率較高;而StringBuffer
是線程安全的,效率較低。
通過他們的append()
方法來看,StringBuffer
是有同步鎖,而StringBuilder
沒有:
@Override
public synchronized StringBuffer append(Object obj) {
toStringCache = null;
super.append(String.valueOf(obj));
return this;
}
@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}
StringBuilder
與StringBuffer
有公共父類AbstractStringBuilder
。
最后,操作可變字符串速度:StringBuilder > StringBuffer > String
,這個答案就顯得不足為奇了。
Collection下:List系(有序、元素允許重復(fù))和Set系(無序、元素不重復(fù))
set根據(jù)equals和hashcode判斷,一個對象要存儲在Set中,必須重寫equals和hashCode方法
Map下:HashMap線程不同步;TreeMap線程同步
Collection系列和Map系列:Map是對Collection的補充,兩個沒什么關(guān)系
之前專門有寫過ArrayList和LinkedList源碼的文章。
ArrayList是實現(xiàn)了基于動態(tài)數(shù)組的數(shù)據(jù)結(jié)構(gòu),LinkedList基于鏈表的數(shù)據(jù)結(jié)構(gòu)。
對于隨機訪問get和set,ArrayList覺得優(yōu)于LinkedList,因為LinkedList要移動指針。
對于新增和刪除操作add和remove,LinedList比較占優(yōu)勢,因為ArrayList要移動數(shù)據(jù)。
public class Test {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(2);
Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()){
Integer integer = iterator.next();
if(integer==2)
list.remove(integer);
}
}
}
執(zhí)行上段代碼是有問題的,會拋出ConcurrentModificationException
異常。
原因:調(diào)用list.remove()
方法導(dǎo)致modCount
和expectedModCount
的值不一致。
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
解決辦法:在迭代器中如果要刪除元素的話,需要調(diào)用Iterator
類的remove
方法。
public class Test {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(2);
Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()){
Integer integer = iterator.next();
if(integer==2)
iterator.remove(); //注意這個地方
}
}
}
相同點:
HashMap和Hashtable都實現(xiàn)了Map接口
都可以存儲key-value數(shù)據(jù)
不同點:
HashMap可以把null作為key或value,HashTable不可以
HashMap線程不安全,效率高。HashTable線程安全,效率低。
HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。
什么是fail-fast?
就是最快的時間能把錯誤拋出而不是讓程序執(zhí)行。
Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的擴展性更好。
ConcurrentHashMap將整個Map分為N個segment(類似HashTable),可以提供相同的線程安全,但是效率提升N倍,默認(rèn)N為16。
HashMap可以通過下面的語句進(jìn)行同步:Map m = Collections.synchronizeMap(hashMap);
答案:字節(jié)流
11.1 什么是字節(jié)流,什么是字符流?
字節(jié)流:傳遞的是字節(jié)(二進(jìn)制),
字符流:傳遞的是字符
我們并不支持下載的文件有沒有包含字節(jié)流(圖片、影像、音源),所以考慮到通用性,我們會用字節(jié)流。
這個之前自己做過總結(jié),也算比較全面。
public class CreatThreadDemo1 extends Thread{
/**
* 構(gòu)造方法: 繼承父類方法的Thread(String name);方法
* @param name
*/
public CreatThreadDemo1(String name){
super(name);
}
@Override
public void run() {
while (!interrupted()){
System.out.println(getName()+'線程執(zhí)行了...');
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
CreatThreadDemo1 d1 = new CreatThreadDemo1('first');
CreatThreadDemo1 d2 = new CreatThreadDemo1('second');
d1.start();
d2.start();
d1.interrupt(); //中斷第一個線程
}
}
常規(guī)方法,不多做介紹了,interrupted方法,是來判斷該線程是否被中斷。(終止線程不允許用stop方法,該方法不會施放占用的資源。所以我們在設(shè)計程序的時候,要按照中斷線程的思維去設(shè)計,就像上面的代碼一樣)。
Thread.sleep(200); //線程休息2ms
Object.wait(); //讓線程進(jìn)入等待,直到調(diào)用Object的notify或者notifyAll時,線程停止休眠
public class CreatThreadDemo2 implements Runnable {
@Override
public void run() {
while (true){
System.out.println('線程執(zhí)行了...');
}
}
public static void main(String[] args) {
//將線程任務(wù)傳給線程對象
Thread thread = new Thread(new CreatThreadDemo2());
//啟動線程
thread.start();
}
}
Runnable 只是來修飾線程所執(zhí)行的任務(wù),它不是一個線程對象。想要啟動Runnable對象,必須將它放到一個線程對象里。
public class CreatThreadDemo3 extends Thread{
public static void main(String[] args) {
//創(chuàng)建無參線程對象
new Thread(){
@Override
public void run() {
System.out.println('線程執(zhí)行了...');
}
}.start();
//創(chuàng)建帶線程任務(wù)的線程對象
new Thread(new Runnable() {
@Override
public void run() {
System.out.println('線程執(zhí)行了...');
}
}).start();
//創(chuàng)建帶線程任務(wù)并且重寫run方法的線程對象
new Thread(new Runnable() {
@Override
public void run() {
System.out.println('runnable run 線程執(zhí)行了...');
}
}){
@Override
public void run() {
System.out.println('override run 線程執(zhí)行了...');
}
}.start();
}
}
創(chuàng)建帶線程任務(wù)并且重寫run方法的線程對象中,為什么只運行了Thread的run方法。我們看看Thread類的源碼,
,我們可以看到Thread實現(xiàn)了Runnable接口,而Runnable接口里有一個run方法。
所以,我們最終調(diào)用的重寫的方法應(yīng)該是Thread類的run方法。而不是Runnable接口的run方法。
public class CreatThreadDemo4 implements Callable {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CreatThreadDemo4 demo4 = new CreatThreadDemo4();
FutureTask<Integer> task = new FutureTask<Integer>(demo4); //FutureTask最終實現(xiàn)的是runnable接口
Thread thread = new Thread(task);
thread.start();
System.out.println('我可以在這里做點別的業(yè)務(wù)邏輯...因為FutureTask是提前完成任務(wù)');
//拿出線程執(zhí)行的返回值
Integer result = task.get();
System.out.println('線程中運算的結(jié)果為:'+result);
}
//重寫Callable接口的call方法
@Override
public Object call() throws Exception {
int result = 1;
System.out.println('業(yè)務(wù)邏輯計算中...');
Thread.sleep(3000);
return result;
}
}
Callable接口介紹:
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
返回指定泛型的call方法。然后調(diào)用FutureTask對象的get方法得道call方法的返回值。
public class CreatThreadDemo5 {
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println('定時器線程執(zhí)行了...');
}
},0,1000); //延遲0,周期1s
}
}
public class CreatThreadDemo6 {
public static void main(String[] args) {
//創(chuàng)建一個具有10個線程的線程池
ExecutorService threadPool = Executors.newFixedThreadPool(10);
long threadpoolUseTime = System.currentTimeMillis();
for (int i = 0;i<10;i++){
threadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+'線程執(zhí)行了...');
}
});
}
long threadpoolUseTime1 = System.currentTimeMillis();
System.out.println('多線程用時'+(threadpoolUseTime1-threadpoolUseTime));
//銷毀線程池
threadPool.shutdown();
threadpoolUseTime = System.currentTimeMillis();
}
}
lambda表達(dá)式不懂的,可以看看我的java8新特性文章:
java8-lambda:
https://www.jianshu.com/p/3a08dc78a05f
java8-stream:
https://www.jianshu.com/p/ea16d6712a00
public class CreatThreadDemo7 {
public static void main(String[] args) {
List<Integer> values = Arrays.asList(10,20,30,40);
//parallel 平行的,并行的
int result = values.parallelStream().mapToInt(p -> p*2).sum();
System.out.println(result);
//怎么證明它是并發(fā)處理呢
values.parallelStream().forEach(p-> System.out.println(p));
}
}
輸出:
200
40
10
20
30
怎么證明它是并發(fā)處理呢,他們并不是按照順序輸出的 。
該專題分為Java基礎(chǔ)、計算機網(wǎng)絡(luò)、操作系統(tǒng)、數(shù)據(jù)結(jié)構(gòu)、算法精讀、數(shù)據(jù)庫面試題、框架面試題、服務(wù)高可用、分布式事務(wù)、分布式鎖、消息隊列等部分,盡量將全網(wǎng)的面試題一網(wǎng)打盡,方便大家手機閱讀和收藏。