一年經驗Java開發0713面試

三分惡發表於2020-07-24

@


介紹一下你做的某些模組,有些什麼比較複雜的地方?

略。


你們的檔案怎麼儲存的?

我們的檔案是儲存在MongoDB中的。

MongoDB單個文件的儲存限制是16M,如果要儲存大於16M的檔案,就要用到MongoDB GridFS。

GridFS是Mongo的一個子模組,使用GridFS可以基於MongoDB來持久儲存檔案。並且支援分散式應用(檔案分佈儲存和讀取)。作為MongoDB中二進位制資料儲存在資料庫中的解決方案,通常用來處理大檔案。

GridFS使用兩個集合(collection)儲存檔案。一個集合是chunks, 用於儲存檔案內容的二進位制資料;一個集合是files,用於儲存檔案的後設資料。

GridFS會將兩個集合放在一個普通的buket中,並且這兩個集合使用buket的名字作為字首。MongoDB的GridFs預設使用fs命名的buket存放兩個檔案集合。因此儲存檔案的兩個集合分別會命名為集合fs.files ,集合fs.chunks。

GridFS儲存檔案示意圖

在這裡插入圖片描述

怎麼沒有用檔案伺服器?

直接將檔案使用通過FTP上傳到檔案伺服器,並將檔案地址儲存到MySQL資料庫。這種方式也是可行的。

但是,檔案系統到了後期會變的很難管理,同時不利於擴充套件,此外我想做分散式檔案系統也顯得不那麼容易。而GridFS卻正好相反,它基於MongoDB的檔案系統,便於管理和擴充套件。

當然了,還有其它的一些分散式檔案儲存系統如FastDFS,可以根據檔案儲存的實際情況來進行選擇。


檔案儲存有沒有做備份?

目前是手動備份。

後面計劃寫一個自動備份的指令碼來每日備份。


在專案上有沒有什麼搞不定的問題?

略。


對搞不定的問題你是怎麼處理的?

略。


你們專案怎麼測試?

略。


MyBatis#和$有什麼區別?

#{}是預編譯處理,${}是字串替換。

(1)mybatis在處理#{}時,會將sql中的#{}替換為?號,呼叫PreparedStatement的set方法來賦值。

(2)mybatis在處理${}時,就是把${}替換成變數的值。

(3)使用#{}可以有效的防止SQL隱碼攻擊,提高系統安全性。原因在於:預編譯機制。

預編譯是提前對SQL語句進行預編譯,而其後注入的引數將不會再進行SQL編譯。我們知道,SQL隱碼攻擊是發生在編譯的過程中,因為惡意注入了某些特殊字元,最後被編譯成了惡意的執行操作。而預編譯機制則可以很好的防止SQL隱碼攻擊。預編譯完成之後,SQL的結構已經固定,即便使用者輸入非法引數,也不會對SQL的結構產生影響,從而避免了潛在的安全風險。


Redis你用到它那些結構?

主要用到了String、Hash、Set。

String:常規key-value快取應用。用來存一些計數。

Hash: 鍵值(key => value)對集合。用來存一些物件,對應Java集合中的HashMap。

Set: set是string型別的無序集合。對應Java中的HashSet,用來存一些需要去重的資料。


多執行緒你瞭解多少?

  • 先說說多執行緒是個什麼:
    要說執行緒,就得先講,程式:程式可以簡單的理解為一個可以獨立執行的程式單位,它是執行緒的集合,程式就是有一個或多個執行緒構成的。而執行緒是程式中的實際執行單位,是作業系統進行運算排程的最小單位。可理解為執行緒是程式中的一個最小執行單元。
    那麼多執行緒就很容易理解:多執行緒就是指一個程式中同時有多個執行緒正在執行。

  • 再說說為什麼要用多執行緒?
    簡單說來,使用多執行緒就是為了提高CPU的利用效率。

  • 最後簡單說說執行緒的建立:在Java中有三種執行緒建立方式。
    繼承 Thread 類建立執行緒類
    實現Runnable介面建立執行緒類
    使用 Callable 和 Future 建立執行緒


怎麼保證執行緒安全?

保證執行緒安全有以下幾種方式:

  • Synchronized 關鍵字:被 Synchronized 關鍵字描述的方法或程式碼塊在多執行緒環境下同一時間只能由一個執行緒進行訪問,在持有當前 Monitor 的執行緒執行完成之前,其他執行緒想要呼叫相關方法就必須進行排隊,知道持有持有當前 Monitor 的執行緒執行結束,釋放 Monitor ,下一個執行緒才可獲取 Monitor 執行。
  • Volatile 關鍵字:被 Volatile 關鍵字描述變數的操作具有可見性和有序性(禁止指令重排)
  • java.util.concurrent.atomic原子操作:ava.util.concurrent.atomic 包提供了一系列的 AtomicBoolean、AtomicInteger、AtomicLong 等類。使用這些類來宣告變數可以保證對其操作具有原子性來保證執行緒安全。
  • Lock:Lock 也是 java.util.concurrent 包下的一個介面,定義了一系列的鎖操作方法。Lock 介面主要有 ReentrantLock,ReentrantReadWriteLock.ReadLock,ReentrantReadWriteLock.WriteLock 實現類。與 Synchronized 不同是 Lock 提供了獲取鎖和釋放鎖等相關介面,使得使用上更加靈活,同時也可以做更加複雜的操作。

怎麼管理執行緒?

通常,會使用執行緒池來管理執行緒。

在 JDK 1.5 之後推出了相關的 api,常見的建立執行緒池方式有以下幾種:

  • Executors.newCachedThreadPool():無限執行緒池。
  • Executors.newFixedThreadPool(nThreads):建立固定大小的執行緒池。
  • Executors.newSingleThreadExecutor():建立單個執行緒的執行緒池。

其實看這三種方式建立的原始碼就會發現:

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

實際上還是利用 ThreadPoolExecutor 類實現的。

通常我們都是使用:

	
threadPool.execute(new Job());

這樣的方式來提交一個任務到執行緒池中,所以核心的邏輯就是 execute() 函式了。

執行緒池一共有五種狀態, 分別是:

  • RUNNING :能接受新提交的任務,並且也能處理阻塞佇列中的任務;
  • SHUTDOWN:關閉狀態,不再接受新提交的任務,但卻可以繼續處理阻塞佇列中已儲存的任務。線上程池處於 RUNNING 狀態時,呼叫 shutdown()方法會使執行緒池進入到該狀態。(finalize() 方法在執行過程中也會呼叫shutdown()方法進入該狀態);
  • STOP:不能接受新任務,也不處理佇列中的任務,會中斷正在處理任務的執行緒。線上程池處於 RUNNING 或 SHUTDOWN 狀態時,呼叫 shutdownNow() 方法會使執行緒池進入到該狀態;
  • TIDYING:如果所有的任務都已終止了,workerCount (有效執行緒數) 為0,執行緒池進入該狀態後會呼叫 terminated() 方法進入TERMINATED 狀態。
  • TERMINATED:在terminated() 方法執行完後進入該狀態,預設terminated()方法中什麼也沒有做。
    進入TERMINATED的條件如下:
    • 執行緒池不是RUNNING狀態;
    • 執行緒池狀態不是TIDYING狀態或TERMINATED狀態;
    • 如果執行緒池狀態是SHUTDOWN並且workerQueue為空;
    • workerCount為0;
    • 設定TIDYING狀態成功。

下圖為執行緒池的狀態轉換過程:

在這裡插入圖片描述
再看看Excute方法的執行:

在這裡插入圖片描述

1、獲取當前執行緒池的狀態。
2、當前執行緒數量小於 coreSize 時建立一個新的執行緒執行。
3、如果當前執行緒處於執行狀態,並且寫入阻塞佇列成功。
4、雙重檢查,再次獲取執行緒狀態;如果執行緒狀態變了(非執行狀態)就需要從阻塞佇列移除任務,並嘗試判斷執行緒是否全部執行完畢。同時執行拒絕策略。
5、如果當前執行緒池為空就新建立一個執行緒並執行。
6、如果在第三步的判斷為非執行狀態,嘗試新建執行緒,如果失敗則執行拒絕策略。

當前SpringBoot比較流行,我們可以發揮Spring的特性,由Spring來替我們管理執行緒:

@Configuration
public class TreadPoolConfig {
    /**
     * 消費佇列執行緒
     * @return
     */
    @Bean(value = "consumerQueueThreadPool")
    public ExecutorService buildConsumerQueueThreadPool(){
        ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
                .setNameFormat("consumer-queue-thread-%d").build();
        ExecutorService pool = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<Runnable>(5),namedThreadFactory,new ThreadPoolExecutor.AbortPolicy());
        return pool ;
    }
}

使用時:

@Resource(name = "consumerQueueThreadPool")
private ExecutorService consumerQueueThreadPool;
@Override
public void execute() {
    //消費佇列
    for (int i = 0; i < 5; i++) {
        consumerQueueThreadPool.execute(new ConsumerQueueThread());
    }
}

其實也挺簡單,就是建立了一個執行緒池的 bean,在使用時直接從 Spring 中取出即可。


假如有一個List,其中存的是使用者User物件,使用者物件有很多屬性,我要根據其中的年齡屬性對List排序,這個該怎麼辦?

可以通過Collections類的sort方法。但需要注意,使用sort方法的時候:

  • 要麼 User類實現Comparable介面,並在類中編寫public int compareTo(T o)方法
public class User implements Comparable<User> {
	private int age;
	private String name;
	private String sex;

	@Override
	public int compareTo(User o) {
		if (this.getAge() > o.getAge()) {
			return 1;
		} else if (this.getAge() < o.getAge()) {
			return -1;
		} else {
			return 0;
		}
	}
  // ……
}
		List<User> userList=new ArrayList();
		userList.add(new User(10, "王二", "男"));
		userList.add(new User(8, "張三", "男"));
		userList.add(new User(17, "李四", "女"));
		Collections.sort(userList);
		System.out.println(userList);
  • 或者在排序的時候,給sort()方法傳入一個比較器。具體來說,就是傳入一個實現比較器介面的匿名內部類。
		List<User> userList=new ArrayList();
		userList.add(new User(10, "王二", "男"));
		userList.add(new User(8, "張三", "男"));
		userList.add(new User(17, "李四", "女"));
		//Collections.sort(userList);
		Collections.sort(userList, new Comparator<User>() {

			@Override
			public int compare(User o1,User o2) {
				if(o1.getAge()>o2.getAge()) {
					return 1;
				}else if(o1.getAge()<o2.getAge()) {
					return -1;
				}else {
					return 0;
				}
			}
		});
		System.out.println(userList);

在Java8以後可以使用Lamda表示式來進行函式式地程式設計:

userList.sort((a, b) -> Integer.compare(a.getAge(), b.getAge()));

jdk 1.8的Stream用的多嗎?

Stream 作為 Java 8 的一大亮點,它與 java.io 包裡的 InputStream 和 OutputStream 是完全不相關的東西。

Java 8 中的 Stream 是對集合(Collection)物件功能的增強,它專注於對集合物件進行各種非常便利、高效的聚合操作(aggregate operation),或者大批量資料操作 (bulk data operation)。
下面是使用流的過程:
在這裡插入圖片描述

下面是一個使用流的例項,用於List的迭代:

		List<String> stringList = new ArrayList<String>();

		stringList.add("one");
		stringList.add("two");
		stringList.add("three");
		stringList.add("one");

		Stream<String> stream = stringList.stream();

		stream.forEach( element -> { System.out.println(element); });


JWT知道嗎?

JSON Web Token(縮寫 JWT)是目前最流行的跨域認證解決方案。

傳統的session認證一般是這樣的流程:

  • 1、使用者向伺服器傳送使用者名稱和密碼。
* 2、伺服器驗證通過後,在當前對話(session)裡面儲存相關資料,比如使用者角色、登入時間等等。
  • 3、伺服器向使用者返回一個 session_id,寫入使用者的 Cookie。

  • 4、使用者隨後的每一次請求,都會通過 Cookie,將 session_id 傳回伺服器。

  • 5、伺服器收到 session_id,找到前期儲存的資料,由此得知使用者的身份。

在這裡插入圖片描述

這種模式的問題在於,擴充套件性(scaling)不好。單機當然沒有問題,如果是伺服器叢集,或者是跨域的服務導向架構,就要求 session 資料共享,每臺伺服器都能夠讀取 session。

一種解決方案是 session共享,將session持久化或者存入快取。各種服務收到請求後,都向持久層或快取請求資料。這種方案的優點是架構清晰,缺點是工程量比較大。另外,持久層或者快取萬一掛了,就會認證失敗。

另一種方案是伺服器索性不儲存 session 資料了,所有資料都儲存在客戶端,每次請求都發回伺服器。JWT 就是這種方案的一個代表。

JWT認證流程:

  • 1、 使用者使用賬號和密碼發出post請求;
  • 2、 伺服器使用私鑰建立一個jwt;
  • 3、 伺服器返回這個jwt給瀏覽器;
  • 4、 瀏覽器將該jwt串在請求頭中像伺服器傳送請求;
  • 5、 伺服器驗證該jwt;
  • 6、 返回響應的資源給瀏覽器。

在這裡插入圖片描述


資料庫事務知道嗎?

事務(TRANSACTION)是作為單個邏輯工作單元執行的一系列操作, 這些操作作為一個整體一起向系統提交,要麼都執行、要麼都不執行 。

事務是一個不可分割的工作邏輯單元事務必須具備以下四個屬性,簡稱 ACID 屬性:

  • 原子性(Atomicity) :事務是一個完整的操作。事務的各步操作是不可分的(原子的);要麼都執行,要麼都不執行。
  • 一致性(Consistency): 當事務完成時,資料必須處於一致狀態。
  • 隔離性(Isolation) :對資料進行修改的所有併發事務是彼此隔離的, 這表明事務必須是獨立的,它不應以任何方式依賴於或影響其他事務。
  • 永久性(Durability) : 事務完成後,它對資料庫的修改被永久保持,事務日誌能夠保持事務的永久性

你寫的程式碼用到事務嗎?

通過在方法加註解 @Transactional 來實現宣告式的事務。

Spring 事務管理分為編碼式和宣告式的兩種方式。程式設計式事務指的是通過編碼方式實現事務;宣告式事務基於 AOP,將具體業務邏輯與事務處理解耦。宣告式事務管理使業務程式碼邏輯不受汙染, 因此在實際使用中宣告式事務用的比較多。宣告式事務有兩種方式,一種是在配置檔案(xml)中做相關的事務規則宣告,另一種是基於 @Transactional 註解的方式。


常用的檢索優化方式有哪些?

  • 1、查詢語句中不要使用select *
  • 2、儘量減少子查詢,使用關聯查詢(left join,right join,inner join)替代
  • 3、減少使用IN或者NOT IN ,使用exists,not exists或者關聯查詢語句替代
  • 4、or 的查詢儘量用 union或者union all 代替(在確認沒有重複資料或者不用剔除重複資料時,union all會更好)
  • 5、應儘量避免在 where 子句中使用!=或<>操作符,否則將引擎放棄使用索引而進行全表掃描。
  • 6、應儘量避免在 where 子句中對欄位進行 null 值判斷,否則將導致引擎放棄使用索引而進行全表掃描,如: select id from t where num is null 可以在num上設定預設值0,確保表中num列沒有null值,然後這樣查詢: select id from t where num=0

Linux瞭解多少?

(這裡應該是想問用過的命令)日產工作中,下面這些命令經常用到:

  • 檢視當前目錄:pwd
  • 切換目錄 : cd
  • 檢視目錄下的檔案 :ls/ls -lh
  • 建立目錄:mkdir
  • 啟動war包:java -jar xx.war
  • 後臺啟動war包:nohup java -jar * xx.war&
  • 查詢程式:ps –aux|grep java
  • 殺死程式:kill -9 pid



參考:

【1】:SpringBoot學習筆記(十一:使用MongoDB儲存檔案 )
【2】:GridFS 基於 MongoDB 的分散式檔案儲存系統
【3】:Linux下shell指令碼實現mongodb定時自動備份
【4】:Mybatis中#{}和${}的區別是什麼
【5】:Redis五種資料型別及應用場景
【6】:Redis五種資料型別及應用場景
【7】:面試官:說說什麼是執行緒安全?一圖帶你瞭解java執行緒安全
【8】:如何優雅的使用和理解執行緒池
【9】:深入理解 Java 執行緒池:ThreadPoolExecutor
【10】:透徹的掌握 Spring 中 @Transactional的使用
【11】:SpringBoot學習筆記(十三:JWT )
【12】:Java8 Stream

相關文章