您好,登錄后才能下訂單哦!
用數組實現隊列時要注意 溢出 現象,這時我們可以采用循環數組的方式來解決,即將數組收尾相接。使用front指針指向隊列首位,tail指針指向隊列末位。
因為生命周期不同。局部變量在方法結束后就會被銷毀,但內部類對象并不一定,這樣就會導致內部類引用了一個不存在的變量。
所以編譯器會在內部類中生成一個局部變量的拷貝,這個拷貝的生命周期和內部類對象相同,就不會出現上述問題。
但這樣就導致了其中一個變量被修改,兩個變量值可能不同的問題。為了解決這個問題,編譯器就要求局部變量需要被final修飾,以保證兩個變量值相同。
在JDK8之后,編譯器不要求內部類訪問的局部變量必須被final修飾,但局部變量值不能被修改(無論是方法中還是內部類中),否則會報編譯錯誤。利用javap查看編譯后的字節碼可以發現,編譯器已經加上了final。
根據代碼的計算結果,s的值應該是-1371654655,這是由于Java中右側值的計算默認是int類型。
NIO(Non-blocking IO)為所有的原始類型提供(Buffer)緩存支持,字符集編碼解碼解決方案。Channel :一個新的原始I/O 抽象。支持鎖和內存映射文件的文件訪問接口。提供多路(non-bloking) 非阻塞式的高伸縮性網絡I/O 。
I
Java NIO和IO之間第一個最大的區別是,IO是面向流的,NIO是面向緩沖區的。Java IO面向流意味著每次從流中讀一個或多個字節,直至讀取所有字節,它們沒有被緩存在任何地方。此外,它不能前后移動流中的數據。如果需要前后移動從流中讀取的數據,需要先將它緩存到一個緩沖區。
Java NIO的緩沖導向方法略有不同。數據讀取到一個它稍后處理的緩沖區,需要時可在緩沖區中前后移動。這就增加了處理過程中的靈活性。但是,還需要檢查是否該緩沖區中包含所有您需要處理的數據。而且,需確保當更多的數據讀入緩沖區時,不要覆蓋緩沖區里尚未處理的數據。
阻塞與非阻塞IO
Java IO的各種流是阻塞的。這意味著,當一個線程調用read() 或 write()時,該線程被阻塞,直到有一些數據被讀取,或數據完全寫入。該線程在此期間不能再干任何事情了。Java NIO的非阻塞模式,是線程向某通道發送請求讀取數據,僅能得到目前可用的數據,如果目前沒有數據可用時,就什么都不會獲取,當然它不會保持線程阻塞。所以直至數據變的可以讀取之前,該線程可以繼續做其他的事情。非阻塞寫也是如此。所以一個單獨的線程現在可以管理多個輸入和輸出通道。
選擇器(Selectors)
Java NIO 的 選擇器允許一個單獨的線程來監視多個輸入通道,你可以注冊多個通道使用一個選擇器,然后使用一個單獨的線程來“選擇”通道:這些通道里已經有可以處理的輸入,或者選擇已準備寫入的通道。這種選擇機制,使得一個單獨的線程很容易來管理多個通道。
Java反射機制可以讓我們在編譯期(Compile Time)之外的運行期(Runtime)檢查類,接口,變量以及方法的信息。反射還可以讓我們在運行期實例化對象,調用方法,通過調用get/set方法獲取變量的值。同時我們也可以通過反射來獲取泛型信息,以及注解。還有更高級的應用–動態代理和動態類加載(ClassLoader.loadclass())。
下面列舉一些比較重要的方法:
getFields:獲取所有 public 的變量。
getDeclaredFields:獲取所有包括 private , protected 權限的變量。
setAccessible:設置為 true 可以跳過Java權限檢查,從而訪問private權限的變量。
getAnnotations:獲取注解,可以用在類和方法上。
獲取方法的泛型參數:
method = Myclass.class.getMethod("setStringList", List.class);
Type[] genericParameterTypes = method.getGenericParameterTypes();
for(Type genericParameterType : genericParameterTypes){
if(genericParameterType instanceof ParameterizedType){
ParameterizedType aType = (ParameterizedType) genericParameterType;
Type[] parameterArgTypes = aType.getActualTypeArguments();
for(Type parameterArgType : parameterArgTypes){
Class parameterArgClass = (Class) parameterArgType;
System.out.println("parameterArgClass = " + parameterArgClass);
}
}
}
動態代理:
//Main.java
public static void main(String[] args) {
HelloWorld helloWorld=new HelloWorldImpl();
InvocationHandler handler=new HelloWorldHandler(helloWorld);
//創建動態代理對象
HelloWorld proxy=(HelloWorld)Proxy.newProxyInstance(
helloWorld.getClass().getClassLoader(),
helloWorld.getClass().getInterfaces(),
handler);
proxy.sayHelloWorld();
}
//HelloWorldHandler.java
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
//調用之前
doBefore();
//調用原始對象的方法
result=method.invoke(obj, args);
//調用之后
doAfter();
return result;
}
通過反射獲取方法注解的參數:
Class aClass = TheClass.class;
Annotation[] annotations = aClass.getAnnotations();
for(Annotation annotation : annotations){
if(annotation instanceof MyAnnotation){
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("name: " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
}
}
非靜態內部類能定義靜態方法嗎?
public class OuterClass{
private static float f = 1.0f;
class InnerClass{
public static float func(){return f;}
}
}
以上代碼會出現編譯錯誤,因為只有靜態內部類才能定義靜態方法。
使用方法的區別
Synchronized:在需要同步的對象中加入此控制,synchronized可以加在方法上,也可以加在特定代碼塊中,括號中表示需要鎖的對象。
Lock:需要顯示指定起始位置和終止位置。一般使用ReentrantLock類做為鎖,多個線程中必須要使用一個ReentrantLock類做為對象才能保證鎖的生效。且在加鎖和解鎖處需要通過lock()和unlock()顯示指出。所以一般會在finally塊中寫unlock()以防死鎖。
性能的區別
synchronized是tuo管給JVM執行的,而lock是java寫的控制鎖的代碼。在Java1.5中,synchronize是性能低效的。因為這是一個重量級操作,需要調用操作接口,導致有可能加鎖消耗的系統時間比加鎖以外的操作還多。相比之下使用Java提供的Lock對象,性能更高一些。但是到了Java1.6,發生了變化。synchronize在語義上很清晰,可以進行很多優化,有適應自旋,鎖消除,鎖粗化,輕量級鎖,偏向鎖等等。導致在Java1.6上synchronize的性能并不比Lock差。
Synchronized:采用的是CPU悲觀鎖機制,即線程獲得的是獨占鎖。獨占鎖意味著 其他線程只能依靠阻塞來等待線程釋放鎖。而在CPU轉換線程阻塞時會引起線程上下文切換,當有很多線程競爭鎖的時候,會引起CPU頻繁的上下文切換導致效率很低。
Lock:用的是樂觀鎖方式。所謂樂觀鎖就是,每次不加鎖而是假設沒有沖突而去完成某項操作,如果因為沖突失敗就重試,直到成功為止。樂觀鎖實現的機制就是CAS操作。我們可以進一步研究ReentrantLock的源代碼,會發現其中比較重要的獲得鎖的一個方法是compareAndSetState。這里其實就是調用的CPU提供的特殊指令。
ReentrantLock:具有更好的可伸縮性:比如時間鎖等候、可中斷鎖等候、無塊結構鎖、多個條件變量或者鎖投票。
folat類型的還有double類型的,這些小數類型在趨近于0的時候直接等于0的可能性很小,一般都是無限趨近于0,因此不能用==來判斷。應該用|x-0|<err來判斷,這里|x-0|表示絕對值,err表示限定誤差。
//用程序表示就是
fabs(x) < 0.00001f
內部類在聲明的時候必須是 Outer.Inner a,就像int a 一樣,至于靜態內部類和非靜態內部類new的時候有點區別:
Outer.Inner a = new Outer().new Inner()(非靜態,先有Outer對象才能 new 內部類)
Outer.Inner a = new Outer.Inner()(靜態內部類)
可以包含:字母、數字、$、_(下劃線),不可用數字開頭,不能是 Java 的關鍵字和保留字。
裝飾模式:java.io
單例模式:Runtime類
簡單工廠模式:Integer.valueOf方法
享元模式:String常量池、Integer.valueOf(int i)、Character.valueOf(char c)
迭代器模式:Iterator
職責鏈模式:ClassLoader的雙親委派模型
解釋器模式:正則表達式java.util.regex.Pattern
JDK 1.7及以前:
ConcurrentHashMap允許多個修改操作并發進行,其關鍵在于使用了鎖分離技術。它使用了多個鎖來控制對hash表的不同部分進行的修改。ConcurrentHashMap內部使用段(Segment)來表示這些不同的部分,每個段其實就是一個小的hash table,它們有自己的鎖。只要多個修改操作發生在不同的段上,它們就可以并發進行。
JDK 1.8:
Segment雖保留,但已經簡化屬性,僅僅是為了兼容舊版本。
插入時使用CAS算法:unsafe.compareAndSwapInt(this, valueOffset, expect, update)。CAS(Compare And Swap)意思是如果valueOffset位置包含的值與expect值相同,則更新valueOffset位置的值為update,并返回true,否則不更新,返回false。插入時不允許key或value為null
與Java8的HashMap有相通之處,底層依然由“數組”+鏈表+紅黑樹;
底層結構存放的是TreeBin對象,而不是TreeNode對象;
CAS作為知名無鎖算法,那ConcurrentHashMap就沒用鎖了么?當然不是,當hash值與鏈表的頭結點相同還是會synchronized上鎖,鎖鏈表。
雖然遞增操作++i是一種緊湊的語法,使其看上去只是一個操作,但這個操作并非原子的,因而它并不會作為一個不可分割的操作來執行。實際上,它包含了三個獨立的操作:讀取count的值,將值加1,然后將計算結果寫入count。這是一個“讀取 - 修改 - 寫入”的操作序列,并且其結果狀態依賴于之前的狀態。所以在多線程環境下存在問題。
要解決自增操作在多線程環境下線程不安全的問題,可以選擇使用Java提供的原子類,如AtomicInteger或者使用synchronized同步方法。
new是一個關鍵字,它是調用new指令創建一個對象,然后調用構造方法來初始化這個對象,可以使用帶參數的構造器
newInstance()是Class的一個方法,在這個過程中,是先取了這個類的不帶參數的構造器Constructor,然后調用構造器的newInstance方法來創建對象。
Class.newInstance不能帶參數,如果要帶參數需要取得對應的構造器,然后調用該構造器的Constructor.newInstance(Object … initargs)方法
接口的默認方法和靜態方法,JDK8允許我們給接口添加一個非抽象的方法實現,只需要使用default關鍵字即可。也可以定義被static修飾的靜態方法。
對HashMap進行了改進,當單個桶的元素個數大于6時就會將實現改為紅黑樹實現,以避免構造重復的hashCode的***
多并發進行了優化。如ConcurrentHashMap實現由分段加鎖、鎖分離改為CAS實現。
JDK8拓寬了注解的應用場景,注解幾乎可以使用在任何元素上,并且允許在同一個地方多次使用同一個注解
Lambda表達式
Xms 堆最小值
Xmx 堆最大值
Xmn: 新生代容量
XX:SurvivorRatio 新生代中Eden與Surivor空間比例
Xss 棧容量
XX:PermSize 方法區初始容量
XX:MaxPermSize 方法區最大容量
XX:+PrintGCDetails 收集器日志參數
重寫loadClass()方法。
hashcode() 返回該對象的哈希碼值,支持該方法是為哈希表提供一些優點,例如,java.util.Hashtable 提供的哈希表。
在 Java 應用程序執行期間,在同一對象上多次調用 hashCode 方法時,必須一致地返回相同的整數,前提是對象上 equals 比較中所用的信息沒有被修改(equals默認返回對象地址是否相等)。如果根據 equals(Object)方法,兩個對象是相等的,那么在兩個對象中的每個對象上調用 hashCode 方法都必須生成相同的整數結果。
以下情況不是必需的:如果根據 equals(java.lang.Object) 方法,兩個對象不相等,那么在兩個對象中的任一對象上調用 hashCode 方法必定會生成不同的整數結果。但是,程序員應該知道,為不相等的對象生成不同整數結果可以提高哈希表的性能。
實際上,由 Object 類定義的 hashCode 方法確實會針對不同的對象返回不同的整數。(這一般是通過將該對象的內部地址轉換成一個整數來實現的,但是 JavaTM 編程語言不需要這種實現技巧I。)
hashCode的存在主要是用于查找的快捷性,如 Hashtable,HashMap等,hashCode 是用來在散列存儲結構中確定對象的存儲地址的;
如果兩個對象相同,就是適用于 equals(java.lang.Object) 方法,那么這兩個對象的 hashCode 一定要相同;
如果對象的 equals 方法被重寫,那么對象的 hashCode 也盡量重寫,并且產生 hashCode 使用的對象,一定要和 equals 方法中使用的一致,否則就會違反上面提到的第2點;
兩個對象的hashCode相同,并不一定表示兩個對象就相同,也就是不一定適用于equals(java.lang.Object) 方法,只能夠說明這兩個對象在散列存儲結構中,如Hashtable,他們“存放在同一個籃子里”。
sleep()和yield()都會釋放CPU。
sleep()使當前線程進入停滯狀態,所以執行sleep()的線程在指定的時間內肯定不會執行;yield()只是使當前線程重新回到可執行狀態,所以執行yield()的線程有可能在進入到可執行狀態后馬上又被執行。
sleep()可使優先級低的線程得到執行的機會,當然也可以讓同優先級和高優先級的線程有執行的機會;yield()只能使同優先級的線程有執行的機會。
{}是預編譯處理,${}是字符串替換。
Mybatis在處理#{}時,會將sql中的#{}替換為?號,調用PreparedStatement的set方法來賦值;
Mybatis在處理{}替換成變量的值。
使用#{}可以有效的防止SQL注入,提高系統安全性。
Dao接口,就是人們常說的Mapper接口,接口的全限名,就是映射文件中的namespace的值,接口的方法名,就是映射文件中MappedStatement的id值,接口方法內的參數,就是傳遞給sql的參數。Mapper接口是沒有實現類的,當調用接口方法時,接口全限名+方法名拼接字符串作為key值,可唯一定位一個MappedStatement,舉例:com.mybatis3.mappers.StudentDao.findStudentById,可以唯一找到namespace為com.mybatis3.mappers.StudentDao下面id = findStudentById的MappedStatement。在Mybatis中,每一個<select>、<insert>、<update>、<delete>標簽,都會被解析為一個MappedStatement對象。
Dao接口里的方法,是不能重載的,因為是全限名+方法名的保存和尋找策略。
Dao接口的工作原理是JDK動態代理,Mybatis運行時會使用JDK動態代理為Dao接口生成代理proxy對象,代理對象proxy會攔截接口方法,轉而執行MappedStatement所代表的sql,然后將sql執行結果返回。
關于Java后端面試題和答案就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果喜歡這篇文章,不如把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。