一年經驗Java開發0713面試

@

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

略。

你們的文件怎麼存儲的?

我們的文件是存儲在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

Tags: