高級Java開發面試常用題的答案1

一、數據結構與算法基礎

· 說一下幾種常見的排序算法和分別的複雜度。

· 用Java寫一個冒泡排序算法


 
/** 現在有一個包含1000個數的數組,僅前面100個無序,後面900個都已排好序且都大於前面100個數字,那麼在第一趟遍歷後,最後發生交換的位置必定小於100,且這個位置之後的數據必定已經有序了,也就是這個位置以後的數據不需要再排序了,於是記錄下這位置,第二次只要從數組頭部遍歷到這個位置就可以了。如果是對於上面的冒泡排序算法2來說,雖然也只排序100次,但是前面的100次排序每次都要對後面的900個數據進行比較,而對於現在的排序算法3,只需要有一次比較後面的900個數據,之後就會設置尾邊界,保證後面的900個數據不再被排序。 */ public static void bubbleSort(int [] a, int n){ int j , k; int flag = n ;//flag來記錄最後交換的位置,也就是排序的尾邊界 while (flag > 0){//排序未結束標誌 k = flag; //k 來記錄遍歷的尾邊界 flag = 0; for(j=1; j<k; j++){ if(a[j-1] > a[j]){//前面的數字大於後面的數字就交換 //交換a[j-1]和a[j] int temp; temp = a[j-1]; a[j-1] = a[j]; a[j]=temp; //表示交換過數據; flag = j;//記錄最新的尾邊界. } } } } 

· 描述一下鏈式存儲結構。

它不要求邏輯上相鄰的元素在物理位置上也相鄰。因此它沒有順序存儲結構所具有的弱點,同時也失去了順序表可隨機存取的優點。

其特點主要表現爲:

1、比順序存儲結構的存儲密度小;

2、插入、刪除靈活,結點可以被插入到鏈表的任何位置,首、中、末都可以,而且不必要移動結點中的指針;

3、鏈表的大小可以按需伸縮,是一種動態存儲結構,其實現的集合在增、刪方面性能更高;

4、查找結點時的效率就相對數組較低,只能從第一個結點開始順着鏈表逐個查找(這是他的缺點)。

· 如何遍歷一棵二叉樹?

二叉樹的遍歷分爲三種:

前序遍歷:按照“根左右”,先遍歷根節點,再遍歷左子樹 ,再遍歷右子樹

中序遍歷:按照“左根右“,先遍歷左子樹,再遍歷根節點,最後遍歷右子樹

後續遍歷:按照“左右根”,先遍歷左子樹,再遍歷右子樹,最後遍歷根節點

其中前,後,中指的是每次遍歷時候的根節點被遍歷的順序


 
package com.tree; import java.util.ArrayList; import java.util.List; public class Tree { private Node root; private List<Node> list=new ArrayList<Node>(); public Tree(){ init(); } //樹的初始化:先從葉節點開始,由葉到根 public void init(){ Node x=new Node("X",null,null); Node y=new Node("Y",null,null); Node d=new Node("d",x,y); Node e=new Node("e",null,null); Node f=new Node("f",null,null); Node c=new Node("c",e,f); Node b=new Node("b",d,null); Node a=new Node("a",b,c); root =a; } //定義節點類: private class Node{ private String data; private Node lchid;//定義指向左子樹的指針 private Node rchild;//定義指向右子樹的指針 public Node(String data,Node lchild,Node rchild){ this.data=data; this.lchid=lchild; this.rchild=rchild; } } /** * 對該二叉樹進行前序遍歷 結果存儲到list中 前序遍歷:ABDXYCEF */ public void preOrder(Node node) { list.add(node); //先將根節點存入list //如果左子樹不爲空繼續往左找,在遞歸調用方法的時候一直會將子樹的根存入list,這就做到了先遍歷根節點 if(node.lchid != null) { preOrder(node.lchid); } //無論走到哪一層,只要當前節點左子樹爲空,那麼就可以在右子樹上遍歷,保證了根左右的遍歷順序 if(node.rchild != null) { preOrder(node.rchild); } } /** * 對該二叉樹進行中序遍歷 結果存儲到list中 */ public void inOrder(Node node) { if(node.lchid!=null){ inOrder(node.lchid); } list.add(node); if(node.rchild!=null){ inOrder(node.rchild); } } /** * 對該二叉樹進行後序遍歷 結果存儲到list中 */ public void postOrder(Node node) { if(node.lchid!=null){ postOrder(node.lchid); } if(node.rchild!=null){ postOrder(node.rchild); } list.add(node); } /** * 返回當前數的深度 * 說明: * 1、如果一棵樹只有一個結點,它的深度爲1。 * 2、如果根結點只有左子樹而沒有右子樹,那麼樹的深度是其左子樹的深度加1; * 3、如果根結點只有右子樹而沒有左子樹,那麼樹的深度應該是其右子樹的深度加1; * 4、如果既有右子樹又有左子樹,那該樹的深度就是其左、右子樹深度的較大值再加1。 * * @return */ public int getTreeDepth(Node node) { if(node.lchid == null && node.rchild == null) { return 1; } int left=0,right = 0; if(node.lchid!=null) { left = getTreeDepth(node.lchid); } if(node.rchild!=null) { right = getTreeDepth(node.rchild); } return left>right?left+1:right+1; } //得到遍歷結果 public List<Node> getResult() { return list; } public static void main(String[] args) { Tree tree=new Tree(); System.out.println("根節點是:"+tree.root); //tree.preOrder(tree.root); tree.postOrder(tree.root); for(Node node:tree.getResult()){ System.out.println(node.data); } System.out.println("樹的深度是"+tree.getTreeDepth(tree.root)); } } 

二叉樹與一般樹的區別

  • 一般樹的子樹不分次序,而二叉樹的子樹有左右之分.
  • 由於二叉樹也是樹的一種,所以大部分的樹的概念,對二叉樹也適用.
  • 二叉樹的存貯:每個節點只需要兩個指針域(左節點,右節點),有的爲了操作方便也會 增加指向父級節點的指針,除了指針域以外,還會有一個數據域用來保存當前節點的信息

二叉樹的特點:

  • 性質1:在二叉樹的第i層上至多有2^(i-1)個節點(i >= 1)
  • 性質2:深度爲k的二叉樹至多有2^k-1個節點(k >=1)
  • 性質3:對於任意一棵二叉樹T而言,其葉子節點數目爲N0,度爲2的節點數目爲N2,則有N0 = N2 + 1。
  • 性質4:具有n個節點的完全二叉樹的深度。

· 倒排一個LinkedList。

根據LinkedList的實現,LinkedList的底層是雙向鏈表,它在get任何一個位置的數據的時候,都會把前面的數據走一遍。用迭代器或者foreach循環(foreach循環的原理就是迭代器)去遍歷LinkedList即可,這種方式是直接按照地址去找數據的,將會大大提升遍歷LinkedList的效率。


 
public static <T> LinkedList<T> reverse(LinkedList<T> linkedList) { if (linkedList == null) return linkedList; LinkedList<T> temp_linkedlist = new LinkedList<T>(); for (T item: linkedList) { temp_linkedlist.addLast(item); } return temp_linkedlist; } 

· 用Java寫一個遍歷目錄下面的所有文件。


 
public static void foreachFileList(String filePath) throws IOException { LinkedList<File> linkedList = new LinkedList<File>(); File file = new File(filePath); if (file.exists()) { linkedList.add(file); while (true) { file = linkedList.poll(); if (file == null) break; File[] fileList = file.listFiles(); for (File fileItem : fileList) { if (fileItem.isDirectory()) { linkedList.add(fileItem); continue;//for } if (fileItem.isFile()) System.out.println(fileItem.getCanonicalPath()); } } } } 

二、Java基礎

· 接口與抽象類的區別?

  • 一個類可以實現多個接口,但只能繼承最多一個抽象類
  • 抽象類可以包含具體的方法;接口所有的方法都是抽象的(不管是否對接口聲明都是抽象的)(jdk1.7以前,jdk1.8開始新增功能接口中有default 方法,有興趣自己研究)
  • 抽象類可以聲明和使用字段;接口則不能,但是可以創建靜態的final常量
  • 抽象類中的方法可以是public、protected、private或者默認的package;接口的方法都是public(不管是否聲明,接口都是公開的)
  • 抽象類可以定義構造函數,接口不能。
  • 接口被聲明爲public,省略後,包外的類不能訪問接口

· Java中的異常有哪幾類?分別怎麼使用?

  • Throwable包含了錯誤(Error)和異常(Excetion兩類)
  • Exception又包含了 運行時異常(RuntimeException, 又叫非檢查異常) 和 非運行時異常(又叫檢查異常)(1) Error是程序無法處理了, 如果OutOfMemoryError、OutOfMemoryError等等, 這些異常發生時, java虛擬機一般會終止線程 .
  • -(2) 運行時異常都是RuntimeException類及其子類,如 NullPointerException、IndexOutOfBoundsException等, 這些異常是不檢查的異常, 是在程序運行的時候可能會發生的, 所以程序可以捕捉, 也可以不捕捉. 這些錯誤一般是由程序的邏輯錯誤引起的, 程序應該從邏輯角度去儘量避免.
  • (3) 檢查異常是運行時異常以外的異常, 也是Exception及其子類, 這些異常從程序的角度來說是必須經過捕捉檢查處理的, 否則不能通過編譯. 如IOException、SQLException等

· 常用的集合類有哪些?比如List如何排序?

  • 常用的集合分爲List(有序排放)、Map(以名和值一一對應的存放)、Set(既無序也沒名).在這三者之中其中List和Set是Collection接口的子接口,而Map不是Collection接口的子接口.
  • List常用有:ArrayList和LinkedList,Vecotr(線程安全)
  • Set常用有: TreeSet, HashSet 元素不可重複,內部結構用HashMap,Key爲Set的item值,value爲一個固定的常量。java.util.Collections.newHashSetFromMap(),內部其實質還是通過ConcurrentHashMap實現線程安全的。
  • Map: TreeMap和LinkedHashMap,HashMap,HashTable(線程安全)
  • sort()方法排序的本質其實也是藉助Comparable接口和Comparator接口的實現,一般有2種用法:
  1. 直接將需要排序的list作爲參數傳入,此時list中的對象必須實現了Comparable接口,然後sort會按升序的形式對元素進行排序;
  2. 傳入list作爲第一個參數,同時追加一個Comparator的實現類作爲第二個參數,然後sort方法會根據Comparator接口的實現類的邏輯,按升序進行排序;

· ArrayList和LinkedList內部的實現大致是怎樣的?他們之間的區別和優缺點?

  • Linkedlist集合的優勢:添加元素時可以指定位置,比ArrayList集合添加元素要快很多。
  • Linkedlist在get很慢,LinkedList在get任何一個位置的數據的時候,都會把前面的數據走一遍。儘量不使用,而使用foreach LinkedList的方式來直接取得數據。
  • 這兩種方式各有優缺,爲更好的使用可以將這兩者進行聯合使用,使用Linkedlist集合進行存儲和添加元素,使用Arraylist集合進行get獲取元素。

· 內存溢出是怎麼回事?請舉一個例子?

  • 內存溢出(out of memory)通俗理解就是內存不夠,在計算機程序中通俗的理解就是開闢的內存空間得不到釋放。
  • OOM有堆溢出,棧溢出,方法區溢出(主要是動態生成class的處理過多)

· ==和equals的區別?

  • ==號在比較基本數據類型時比較的是值,而用==號比較兩個對象時比較的是兩個對象的地址值
  • Object類中equals()方法底層依賴的是==號,那麼,在所有沒有重寫equals()方法的類中,調用equals()方法其實和使用==號的效果一樣,然而,Java提供的所有類中,絕大多數類都重寫了equals()方法,重寫後的equals()方法一般都是比較兩個對象的值

· hashCode方法的作用?

  1. hashCode的存在主要是用於查找的快捷性,如Hashtable,HashMap等,hashCode是用來在散列存儲結構中確定對象的存儲地址的;
  2. 如果兩個對象相同,就是適用於equals(Java.lang.Object) 方法,那麼這兩個對象的hashCode一定要相同;
  3. 如果對象的equals方法被重寫,那麼對象的hashCode也儘量重寫,並且產生hashCode使用的對象,一定要和equals方法中使用的一致,否則就會違反上面提到的第2點;
  4. 兩個對象的hashCode相同,並不一定表示兩個對象就相同,也就是不一定適用於equals(java.lang.Object) 方法,只能夠說明這兩個對象在散列存儲結構中,如Hashtable,他們“存放在同一個籃子裏”。

· NIO是什麼?適用於何種場景?

  • JDK引入了一種基於通道和緩衝區的 I/O 方式,它可以使用 Native 函數庫直接分配堆外內存,然後通過一個存儲在 Java 堆的 DirectByteBuffer 對象作爲這塊內存的引用進行操作,避免了在 Java 堆和 Native 堆中來回複製數據。
  • NIO 是一種同步非阻塞的 IO 模型。同步是指線程不斷輪詢 IO 事件是否就緒,非阻塞是指線程在等待 IO 的時候,可以同時做其他任務。同步的核心就是 Selector,Selector 代替了線程本身輪詢 IO 事件,避免了阻塞同時減少了不必要的線程消耗;非阻塞的核心就是通道和緩衝區,當 IO 事件就緒時,可以通過寫道緩衝區,保證 IO 的成功,而無需線程阻塞式地等待。

· HashMap實現原理,如何保證HashMap的線程安全?

HashMap實際上是一個“鏈表散列”的數據結構,即數組和鏈表的結合體。HashMap底層就是一個數組結構,數組中的每一項又是一個鏈表。

  • =使用 java.util.Hashtable 類,此類是線程安全的。Hashtable是通過每個方法用synchronized來處理,性能不及ConcurrentHashMap
  • 使用 java.util.concurrent.ConcurrentHashMap,此類是線程安全的。採用了分段鎖實現同步。
  • 使用 java.util.Collections.synchronizedMap() 方法包裝 HashMap object,得到線程安全的Map,並在此Map上進行操作。

· JVM內存結構,爲什麼需要GC?

垃圾回收可以有效的防止內存泄露,有效的使用可以使用的內存,簡化代碼開發。

· NIO模型,select/epoll的區別,多路複用的原理

(過長,沒有回答)

· Java中一個字符佔多少個字節,擴展再問int, long, double佔多少字節

Java char: utf-16:2個字節, int-4, long-8,double-9

· 創建一個類的實例都有哪些辦法?

  1. 關鍵字 new。工廠模式是對這種方式的包裝;
  2. 類實現克隆接口,克隆一個實例。原型模式是一個應用實例;
  3. 用該類的加載器,newinstance。java的反射,反射使用實例:Spring的依賴注入、切面編程中動態代理等取得
  4. 通過IO流反序列化讀取一個類,獲得實例。

· final/finally/finalize的區別?

  1. final:如果一個類被final修飾,意味着該類不能派生出新的子類,不能作爲父類被繼承。因此一個類不能被聲明爲abstract,又被聲明爲final。將變量或方法聲明爲final。可以保證他們在使用的時候不被改變。其初始化可以在兩個地方:一是其定義的地方,也就是在final變量在定義的時候就對其賦值;二是在構造函數中。這兩個地方只能選其中的一個。被聲明爲final的方法也只能使用,不能重寫。
  2. finally:在異常處理的時候,提供finally塊在成功或失敗都可以執行任何的清除操作。
  3. finalize:finalize是方法名,java技術允許使用finalize()方法在垃圾收集器將對象從內存中清除出去之前做必要的清理工作。finalize()方法是在垃圾收集器刪除對象之前對這個對象調用的,可以從Object.finalize()繼承下來。

· Session/Cookie的區別?

  • cookie是客戶端的會話狀態的一種儲存機制。一般限制4k以內。session是一種服務器端的信息管理機制。
  • session產生的session_id放在cookie裏面,如果用戶把cookie禁止掉,可以通過在url中保留session_id

· String/StringBuffer/StringBuilder的區別,擴展再問他們的實現?

StringBuilder:線程非安全的,StringBuffer:線程安全的,三者在執行速度方面的比較:StringBuilder > StringBuffer > String

· Servlet的生命週期?

  1. Servlet 通過調用 init () 方法進行初始化。
  2. Servlet 調用 service() 方法來處理客戶端的請求。
  3. Servlet 通過調用 destroy() 方法終止(結束)。
  4. 最後,Servlet 是由 JVM 的垃圾回收器進行垃圾回收的。

· 如何用Java分配一段連續的1G的內存空間?需要注意些什麼?

使用ArrayList來分配,注意堆內存不足造面OOM

· Java有自己的內存回收機制,但爲什麼還存在內存泄漏的問題呢?

主要是沒有釋放對象引用造成的內存泄漏,比如下例:


 
class MyList{ /* * 此處只爲掩飾效果,並沒有進行封裝之類的操作 * 將List集合用關鍵字 static 聲明,這時這個集合將不屬於任MyList 對象,而是一個類成員變量 */ public static List<String> list = new ArrayList<String>(); } class Demo{ public static void main(String[] args) { MyList list = new MyList(); list.list.add("123456"); // 此時即便我們將 list指向null,仍然存在內存泄漏,因爲MyList中的list是靜態的,它屬於類所有而不屬於任何特定的實例  list = null; } } 

· 什麼是java序列化,如何實現java序列化?(寫一個實例)?

序列化就是一種用來處理對象流的機制,所謂對象流也就是將對象的內容進行流化。可以對流化後的對象進行讀寫操作,也可將流化後的對象傳輸於網絡之間。Java的序列化需要實現Serializable接口。


 
//序列化後生成指定文件路徑  File file = new File("D:" + File.separator + "person.ser") ; ObjectOutputStream oos = null ; //裝飾流(流) oos = new ObjectOutputStream(new FileOutputStream(file)) ; //實例化類  Person per = new Person("張三",30) ; oos.writeObject(per) ; //把類對象序列化 oos.close() ; 

· String s = new String("abc");創建了幾個 String Object?

兩個對象,一個是“abc”對象,在常量池中創建;一個是new關鍵字創建的s對象指向“abc”。

”我自己是一名從事了十餘年的後端的老程序員,辭職後目前在做講師,近期我花了一個月整理了一份最適合2018年學習的JAVA乾貨(裏面有高可用、高併發、高性能及分佈式、Jvm性能調優、Spring源碼,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個知識點的架構資料)從事後端的小夥伴們都可以來了解一下的,這裏是程序員祕密聚集地,各位還在架構師的道路上掙扎的小夥伴們速來。“

加QQ羣:585550789(名額有限哦!)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章