一個Java小白麪試得力集團的收穫

###前言html

昨天知道得力集團在某一個培訓機構進行校園招聘。因而我今天就來了,聽了一下宣講內容。發現得力集團剛8月份在武漢成立了研究院,主要是作雲服務,從0開始,如今的團隊規模大概在20多人。一開始宣講的是HR,後來就是技術總監,感受技術總監給人一種很厲害的感受。java

不過薪資的確是過低了,4.5K - 5.5K,並且浮動的1k還要看學歷。得力主要誘惑個人是雲服務項目是從0開始,能讓本身獲得很大的提升。個人確心動了一下,心想先去面試一下,看本身的技術怎麼樣。程序員

persistence.jpg


###初面面試

  • 面試官是一個很漂亮的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);
    }
}
複製代碼

image.png

  • 觀察者模式的優缺點: 優勢:

    • 觀察者和被觀察者是抽象耦合的。
    • 創建一套觸發機制。

    缺點:

    • 若是一個被觀察者對象有不少的直接和間接的觀察者的話, 將全部的觀察者都通知到會花費不少時間。

    • 若是在觀察者和被觀察者之間有循環依賴的話,被觀察者會觸發它們之間進行循環調用,可能會致使系統崩潰。

    • 觀察者模式沒有相應的機制讓觀察者知道所觀察的目標對象是怎麼發生變化的,而僅僅只是知道觀察目標發生了變化。


####POST和GET的區別 面試的時候,回答POSTGET的區別,受到了網上一些博客的誤導。如今必須開始糾正了。

  • GETPOST本質上是TCP連接,並沒有差異。大多數瀏覽器一般都會限制url長度在2K個字節,而大多數服務器最多處理64K大小的url。因爲HTTP的規定和瀏覽器/服務器的限制,致使它們在應用過程當中體現出不一樣。

  • 對於GET方式的請求,瀏覽器會把http headerdata一塊兒發送出去,服務器響應200(返回數據)。 而對於POST,瀏覽器先發送header,服務器響應100 continue,瀏覽器再去發送data,服務器響應200(返回數據)。因此GET產生一個TCP數據包,POST產生兩個數據包。並非全部瀏覽器都會在POST中發送兩次包,而Firefox就只發送一次。

  • GET把參數包含在URL中,POST經過request body傳遞參數。

  • 冪等主要是爲了處理同一個請求重複發送的狀況,好比在請求響應前失去鏈接,若是方法是冪等的,就能夠放心的重發一次請求。GETPUTDELETE都是冪等的,可是POST不是冪等,這也是瀏覽器再後退或者刷新時遇到POST請求會給用戶提示的緣由,重複請求可能會形成意想不到的結果。


####什麼是冪等?

  • 冪等是一個數學或計算機學概念。經常使用於抽象代數中。對於單目運算符來講,若是一個運算對於在範圍內的全部的一個數屢次進行該運算所得的結果和進行一次運算所得的結果是同樣的。那麼咱們就稱該運算是冪等的。好比絕對值運算就是一個例子。在實數集中,有abs(a) = abs(abs(a))。對於雙目運算,則要求當參與運算的兩個值都是等值的狀況下,若是知足運算結果與參與運算的兩個值相等,那麼能夠稱這個運算爲冪等。好比max(x,x) = x

  • 冪等是指同一個請求方法執行屢次和僅執行一次的效果徹底相同。


####SpringMVC註解

  • 關於SpringMVC註解,能夠看我以前的一篇文章有提到過。MyBatis-Spring官方文檔 學習筆記

  • 面試官問我自動掃包的註解,可是我忘記怎麼讀了。<context:component-scan base-package="com.augmentum.exam" />


####Java序列化

  • 序列化就是把對象轉換成字節序列的過程。

  • 反序列化就是把字節序列恢復爲對象的過程。·

  • ParcelableSerializable都能實現序列化。SerializableJava中的序列化接口,其使用起來簡單可是開銷很大,序列化和反序列化過程須要大量的I/O操做。而ParcelableAndroid中的序列化方式,所以更適合在Android平臺上,它的缺點就是使用起來稍微麻煩點,可是它的效率很高,這是Android推薦的序列化方式,所以咱們要首選ParcelableParcelable主要用在內存序列化上,Serializable主要用於將對象序列化到存儲設備中或者將對象序列化後經過網絡傳輸。

  • 咱們須要指定serialVersionUID的值,若是反序列化時當前的類有所改變,好比增長或者刪除了某些成員變量,那麼系統就會從新計算當前類的hash值並把它賦值給serialVersionUID。這個時候當前類的serialVersionUID就和序列化的數據中的serialVersionUID不一致,因而反序列化失敗了。

面試官問我JavaSerializable序列化性能太差,問我如何高效的序列化。當時一臉懵逼,不知所云。如今回想起來,應該回答使用第三方序列化工具,也就是fastjson

  • 替換其餘全部的json庫,java世界裏沒有其餘的json庫可以和fastjson可相比了。

  • 使用fastjson的序列化和反序列化替換java Serializablejava Serializable不單性能慢,並且體積大。

  • 使用fastjson替換hessian(是一個基於binary-RPC實現的遠程通信library,使用二進制傳輸數據),json協議和hessian協議大小差很少同樣,並且fastjson性能優越,10倍於hessian

  • fastjson用於memcached(是一個高性能的分佈式內存對象緩存對象系統,用於動態Web應用以減輕數據庫負載)緩存對象數據。

圖片來自互聯網.png

寫着寫着,忽然又想到了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對象不是同一個對象。那麼得出結論:反序列會從新生成一個對象。

    image.png

  • 那麼能夠有一個假設,使用Externalizable方式反序列化會調用無參構造器。咱們去掉Person類的無參構造器,再運行一下,會發生什麼呢?會打印出"no valid constructor"這一行,很顯然須要一個無參構造器。

    image.png

關於對象序列化,還有幾點須要注意。

  • 對象的類名、實例變量(包括基本類型、數組、對其餘對象的引用)都會被序列化;方法、類變量,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又去訪問該文件,就會出錯,應該使用同步機制。好比銀行的轉帳系統,數據庫的保存操做等就須要同步了。

那麼NIOIO有什麼區別呢

  • IO只能實現阻塞式的網絡通訊,NIO可以實現非阻塞的網絡通訊。

  • 標準IO基於字節或者字符流進行操做,而NIO是基於Channel進行操做的。

  • 流的讀寫一般是單向的,要麼是輸入,要麼輸出。 通道是雙向的,既能夠寫數據到Channel,又能夠從Channel讀取數據。

區別說完了,那麼開始NIO之旅了。

  • NIO使用了不一樣的方式來輸入IONIO採用內存映射文件的方式去處理輸入/輸出,NIO將文件或者文件的一段區域映射到內存中,這樣就能夠向訪問內存同樣來訪問文件了。

  • Channel與傳統的InputStreamOutputStream最大的區別在於它提供了一個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裝入數據結束後,調用Bufferflip()方法,該方法將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();
        }
    }
}
複製代碼

###注意事項

  • 若是第一個參考文獻連接打開提示參數錯誤,那麼請複製連接經過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


###尾言

心之所向,素履以往。生如逆旅,一葦以航。總有一天,已百鍊,遂成鋼。

相關文章
相關標籤/搜索