Java複習01-基礎知識點
本次的基礎知識點複習會有深度的複習,尤其是原理等是重點,做到知其所以然。
先放一張導圖:
1.環境變量配置理解、
大家都知道開發一個java程序 打印出hello world 的流程基本有以下步驟:
下載JDK,
安裝JDK
配置環境變量
編寫代碼以.java保存
運行JavaC編譯生成xx.class 文件
運行Java xx.class文件,控制檯輸出
大家想過其中的原因沒,爲什麼要這樣做,不這麼做會有什麼問題。還有都說Java是一次編譯到處運行的,原理又是什麼類。我們一個一個來解決。
1.下載JDK: 這個沒什麼好說的,就比如 你要拍照 得先買個照相機。
2.安裝JDK: 也很好理解,照相機買了 還得做一些準備工作,比如充電,放內存卡等等。JDK安裝後會有2個文件夾
其中JDK就是Java Development Kit的縮寫意思是java開發工具。顧名思義 這個文件夾下面包含了我們一切我們開發需要的東西。比如,編譯器,一些好用的工具,說明文檔,類庫等。而JRE是Java Runtime Environment的縮寫,顧名思義是java運行時環境,包含了java虛擬機,java基礎類庫。
環境變量配置:
變量名:JAVA_HOME
變量值:C:\Program Files (x86)\Java\jdk1.8.0_91 // 要根據自己的實際路徑配置
變量名:CLASSPATH
變量值:.;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar;
變量名:Path
變量值:%JAVA_HOME%\bin;%JAVA_HOME%\jre\bin;
配置環境變量主要的作用就是 配置路徑,讓系統可以去這些路徑下找東西。
Java_Home是路徑。
ClassPath是依賴類庫的路徑
Path是虛擬機路徑和編譯器路徑
編寫代碼以.java保存:運行JavaC編譯生成xx.class 文件:
.java文件格式是java規定的,這個javaC就是JDK中bin目錄下的。
用16進制打開Lear.class文件:
這個一個滿足虛擬機的運行規範的文件夾格式。開頭4個字節是標識。具體的參考虛擬機規範。
我們還可以反編譯回去看看,這個機器在分析一些問題時很有用,如:爲什麼非靜態內部類會導致內存泄漏。
如我們編寫的java文件:
先javac 編譯,然後反編譯:
出現了2個類,其中 一個就是我們的內部類MyTest。打開他們看看。
運行Java xx.class文件,控制檯輸出:
爲什麼java Learn可以輸出hello world,直接運行Learn就不行。其實很簡單,window只能運行.exe結尾的文件,.class是無法運行的。可以看到java.exe 和javac.exe都是可以直接運行的。
總結:我們寫java文件 編譯的class文件,運行的不是在window 上。而是在虛擬機上,每個平臺的虛擬機是不一樣的。window版本虛擬機,mac虛擬機。但是虛擬機運行字節碼文件的規則是一樣的。所以纔會一次編譯到處理運行的說法。如果你的電腦沒裝虛擬機那肯定是運行不了。
2.數據類型
java數據類型分爲:1.8大基本數據類型:byte short int long float double boolean char
2.引用類型數據:類,接口,數組
大小:1byte=8bit。1short=16bit 1int=32bit 1long=64bit. 1float=32bit 1double=64bit 1boolean=8bit 1char=2bit 單一的 16 位 Unicode 字符.
char與Unicode的關係,以及char字符佔多大內存問題。
如果你說的“字符”就是指Java中的char,那好,那它就是16位,2字節。
如果你說的“字符”是指我們用眼睛看到的那些“抽象的字符”,那麼,談論它佔幾個字節是沒有意義的。
具體地講,脫離具體的編碼談某個字符佔幾個字節是沒有意義的。
就好比有一個抽象的整數“42”,你說它佔幾個字節?這得具體看你是用byte,short,int,還是long來存它。
字符是同樣的道理,如果你想談“佔幾個字節”,就要先把編碼說清楚。
同一個字符在不同的編碼下可能佔不同的字節。
就以你舉的“字”字爲例,“字”在GBK編碼下佔2字節,在UTF-16編碼下也佔2字節,在UTF-8編碼下佔3字節,在UTF-32編碼下佔4字節。
關於Unicode的理解:Unicode的理解
unicode是編碼字符集(將字符編碼),而UTF-8、UTF-16、UTF-32是字符集編碼
保存到計算機中(硬盤、內存),機器碼,取決於用到的字符集編碼是哪種。
基本數據類型和引用類型的區別主要在於基本數據類型是分配在棧上的,而引用類型是分配在堆上的。
基本數據類型使用比較比較java基本類型:比較基本類型只能用"",不能用"equals",這裏的"=="比較的是兩個基本類型的值;
2.String,StringBuilder,StringBuffer
String 是一種特殊的數據類型,本質上還是一個引用類型數據。內部是通過一個char[]數組實現。
String是一個final類,既不能被繼承的類。
如果是對字符串進行修改,其本質是new了一個新的String的對象
1:String 內容不可變,StringBuffer。StringBudiler可變
2:StringBuffer:同步的,數據安全,效率低。
StringBuilder:不同步的,數據不安全,效率高。1.5之後纔有的
1 String類是引用類型;
2 String類重寫Object類的equals()和hashCode(),用於比較內容是否相等,而非引用地址;
3 “==”運算符,對基本數據類型比較的是字面值,對引用類型比較的則是引用地址;
String 字符串常量
StringBuffer 字符串變量
StringBuilder 字符串變量
主要原因是 String 類中字符數組是常量的:
而StringBuilder和StringBuffer 是繼承 AbstractStringBuilder 裏面的字符數組不是:
而且看到 還有一個最大值。
常量池就類似一個JAVA系統級別提供的緩存,位於虛擬機中
8種基本類型的常量池都是系統協調的,String類型的常量池比較特殊。它的主要使用方法有兩種:
直接使用雙引號聲明出來的String對象會直接存儲在常量池中。
如果不是用雙引號聲明的String對象,可以使用String提供的intern方法。intern 方法會從字符串常量池中查詢當前字符串是否存在,若不存在就會將當前字符串放入常量池中
在HotSpot VM裏實現的string pool功能的是一個StringTable類,它是一個Hash表,默認值大小長度是1009;這個StringTable在每個HotSpot VM的實例只有一份,被所有的類共享。字符串常量由一個一個字符組成,放在了StringTable上。
在JDK6.0中,StringTable的長度是固定的,長度就是1009,因此如果放入String Pool中的String非常多,就會造成hash衝突,導致鏈表過長,當調用String#intern()時會需要到鏈表上一個一個找,從而導致性能大幅度下降;
在JDK7.0中,StringTable的長度可以通過參數指定:
-XX:StringTableSize=66666
需要說明的是:字符串常量池中的字符串只存在一份!
字面量包括:1.文本字符串 2.八種基本類型的值 3.被聲明爲final的常量等;
最常用的寫法:
String aaa=“I am String”;
這個叫做字面量。其實和 int i=5;是一樣的。
雖然目前還不知道虛擬機裏面是怎麼執行這個一句的。
我們反編譯看看是什麼:
看不出什麼,那麼到虛擬機看看這句話的字節碼指令集到底是什麼:
好像也看不出什麼
當.java文件編譯成.class文件時,其類中的靜態String數據是以以下數據結構去存儲的:
CONSTANT_Utf8_info {
u1 tag;
u2 length; // 0 ~ 65535
u1 bytes[length];
}
u2是表示一個2個字節的數據類型,這也就意味着允許的最大長度爲65535。
是不是到處都找不到這個東西?
其實它是在 虛擬機的規範中 定義的,所以後續需要 好好的看看《深入理解JAVA虛擬機2》
還可以
很亂不過基本上是有點頭緒了。具體的還需要《深入理解JAVA虛擬機2》 專研。
3.作用域
這個需要注意的知識點是就是默認修飾符 就是什麼都不寫
Java中,可以使用訪問控制符來保護對類、變量、方法和構造方法的訪問。Java 支持 4 種不同的訪問權限。
default (即默認,什麼也不寫): 在同一包內可見,不使用任何修飾符。使用對象:類、接口、變量、方法。
private : 在同一類內可見。使用對象:變量、方法。 注意:不能修飾類(外部類)
public : 對所有類可見。使用對象:類、接口、變量、方法
protected : 對同一包內的類和所有子類可見。使用對象:變量、方法。 注意:不能修飾類(外部類)。
4.流程控制和運算符
這個也沒什麼特別的,基本每個語言通用的,如果跳轉,循環,判斷 加減等。需要 勤加練習的 有位運算。
這裏有個知識點就是 先++還是先返回如:
int a=10;
a=a++; 先返回 再自加 a=10
a=++a; 先自加 再返回 a=11
重點一個就是 使用位運算實現 加減乘除:
/**
* 二進制實現加法-預算的基礎 全加器。類似十進制 先來個我們最熟悉的十進制的加法運算:
*
* 13 + 9 = 22
*
* 我們像這樣來拆分這個運算過程:
*
* 不考慮進位,分別對各位數進行相加,結果爲sum: 個位數3加上9爲2;十位數1加上0爲1; 最終結果爲12;
*
* 只考慮進位,結果爲carry: 3 + 9 有進位,進位的值爲10;
*
* 如果步驟2所得進位結果carry不爲0,對步驟1所得sum,步驟2所得carry重複步驟1、
* 2、3;如果carry爲0則結束,最終結果爲步驟1所得sum: 這裏即是對sum = 12 和carry = 10重複以上三個步驟,(a)
* 不考慮進位,分別對各位數進行相加:sum = 22; (b) 只考慮進位: 上一步沒有進位,所以carry = 0; (c) 步驟2carry =
* 0,結束,結果爲sum = 22. 我們發現這三板斧行得通!
*/
private static int add(int number1, int number2) {
int sum = number1 ^ number2;
int carry = (number1 & number2) << 1;
while (carry != 0) {
int a = sum;
int b = carry;
sum = a ^ b;
carry = (a & b) << 1;
}
return sum;
}
// 遞歸寫法
// private static int add(int num1, int num2){
// if(num2 == 0)
// return num1;
// int sum = num1 ^ num2;
// int carry = (num1 & num2) << 1;
// return add(sum, carry);
// }
/** 二進制實現減法 */
private static int substract(int number1, int number2) {
int subtractor = add(~number2, 1);// 先求減數的補碼(取反加一)
int result = add(number1, subtractor);
return result;
}
/**
* 二進制實現乘法 -類似我們在草稿紙上運算 乘法 (1) 判斷乘數是否爲0,爲0跳轉至步驟(4)
* (2)將乘數與1作與運算,確定末尾位爲1還是爲0,如果爲1,則相加數爲當前被乘數;如果爲0,則相加數爲0;將相加數加到最終結果中
* (3)被乘數左移一位,乘數右移一位;回到步驟(1) (4) 確定符號位,輸出結果;
*/
private static int multiply(int a, int b) {
// 將乘數和被乘數都取絕對值
int multiplicand = a < 0 ? add(~a, 1) : a;
int multiplier = b < 0 ? add(~b, 1) : b;
// 計算絕對值的乘積
int product = 0;
// 連續累加效率不高
while (multiplier > 0) {
if ((multiplier & 0x1) > 0) {// 每次考察乘數的最後一位
product = add(product, multiplicand);
}
multiplicand = multiplicand << 1;// 每運算一次,被乘數要左移一位
multiplier = multiplier >> 1;// 每運算一次,乘數要右移一位(可對照上圖理解)
}
// 計算乘積的符號
if ((a ^ b) < 0) {// 只考慮最高位,如果a,b異號,則異或後最高位爲1;如果同號,則異或後最高位爲0;
product = add(~product, 1);
}
return product;
}
/**
* 二進制實現除法
*
* @param n
* @return
*/
private static int divide_v2(int a, int b) {
// 先取被除數和除數的絕對值
int dividend = a > 0 ? a : add(~a, 1);// 被除數
int divisor = b > 0 ? b : add(~b, 1); // 除數
int quotient = 0;// 商
int remainder = 0;// 餘數
for (int i = 31; i >= 0; i--) {
// 比較dividend是否大於divisor的(1<<i)次方,不要將dividend與(divisor<<i)比較,而是用(dividend>>i)與divisor比較,
// 效果一樣,但是可以避免因(divisor<<i)操作可能導致的溢出,如果溢出則會可能dividend本身小於divisor,但是溢出導致dividend大於divisor
if ((dividend >> i) >= divisor) {
quotient = add(quotient, 1 << i);
dividend = substract(dividend, divisor << i);
}
}
// 不斷用除數去減被除數,直到被除數小於被除數(即除不盡了)但是效率不高
// while (dividend >= divisor) {// 直到商小於被除數
// quotient = add(quotient, 1);
// dividend = substract(dividend, divisor);
// println(" quotient" + quotient);
// }
// 確定商的符號
if ((a ^ b) < 0) {
// 如果除數和被除數異號,則商爲負數
quotient = add(~quotient, 1);
}
// 確定餘數符號
remainder = b > 0 ? dividend : add(~dividend, 1);
return quotient;// 返回商
}
篇幅有限 剩餘的 數組,輸入 輸出流,異常等基礎內容放到下篇