###前言html
昨天知道得力集團在某一個培訓機構進行校園招聘。因而我今天就來了,聽了一下宣講內容。發現得力集團剛
8
月份在武漢成立了研究院,主要是作雲服務,從0
開始,如今的團隊規模大概在20
多人。一開始宣講的是HR
,後來就是技術總監,感受技術總監給人一種很厲害的感受。java
不過薪資的確是過低了,
4.5K - 5.5K
,並且浮動的1k
還要看學歷。得力主要誘惑個人是雲服務項目是從0
開始,能讓本身獲得很大的提升。個人確心動了一下,心想先去面試一下,看本身的技術怎麼樣。程序員
###初面面試
HR
小姐姐。首先讓我自我介紹一下,而後問了我如下的幾個問題。
我回答的很乾淨利落,而後進入了複試。複試面試官是一個HR和技術總監,很讓我意外的是技術總監問的題目把我問懵逼了,我都沒法完整的答上來。算法
面試的最後,技術總監問我有什麼想說的嗎,我就諮詢了加薪的標準,而後HR頓時臉黑了,很不耐煩的跟我說一堆。我從她的話知道了,漲薪極小值是10%
,極大值是30%
。我頓時感受無望了,考覈標準仍是一年,並且實習沒有薪水。最後HR
還問我掛科沒有,我說掛了單片機。數據庫
HR
一聽臉又黑了,不耐煩的噼裏啪啦的說了一堆。我如今對得力集團徹底沒有好感了,可是技術總監難倒個人問題,我仍是須要覆盤分析一波,畢竟學習是本身的。json
###關於複試的題目c#
####觀察者模式 這個模式我很熟悉,EventBus
的實現就是基於這個模式。可是仍是有必要的提起這個模式。數組
當對象存在一對多關係時,則使用觀察者模式。好比,當一個對象被修改時,則會自動通知它的依賴對象。觀察者模式屬於行爲模式。瀏覽器
用白話說,就是觀察者模式定義了一種一對多的依賴關係,讓多個觀察者對象同時監聽某一個主題對象。這個主題對象在狀態上發生變化時,會通知全部觀察者對象,讓它們可以自動更新本身。
觀察者模式的組成:
抽象主題角色:把全部對觀察者對象的引用保存在一個集合中,通常用ArrayList
。每一個抽象主題角色均可以有任意數量的觀察者。抽象主題能夠提供一個接口,能夠增長和刪除觀察者。通常用一個抽象類和接口來實現。
抽象觀察者角色:爲全部具體的觀察者定義一個接口,在獲得主題的通知時能夠更新本身。
具體主題角色: 在具體主題內部狀態發生改變的時候,給全部註冊過的觀察者發出通知。
具體觀察者角色: 實現抽象觀察者中的更新接口,以便使自己的狀態與主題的狀態相互協調。
手寫觀察者模式Demo
。
Subject
類,也就是被觀察者。public class Subject {
private List<Observer> observers = new ArrayList<Observer>();
private int state;
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
notifyAllObservers();
}
public void attach(Observer observer) {
observers.add(observer);
}
public void notifyAllObservers() {
for (Observer observer : observers) {
observer.update();
}
}
}
複製代碼
ObServer
類,也就是抽象的觀察者類。public abstract class Observer {
protected Subject subject;
public abstract void update();
}
複製代碼
BinaryObserver
類,它繼承ObServer
類。public class BinaryObserver extends Observer {
public BinaryObserver(Subject subject) {
this.subject = subject;
this.subject.attach(this);
}
@Override
public void update() {
System.out.println("binary=" + Integer.toBinaryString(subject.getState()));
}
}
複製代碼
OctalObserver
類,它繼承於ObServer
類。public class OctalObserver extends Observer {
public OctalObserver(Subject subject) {
this.subject = subject;
this.subject.attach(this);
}
@Override
public void update() {
System.out.println("octal:" + Integer.toOctalString(subject.getState()));
}
}
複製代碼
ObserverPatternDemo
,並運行。public class ObserverPatternDemo {
public static void main(String[] args) {
Subject subject = new Subject();
new BinaryObserver(subject);
new OctalObserver(subject);
subject.setState(15);
subject.setState(10);
}
}
複製代碼
觀察者模式的優缺點: 優勢:
缺點:
若是一個被觀察者對象有不少的直接和間接的觀察者的話, 將全部的觀察者都通知到會花費不少時間。
若是在觀察者和被觀察者之間有循環依賴的話,被觀察者會觸發它們之間進行循環調用,可能會致使系統崩潰。
觀察者模式沒有相應的機制讓觀察者知道所觀察的目標對象是怎麼發生變化的,而僅僅只是知道觀察目標發生了變化。
####POST和GET的區別 面試的時候,回答POST
和GET
的區別,受到了網上一些博客的誤導。如今必須開始糾正了。
GET
和POST
本質上是TCP
連接,並沒有差異。大多數瀏覽器一般都會限制url
長度在2K
個字節,而大多數服務器最多處理64K
大小的url
。因爲HTTP的規定和瀏覽器/服務器的限制,致使它們在應用過程當中體現出不一樣。
對於GET
方式的請求,瀏覽器會把http header
和 data
一塊兒發送出去,服務器響應200
(返回數據)。 而對於POST
,瀏覽器先發送header
,服務器響應100 continue
,瀏覽器再去發送data
,服務器響應200
(返回數據)。因此GET
產生一個TCP
數據包,POST
產生兩個數據包。並非全部瀏覽器都會在POST
中發送兩次包,而Firefox
就只發送一次。
GET
把參數包含在URL
中,POST
經過request body
傳遞參數。
冪等主要是爲了處理同一個請求重複發送的狀況,好比在請求響應前失去鏈接,若是方法是冪等的,就能夠放心的重發一次請求。GET
,PUT
,DELETE
都是冪等的,可是POST
不是冪等,這也是瀏覽器再後退或者刷新時遇到POST
請求會給用戶提示的緣由,重複請求可能會形成意想不到的結果。
####什麼是冪等?
冪等是一個數學或計算機學概念。經常使用於抽象代數中。對於單目運算符來講,若是一個運算對於在範圍內的全部的一個數屢次進行該運算所得的結果和進行一次運算所得的結果是同樣的。那麼咱們就稱該運算是冪等的。好比絕對值運算就是一個例子。在實數集中,有abs(a) = abs(abs(a))
。對於雙目運算,則要求當參與運算的兩個值都是等值的狀況下,若是知足運算結果與參與運算的兩個值相等,那麼能夠稱這個運算爲冪等。好比max(x,x) = x
。
冪等是指同一個請求方法執行屢次和僅執行一次的效果徹底相同。
####SpringMVC註解
關於SpringMVC
註解,能夠看我以前的一篇文章有提到過。MyBatis-Spring官方文檔 學習筆記
面試官問我自動掃包的註解,可是我忘記怎麼讀了。<context:component-scan base-package="com.augmentum.exam" />
####Java序列化
序列化就是把對象轉換成字節序列的過程。
反序列化就是把字節序列恢復爲對象的過程。·
Parcelable
和Serializable
都能實現序列化。Serializable
是Java
中的序列化接口,其使用起來簡單可是開銷很大,序列化和反序列化過程須要大量的I/O
操做。而Parcelable
是Android
中的序列化方式,所以更適合在Android
平臺上,它的缺點就是使用起來稍微麻煩點,可是它的效率很高,這是Android
推薦的序列化方式,所以咱們要首選Parcelable
。Parcelable
主要用在內存序列化上,Serializable
主要用於將對象序列化到存儲設備中或者將對象序列化後經過網絡傳輸。
咱們須要指定serialVersionUID
的值,若是反序列化時當前的類有所改變,好比增長或者刪除了某些成員變量,那麼系統就會從新計算當前類的hash
值並把它賦值給serialVersionUID
。這個時候當前類的serialVersionUID
就和序列化的數據中的serialVersionUID
不一致,因而反序列化失敗了。
面試官問我Java
的Serializable
序列化性能太差,問我如何高效的序列化。當時一臉懵逼,不知所云。如今回想起來,應該回答使用第三方序列化工具,也就是fastjson
。
替換其餘全部的json
庫,java
世界裏沒有其餘的json庫可以和fastjson可相比了。
使用fastjson
的序列化和反序列化替換java Serializable
,java Serializable
不單性能慢,並且體積大。
使用fastjson
替換hessian
(是一個基於binary-RPC
實現的遠程通信library
,使用二進制傳輸數據),json
協議和hessian
協議大小差很少同樣,並且fastjson
性能優越,10
倍於hessian
。
把fastjson
用於memcached
(是一個高性能的分佈式內存對象緩存對象系統,用於動態Web
應用以減輕數據庫負載)緩存對象數據。
寫着寫着,忽然又想到了Externalizable
接口。這是Java
提供的另外一種序列化機制,這種序列化方式徹底由程序員決定存儲和恢復對象數據。要實現該目標,Java
類必須實現Externalizable
接口。咱們接下來寫一個Demo
。
Person
類,實現了java.io.Externalizable
接口。Person
類必須去實現readExternal()
,writeExternal()
兩個方法。public class Person implements Externalizable {
private String name;
private int age;
public Person(String name, int age) {
System.out.println("有參數的構造器");
this.name = name;
this.age = age;
}
public Person() {
System.out.println("無參數的構造器");
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(new StringBuffer(name).reverse());
out.writeInt(age);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
this.name = ((StringBuffer) in.readObject()).reverse().toString();
this.age = in.readInt();
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}
複製代碼
public class ExternalizableDemo {
public static void main(String[] args) throws IOException {
File fileName = new File("externalizable.txt");
FileOutputStream fos = new FileOutputStream(fileName);
FileInputStream fis = new FileInputStream(fileName);
ObjectOutputStream os = new ObjectOutputStream(fos);
ObjectInputStream is = new ObjectInputStream(fis);
try {
Person person = new Person("cmazxiaoma", 21);
os.writeObject(person);
os.writeObject(person);
Person newPerson = (Person) is.readObject();
System.out.println(newPerson);
System.out.println("兩個person對象引用是否相等 :" + person == newPerson + "");
} catch (Exception e) {
System.out.println(e.getMessage());
} finally {
close(is);
close(os);
close(fis);
close(fos);
}
}
public static void close(Closeable closeable) {
try {
if (closeable != null) {
closeable.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
複製代碼
運行測試用例,看控制檯輸出。咱們發現反序列化的時候,會讀取Java
對象中的數據,而後調用無參構造器給對象完成必要的初始化。咱們還會發現序列化以前的Person
對象和反序列以後生成的Person
對象不是同一個對象。那麼得出結論:反序列會從新生成一個對象。
那麼能夠有一個假設,使用Externalizable
方式反序列化會調用無參構造器。咱們去掉Person
類的無參構造器,再運行一下,會發生什麼呢?會打印出"no valid constructor"
這一行,很顯然須要一個無參構造器。
關於對象序列化,還有幾點須要注意。
對象的類名、實例變量(包括基本類型、數組、對其餘對象的引用)都會被序列化;方法、類變量,transient
實例變量(瞬態實列變量)都不會被序列化。
實現Serializable
接口的類若是須要讓某一個實例變量不被序列化,則能夠在該實例變量前加transient
修飾符,而不是加static
關鍵字。雖然static
關鍵字也能夠達到這種效果,可是不能這樣用。
反序列化對象時必須有序列化對象的class
文件。
當經過文件、網絡來讀取序列化後的對象時,必須按實際寫入的順序讀取。
Serializable
反序列化機制在恢復Java
對象時無需調用構造器來初始化Java
對象,而Externalizable
反序列化機制就須要無參構造器。
在這裏還須要說,Java
序列化機制採用一種特殊的序列化算法,以下:
全部保存到磁盤中的對象都有一個序列化編號。
當程序試圖序列化一個對象時,程序將先檢查該對象是否已經被序列化過,只有該對象從未在本次虛擬機中被序列化過,系統纔會將該對象轉換成字節序列並輸出。
若是某個對象已經序列化過,程序將只輸出一個序列化編號,而不是再次從新序列化該對象。
####什麼是NIO? 關於NIO
這個概念,也是我學習Java
知識所忽略的一個點吧。之前看博客的時候,零星的看過,當時沒有什麼在乎。記得昨天技術總監問我NIO
是什麼? 我當時沒聽清他的回答,而後反問NIO
是什麼? 他跟我說NIO
是異步IO
,也就是Asynchronous IO
的意思。當時一臉懵逼,不知所云。今天在掘金上面搜索了一下關於NIO
的文章,也總結一波。
BIO(Blocking I/O)
:同步阻塞IO
模式,數據的讀取寫入必須阻塞在一個線程內等待其完成。
NIO(New I/O)
:同時支持阻塞和非阻塞模式。咱們以同步非阻塞IO
模式來講,若是拿燒開水爲說,NIO
的作法是開啓一個線程不斷的輪詢水壺的狀態。
AIO(Asynchronous I/O)
:異步非阻塞IO
模式。異步非阻塞和同步非阻塞的區別在於無需開啓一個線程去輪詢水壺的狀態,當水燒開了,水壺會發生叫聲,系統就會通知對應的線程來處理。
那麼咱們須要說同步和異步的區別了。
同步:好比發送一個請求,須要等待返回,而後才能發送下一個請求,中間有等待過程。
異步:指發送一個請求,不須要等待返回,隨時能夠再發送下一個請求,即不須要等待。
場景需求: 同步能夠避免讀髒數據的發生。通常共享某一資源的時候用,若是每一個人都有修改權限,當A
刪除了一個文件時,B
又去訪問該文件,就會出錯,應該使用同步機制。好比銀行的轉帳系統,數據庫的保存操做等就須要同步了。
那麼NIO
與IO
有什麼區別呢
IO
只能實現阻塞式的網絡通訊,NIO
可以實現非阻塞的網絡通訊。
標準IO
基於字節或者字符流進行操做,而NIO
是基於Channel
進行操做的。
流的讀寫一般是單向的,要麼是輸入,要麼輸出。 通道是雙向的,既能夠寫數據到Channel
,又能夠從Channel
讀取數據。
區別說完了,那麼開始NIO
之旅了。
NIO
使用了不一樣的方式來輸入IO
,NIO
採用內存映射文件的方式去處理輸入/輸出,NIO
將文件或者文件的一段區域映射到內存中,這樣就能夠向訪問內存同樣來訪問文件了。
Channel
與傳統的InputStream
,OutputStream
最大的區別在於它提供了一個map()
方法,經過該map
方法能夠直接將一塊數據映射到內存中。若是說傳統的輸入/輸出系統是面向流的處理,那麼NIO
則是面向塊的處理。
Buffer
能夠理解成一個容器,它的本質是一個數組,發送到Channel
中的全部對象都必須先放到Buffer
中,而從Channel
中讀取的數據也必須先放入Buffer
。
NIO
還提供了用於將Unicode
字符串映射成字節序列以及逆映射操做的Charset
類,也提供了非阻塞式輸入/輸出的Selector
類。
在Buffer中有3
個重要的概念: 容量(capacity)
,界限(limit)
,位置(position)
。
capacity
: 緩衝區的容量標識該Buffer
的最大數據容量。
limit
:位於limit
後的數據既不可被讀,也不可被寫。
position
:用於指明下一個能夠被讀寫的緩衝區位置的索引(相似於IO
流中的記錄指針)。
接着就來講Buffer
中的flip()
和clear()
方法。
當Buffer
裝入數據結束後,調用Buffer
的flip()
方法,該方法將limit
設置爲position
位置,並將position
設爲0
,這就使得Buffer
的讀寫指針又移動了開始位置。簡而言之,filp()
爲從Buffer
中取出數據作好準備。
當Buffer
輸出數據結束後,Buffer
調用clear()
方法,clear()
方法不是清空Buffer
中的數據,它僅僅將position
置爲0
,將limit
設置爲capacity
,這樣爲再次向Buffer
中裝入數據作好準備。
理論總結的不少,那麼開始手寫代碼吧。
NIODemo
中寫了3
種方法,都是從讀取"nio_read.txt"
文件的內容,而後寫入"nio_write.txt"
文件中。public class NIODemo {
public static void main(String[] args) throws IOException {
// methodOne();
// methodTwo();
methodThree();
}
public static void methodOne() throws IOException {
String rFile = "nio_read.txt";
String wFile = "nio_write.txt";
FileChannel rFileChannel = new FileInputStream(rFile).getChannel();
FileChannel wFileChannel = new FileOutputStream(wFile).getChannel();
ByteBuffer buff = ByteBuffer.allocate(1024);
while (rFileChannel.read(buff) > 0) {
buff.flip();
wFileChannel.write(buff);
buff.clear();
}
close(wFileChannel);
close(rFileChannel);
}
public static void methodTwo() throws IOException {
String rFile = "nio_read.txt";
String wFile = "nio_write.txt";
FileChannel rFileChannel = new FileInputStream(rFile).getChannel();
FileChannel wFileChannel = new FileOutputStream(wFile).getChannel();
rFileChannel.transferTo(0, rFileChannel.size(), wFileChannel);
close(wFileChannel);
close(rFileChannel);
}
public static void methodThree() throws IOException {
String rFile = "nio_read.txt";
String wFile = "nio_write.txt";
RandomAccessFile raf = new RandomAccessFile(rFile, "rw");
FileChannel randomChannel = raf.getChannel();
FileChannel wFileChannel = new FileOutputStream(wFile).getChannel();
// 將Channel中的全部數據映射成ByteChannel
ByteBuffer buff = randomChannel.map(FileChannel.MapMode.READ_ONLY, 0, raf.length());
// 把Channel的指針移動到最後
randomChannel.position(raf.length());
wFileChannel.write(buff);
close(wFileChannel);
close(randomChannel);
}
public static void close(Closeable closeable) {
try {
if (closeable != null) {
closeable.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
複製代碼
既然methodThree()
方法中用到了RandomAccessFile
。那麼就順便說一下使用注意事項:RandomAccessFile
依然不能向文件的指定位置插入內容,若是直接將文件記錄指針移動到中間某一個位置後開始輸出,則新輸出的內容會覆蓋文件中原有的內容。若是須要向指定位置插入內容,程序須要先把插入點後面的位置讀入到緩衝區,等把須要插入的數據寫入文件中後,再把緩衝區的內容追加到文件後面。 ###參考文獻
###注意事項
若是第一個參考文獻連接打開提示參數錯誤,那麼請複製連接經過QQ或者微信打開https://mp.weixin.qq.com/s?sn=71f6c214f3833d9ca20b9f7dcd9d33e4&__biz=MzI3NzIzMzg3Mw%3D%3D&mid=100000054&idx=1#rd
若是最後一個參考文獻連接打開提示參數錯誤,那麼請複製連接經過QQ或者微信打開 https://mp.weixin.qq.com/s?__biz=MzIzMzgxOTQ5NA==&mid=100000199&idx=1&sn=1e9006f2289cdfb612f22e9f6b7b44cb&chksm=68fe9dce5f8914d8ba791b26ae6de6742686dc660db0f38a67c41ed87c942cbe679f26a4b24c#rd
###尾言
心之所向,素履以往。生如逆旅,一葦以航。總有一天,已百鍊,遂成鋼。