本人在傳統軟件公司工做了三年,在大學又學習了一年多了。如今,又面臨着再次找工做。以前誰說的「最好的穩定,是上一份工做失去以後,立刻就能找到下一份。」曾有一段時間深覺得然,但是真的要找個合適的確實不那麼容易啊!本人現但願謀得一份Java中級工程師的職位,望你們能推薦一下。java
面試的這個公司是同窗推薦去的,所以也省了不少事情。主要談一下技術面試,雖然他們主要開發語言是PHP,可是面試過程當中卻是沒怎麼涉及到PHP的問題。主要仍是一些基本的知識,現將主要的內容總結以下:node
============================華麗分割線============================================面試
1.介紹cookie和session的區別,怎麼獲取與使用?(這個問題比較開放,可深可淺,如今將這裏涉及的主要問題總計以下答案)算法
答:數據庫
cookie機制採用的是在客戶端保持狀態的方式,而session機制採用的是在服務器端保持狀態的方式。數組
同時咱們也看到,因爲服務器端保持狀態的方案在客戶端也須要保存一個標識,因此session機制可能須要藉助於cookie機制來達到保存標識的目的,但實際上還有其餘選擇。瀏覽器
若是不設置過時時間,則表示這個cookie生命週期爲瀏覽器會話期間,只要關閉瀏覽器窗口,cookie就消失了。這種生命期爲瀏覽會話期的cookie被稱爲會話cookie。會話cookie通常不保存在硬盤上而是保存在內存裏。服務器
若是設置了過時時間,瀏覽器就會把cookie保存到硬盤上,關閉後再次打開瀏覽器,這些cookie依然有效直到超過設定的過時時間。cookie
存儲在硬盤上的cookie能夠在不一樣的瀏覽器進程間共享,好比兩個IE窗口。而對於保存在內存的cookie,不一樣的瀏覽器有不一樣的處理方式。網絡
當用戶在某個網站註冊後,就會收到一個唯一用戶ID的cookie。客戶後來從新鏈接時,這個用戶ID會自動返回,服務器對它進行檢查,肯定它是否爲註冊用戶且選擇了自動登陸,從而使用戶務需給出明確的用戶名和密碼,就能夠訪問服務器上的資源。
網站可使用cookie記錄用戶的意願。對於簡單的設置,網站能夠直接將頁面的設置存儲在cookie中完成定製。然而對於更復雜的定製,網站只需僅將一個唯一的標識符發送給用戶,由服務器端的數據庫存儲每一個標識符對應的頁面設置。
1.建立Cookie對象
2.設置最大時效
3.將Cookie放入到HTTP響應報頭
若是你建立了一個cookie,並將他發送到瀏覽器,默認狀況下它是一個會話級別的cookie:存儲在瀏覽器的內存中,用戶退出瀏覽器以後被刪除。若是你但願瀏覽器將該cookie存儲在磁盤上,則須要使用maxAge,並給出一個以秒爲單位的時間。將最大時效設爲0則是命令瀏覽器刪除該cookie。
發送cookie須要使用HttpServletResponse的addCookie方法,將cookie插入到一個Set-Cookie HTTP請求報頭中。因爲這個方法並不修改任何以前指定的Set-Cookie報頭,而是建立新的報頭,所以咱們將這個方法稱爲是addCookie,而非setCookie。一樣要記住響應報頭必須在任何文檔內容發送到客戶端以前設置。
1.調用request.getCookie
要獲取有瀏覽器發送來的cookie,須要調用HttpServletRequest的getCookies方法,這個調用返回Cookie對象的數組,對應由HTTP請求中Cookie報頭輸入的值。
2.對數組進行循環,調用每一個cookie的getName方法,直到找到感興趣的cookie爲止
cookie與你的主機(域)相關,而非你的servlet或JSP頁面。於是,儘管你的servlet可能只發送了單個cookie,你也可能會獲得許多不相關的cookie。
例如:
String cookieName = 「userID」; Cookie cookies[] = request.getCookies(); if (cookies!=null){ for(int i=0;i<cookies.length;i++){ Cookie cookie = cookies[i]; if (cookieName.equals(cookie.getName())){ doSomethingWith(cookie.getValue()); } } }
A.調用HttpServletRequest.getCookies()獲取Cookie數組
B.在循環中檢索指定名字的cookie是否存在以及對應的值是否正確
C.若是是則退出循環並設置區別標識
D.根據區別標識判斷用戶是否爲初訪者從而進行不一樣的操做
不能僅僅由於cookie數組中不存在在特定的數據項就認爲用戶是個初訪者。若是cookie數組爲null,客戶多是一個初訪者,也多是因爲用戶將cookie刪除或禁用形成的結果。可是,若是數組非null,也不過是顯示客戶曾經到過你的網站或域,並不能說明他們曾經訪問過你的servlet。其它servlet、JSP頁面以及非Java Web應用均可以設置cookie,依據路徑的設置,其中的任何cookie都有可能返回給用戶的瀏覽器。
正確的作法是判斷cookie數組是否爲空且是否存在指定的Cookie對象且值正確。
屬性是從服務器發送到瀏覽器的報頭的一部分;但它們不屬於由瀏覽器返回給服務器的報頭。
所以除了名稱和值以外,cookie屬性只適用於從服務器輸出到客戶端的cookie;服務器端來自於瀏覽器的cookie並無設置這些屬性。
於是不要指望經過request.getCookies獲得的cookie中可使用這個屬性。這意味着,你不能僅僅經過設置cookie的最大時效,發出它,在隨後的輸入數組中查找適當的cookie,讀取它的值,修改它並將它存回Cookie,從而實現不斷改變的cookie值。
1.獲取cookie數組中專門用於統計用戶訪問次數的cookie的值
2.將值轉換成int型
3.將值加1並用原來的名稱從新建立一個Cookie對象
4.從新設置最大時效
5.將新的cookie輸出
session,中文常常翻譯爲會話,其原本的含義是指善始善終的一系列動做/消息,好比打電話是從拿起電話撥號到掛斷電話這中間的一系列過程能夠稱之爲一個session。
然而當session一詞與網絡協議相關聯時,它又每每隱含了「面向鏈接」和/或「保持狀態」這樣兩個含義。
session在Web開發環境下的語義又有了新的擴展,它的含義是指一類用來在客戶端與服務器端之間保持狀態的解決方案。有時候Session也用來指這種解決方案的存儲結構。
session機制是一種服務器端的機制,服務器使用一種相似於散列表的結構(也可能就是使用散列表)來保存信息。
但程序須要爲某個客戶端的請求建立一個session的時候,服務器首先檢查這個客戶端的請求裏是否包含了一個session標識-稱爲session id,若是已經包含一個session id則說明之前已經爲此客戶建立過session,服務器就按照session id把這個session檢索出來使用(若是檢索不到,可能會新建一個,這種狀況可能出如今服務端已經刪除了該用戶對應的session對象,但用戶人爲地在請求的URL後面附加上一個JSESSION的參數)。
若是客戶請求不包含session id,則爲此客戶建立一個session而且生成一個與此session相關聯的session id,這個session id將在本次響應中返回給客戶端保存。
A.保存session id的方式能夠採用cookie,這樣在交互過程當中瀏覽器能夠自動的按照規則把這個標識發送給服務器。
B.因爲cookie能夠被人爲的禁止,必須有其它的機制以便在cookie被禁止時仍然可以把session id傳遞迴服務器,常常採用的一種技術叫作URL重寫,就是把session id附加在URL路徑的後面,附加的方式也有兩種,一種是做爲URL路徑的附加信息,另外一種是做爲查詢字符串附加在URL後面。網絡在整個交互過程當中始終保持狀態,就必須在每一個客戶端可能請求的路徑後面都包含這個session id。
C.另外一種技術叫作表單隱藏字段。就是服務器會自動修改表單,添加一個隱藏字段,以便在表單提交時可以把session id傳遞迴服務器。
一個常見的錯誤是覺得session在有客戶端訪問時就被建立,然而事實是直到某server端程序(如Servlet)調用HttpServletRequest.getSession(true)這樣的語句時纔會被建立。
session在下列狀況下被刪除:
A.程序調用HttpSession.invalidate()
B.距離上一次收到客戶端發送的session id時間間隔超過了session的最大有效時間
C.服務器進程被中止
再次注意關閉瀏覽器只會使存儲在客戶端瀏覽器內存中的session cookie失效,不會使服務器端的session對象失效。
對全部的URL使用URL重寫,包括超連接,form的action,和重定向的URL。每一個引用你的站點的URL,以及那些返回給用戶的URL(即便經過間接手段,好比服務器重定向中的Location字段)都要添加額外的信息。
這意味着在你的站點上不能有任何靜態的HTML頁面(至少靜態頁面中不能有任何連接到站點動態頁面的連接)。所以,每一個頁面都必須使用servlet或JSP動態生成。即便全部的頁面都動態生成,若是用戶離開了會話並經過書籤或連接再次回來,會話的信息都會丟失,由於存儲下來的連接含有錯誤的標識信息-該URL後面的SESSION ID已通過期了。
僅當每一個頁面都是有表單提交而動態生成時,才能使用這種方法。單擊常規的<A HREF..>超文本連接並不產生表單提交,所以隱藏的表單域不能支持一般的會話跟蹤,只能用於一系列特定的操做中,好比在線商店的結帳過程
1.訪問與當前請求相關的會話對象
2.查找與會話相關的信息
3.存儲會話信息
4.廢棄會話數據
getSession()/getSession(true):當session存在時返回該session,不然新建一個session並返回該對象
getSession(false):當session存在時返回該session,不然不會新建session,返回null
setAttribute會替換任何以前設定的值;若是想要在不提供任何代替的狀況下移除某個值,則應使用removeAttribute。這個方法會觸發全部實現了HttpSessionBindingListener接口的值的valueUnbound
方法。
一般會話屬性的類型只要是Object就能夠了。除了null或基本類型,如int,double,boolean。
若是要使用基本類型的值做爲屬性,必須將其轉換爲相應的封裝類對象
A.只移除本身編寫的servlet建立的數據:
調用removeAttribute(「key」)將指定鍵關聯的值廢棄
B.刪除整個會話(在當前Web應用中):
調用invalidate,將整個會話廢棄掉。這樣作會丟失該用戶的全部會話數據,而非僅僅由咱們
servlet或JSP頁面建立的會話數據
C.將用戶從系統中註銷並刪除全部屬於他(或她)的會話
調用logOut,將客戶從Web服務器中註銷,同時廢棄全部與該用戶相關聯的會話(每一個Web應用至多一個)。這個操做有可能影響到服務器上多個不一樣的Web應用
public boolean isNew()方法若是會話還沒有和客戶程序(瀏覽器)發生任何聯繫,則這個方法返回true,這通常是由於會話是新建的,不是由輸入的客戶請求所引發的。
但若是isNew返回false,只不過是說明他以前曾經訪問該Web應用,並不表明他們曾訪問過咱們的servlet或JSP頁面。
由於session是與用戶相關的,在用戶以前訪問的每個頁面都有可能建立了會話。所以isNew爲false只能說用戶以前訪問過該Web應用,session能夠是當前頁面建立,也多是由用戶以前訪問過的頁面建立的。
正確的作法是判斷某個session中是否存在某個特定的key且其value是否正確
會話的超時由服務器來維護,它不一樣於Cookie的失效日期。首先,會話通常基於駐留內存的cookie
不是持續性的cookie,於是也就沒有截至日期。即便截取到JSESSIONID cookie,併爲它設定一個失效日期發送出去。瀏覽器會話和服務器會話也會大相徑庭。
當用戶關閉了瀏覽器雖然session cookie已經消失,但session對象仍然保存在服務器端
程序通常都是在用戶作log off的時候發個指令去刪除session,然而瀏覽器歷來不會主動在關閉以前通知服務器它將要被關閉,所以服務器根本不會有機會知道瀏覽器已經關閉。服務器會一直保留這個會話對象直到它處於非活動狀態超過設定的間隔爲止。
之因此會有這種錯誤的認識,是由於大部分session機制都使用會話cookie來保存session id,而關閉瀏覽器後這個session id就消失了,再次鏈接到服務器時也就沒法找到原來的session。
若是服務器設置的cookie被保存到硬盤上,或者使用某種手段改寫瀏覽器發出的HTTP請求報頭,把原來的session id發送到服務器,則再次打開瀏覽器仍然可以找到原來的session。偏偏是因爲關閉瀏覽器不會致使session被刪除,迫使服務器爲session設置了一個失效時間,當距離客戶上一次使用session的時間超過了這個失效時間時,服務器就能夠認爲客戶端已經中止了活動,纔會把session刪除以節省存儲空間。
由此咱們能夠得出以下結論:
關閉瀏覽器,只會是瀏覽器端內存裏的session cookie消失,但不會使保存在服務器端的session對象消失,一樣也不會使已經保存到硬盤上的持久化cookie消失。
一般session cookie是不能跨窗口使用的,當你新開了一個瀏覽器窗口進入相同頁面時,系統會賦予你一個新的session id,這樣咱們信息共享的目的就達不到了。
此時咱們能夠先把session id保存在persistent cookie中(經過設置session的最大有效時間),而後在新窗口中讀出來,就能夠獲得上一個窗口的session id了,這樣經過session cookie和persistent cookie的結合咱們就能夠實現了跨窗口的會話跟蹤。
因爲客戶的訪問次數是一個整型的變量,但session的屬性類型中不能使用int,double,boolean等基本類型的變量,因此咱們要用到這些基本類型的封裝類型對象做爲session對象中屬性的值
但像Integer是一種不可修改(Immutable)的數據結構:構建後就不能更改。這意味着每一個請求都必須建立新的Integer對象,以後使用setAttribute來代替以前存在的老的屬性的值。例如:
HttpSession session = request.getSession(); SomeImmutalbeClass value = (SomeImmutableClass)session.getAttribute(「SomeIdentifier」); if (value= =null){ value = new SomeImmutableClass(…); // 新建立一個不可更改對象 }else{ value = new SomeImmutableClass(calculatedFrom(value)); // 對value從新計算後建立新的對象 } session.setAttribute(「someIdentifier」,value); // 使用新建立的對象覆蓋原來的老的對象
使用可變的數據結構,好比數組、List、Map或含有可寫字段的應用程序專有的數據結構。經過這種方式,除非首次分配對象,不然不須要調用setAttribute。例如
HttpSession session = request.getSession(); SomeMutableClass value = (SomeMutableClass)session.getAttribute(「someIdentifier」); if(value = = null){ value = new SomeMutableClass(…); session.setAttribute(「someIdentifier」,value); }else{ value.updateInternalAttribute(…); // 若是已經存在該對象則更新其屬性而不需從新設置屬性 }
不可更改對象由於一旦建立以後就不能更改,因此每次要修改會話中屬性的值的時候,都須要調用setAttribute(「someIdentifier」,newValue)來代替原有的屬性的值,不然屬性的值不會被更新可更改對象由於其自身通常提供了修改自身屬性的方法,因此每次要修改會話中屬性的值的時候,只要調用該可更改對象的相關修改自身屬性的方法就能夠了。這意味着咱們就不須要調用setAttribute方法了。
=====================================華麗分割線=============================================
2.二叉樹遍歷的各類變種問題。(問的是二叉樹結點求和)
答:雖然數據結構用C++表述的話比較方便,以前學習的時候多用C++寫成。如今,要應聘Java工程師,所以,將這些都由JAVA實現。
二叉樹簡單介紹:
二叉樹是樹形結構的一個重要類型。許多實際問題抽象出來的數據結構每每是二叉樹的形式,即便是通常的樹也能簡單地轉換爲二叉樹,並且二叉樹的存儲結構及其算法都較爲簡單,所以二叉樹顯得特別重要。
二叉樹(BinaryTree)是n(n≥0)個結點的有限集,它或者是空集(n=0),或者由一個根結點及兩棵互不相交的、分別稱做這個根的左子樹和右子樹的二叉樹組成。
這個定義是遞歸的。因爲左、右子樹也是二叉樹, 所以子樹也可爲空樹。
對於二叉樹來說最主要、最基本的運算是遍歷。
遍歷二叉樹 是指以必定的次序訪問二叉樹中的每一個結點。所謂 訪問結點 是指對結點進行各類操做的簡稱。例如,查詢結點數據域的內容,或輸出它的值,或找出結點位置,或是執行對結點的其餘操做。遍歷二叉樹的過程實質是把二叉樹的結點進行線性排列的過程。假設遍歷二叉樹時訪問結點的操做就是輸出結點數據域的值,那麼遍歷的結果獲得一個線性序列。
從二叉樹的遞歸定義可知,一棵非空的二叉樹由根結點及左、右子樹這三個基本部分組成。所以,在任一給定結點上,能夠按某種次序執行三個操做:
(1)訪問結點自己(N),
(2)遍歷該結點的左子樹(L),
(3)遍歷該結點的右子樹(R)。
具體實現以下:(包括數據的構造,二叉樹遞歸遍歷,非遞歸遍歷,以及變種問題的解決方案。也能夠重構以後,直接做爲庫函數使用)
/** * 二叉樹的遞歸遍歷,非遞歸遍歷,已經衍生出來的其餘問題。 */ package com.algorithm.Tree; import java.util.LinkedList; import java.util.List; import java.util.Stack; /** * @author Administrator */ public class BinaryTree { private int data[]={1,2,3,5,9,8,5}; private static List<TreeNode> nodeList=null; /** * 建立B樹 */ public void createBTree(){ nodeList = new LinkedList<BinaryTree.TreeNode>(); for(int nodeIndex=0;nodeIndex<data.length;nodeIndex++){ nodeList.add(new TreeNode(data[nodeIndex])); } //create as the binary tree. for(int parentIndex=0;parentIndex<data.length/2-1;parentIndex++){ nodeList.get(parentIndex).leftTree=nodeList.get(parentIndex*2+1); nodeList.get(parentIndex).rightTree=nodeList.get(parentIndex*2+2); } int lastParentIndex = data.length / 2 - 1; // 左孩子 nodeList.get(lastParentIndex).leftTree = nodeList .get(lastParentIndex * 2 + 1); if (data.length % 2 == 1) { nodeList.get(lastParentIndex).rightTree = nodeList .get(lastParentIndex * 2 + 2); } } /** * 中序遍歷 * @param treeNode */ public void inOrderTraverse(TreeNode treeNode) { if (treeNode == null) return; inOrderTraverse(treeNode.leftTree); visit(treeNode); inOrderTraverse(treeNode.rightTree); } /** * 遞歸實現樹的求和。 * @param treeNode * @return */ public int sum(TreeNode treeNode){ int sum=0; if(treeNode == null){ sum=0; }else{ sum+=treeNode.getData(); sum+=sum(treeNode.leftTree); sum+=sum(treeNode.rightTree); } return sum; } /** * 非遞歸實現中序遍歷 * @param treeNode */ public void noRecInOrder(TreeNode treeNode){ Stack<BinaryTree.TreeNode> stack=new Stack<BinaryTree.TreeNode>(); while(treeNode!=null||stack.size()>0){ while(treeNode!=null){ stack.push(treeNode); treeNode=treeNode.leftTree; } if(stack.size()>0){ treeNode=stack.pop(); visit(treeNode); treeNode=treeNode.rightTree; } } } public int noRecSum(TreeNode treeNode){ int sum=0; Stack<BinaryTree.TreeNode> stack=new Stack<BinaryTree.TreeNode>(); while(treeNode!=null||stack.size()>0){ while(treeNode!=null){ stack.push(treeNode); treeNode=treeNode.leftTree; } if(stack.size()>0){ treeNode=stack.pop(); sum+=treeNode.getData(); treeNode=treeNode.rightTree; } } return sum; } private void visit(TreeNode treeNode){ treeNode.setVisted(true); System.out.print(treeNode.getData()+" "); } public static void main(String[] args) { BinaryTree bt=new BinaryTree(); bt.createBTree(); bt.inOrderTraverse(nodeList.get(0)); System.out.println("\n+++++++++++++"); System.out.println(bt.sum(nodeList.get(0))); System.out.println("\n+++++++++++++"); bt.noRecInOrder(nodeList.get(0)); System.out.println("\n+++++++++++++"); System.out.println(bt.noRecSum(nodeList.get(0))); } /** * 內部內,主要構造爲樹的節點。 */ private static class TreeNode{ private TreeNode leftTree; private TreeNode rightTree; private int data; private boolean isVisted=false;//是否已經訪問標識 TreeNode(int newData){ this.leftTree=null; this.rightTree=null; this.data = newData; this.setVisted(false); } public int getData(){ return data; } @SuppressWarnings("unused") public boolean isVisted() { return isVisted; } public void setVisted(boolean isVisted) { this.isVisted = isVisted; } } }
涉及到其餘問題,後續再論!