1991 年Sun公司的James Gosling(詹姆斯·高斯林)等人開始開發名稱爲 Oak 的語言,但願用於控制嵌入在有線電視交換盒、PDA等的微處理器。1994年將Oak語言改名爲Java。 java
J2EE:Java PlatformEnterprise Edition,開發企業環境下的應用程序,主要針對web程序開發;linux
J2SE:Java PlatformStandard Edition,完成桌面應用程序的開發,是其它二者的基礎;web
J2ME:Java PlatformMicro Edition,開發電子消費產品和嵌入式設備,如手機中的程序; 數據庫
1,JDK:JavaDevelopment Kit,java的開發和運行環境,java的開發工具和jre。express
2,JRE:Java RuntimeEnvironment,java程序的運行環境,java運行的所需的類庫+JVM。編程
3,配置環境變量:讓java jdk\bin目錄下的工具,能夠在任意目錄下運行,緣由是,將該工具所在目錄告訴了系統,當使用該工具時,由系統幫咱們去找指定的目錄。windows
4,javac命令和java命令作什麼事情呢?數組
要知道java是分兩部分的:一個是編譯,一個是運行。安全
javac:負責的是編譯的部分,當執行javac時,會啓動java的編譯器程序。對指定擴展名的.java文件進行編譯。 生成了jvm能夠識別的字節碼文件。也就是class文件,也就是java的運行程序。服務器
java:負責運行的部分.會啓動jvm.加載運行時所需的類庫,並對class文件進行執行.
Java爲何能跨平臺:
由於Java程序編譯以後的代碼不是能被硬件系統直接運行的代碼,而是一種「中間碼」,二進制字節碼的class文件,jvm就解釋執行class文件。不一樣的硬件平臺上安裝有不一樣的Java虛擬機(JVM),由JVM來把字節碼再「翻譯」成所對應的硬件平臺可以執行的代碼。因此它的代碼就能不經修改,就能在不一樣平臺的jvm上運行(在UNIX用UNIX的jvm,在linux上用linux的jvm,在windows上用windows的jvm)。假如用windows移植到UNIX,只需把java文件是UNIX的jvm上編譯成class文件,而後用jvm運行就能夠了.所以對於Java編程者來講,不須要考慮硬件平臺是什麼。因此Java能夠跨平臺。
&:位運算符,無論怎樣,都會執行"&"符號左右兩邊的程序。
&&:邏輯運算符,只有當符號"&&"左邊程序爲真(true)後,纔會執行符號"&&"右邊的程序。
例子: if(str != null & !"".equals(str)),if(str != null && !"".equals(str))
使用場景:條件判斷下使用&&比使用&效率更高,&通常用於位運算。
|和||同理可證。
1:這個關鍵字是一個修飾符,能夠修飾類,方法,變量。
2:被final修飾的類是一個最終類,不能夠被繼承。
3:被final修飾的方法是一個最終方法,不能夠被覆蓋。
4:被final修飾的變量是一個常量,只能賦值一次。
常量名稱定義時,有規範,全部字母都大寫,若是由多個單詞組成,中間用 _ 鏈接。
例子:private staticfiinal String OS_TYPE = 「windows」;
值類型是存儲在內存中的堆棧(之後簡稱棧),而引用類型的變量在棧中僅僅是存儲引用類型變量的地址,而其自己則存儲在堆中。
==操做:比較的是兩個變量的值是否相等,對於引用型變量表示的是兩個變量在堆中存儲的地址是否相同,即棧中的內容是否相同。
equals操做:表示的兩個變量是不是對同一個對象的引用,即堆中的內容是否相同。
==比較的是2個對象的地址,而equals比較的是2個對象的內容。
顯然,當equals爲true時,==不必定爲true;
例子:
1、String中的equals和==
一、
public class TestString{
public static voidmain(String[] args) {
String s1 ="Monday";
String s2 ="Monday";
}
}
上面這段程序中,到底有幾個對象呢?
來檢測一下吧,稍微改動一下程序
public class TestString{
public static voidmain(String[] args) {
String s1 ="Monday";
String s2 ="Monday";
if (s1 == s2)
System.out.println("s1 == s2");
else
System.out.println("s1!= s2");
}
}
編譯並運行程序,輸出:s1== s2說明:s1 與 s2 引用同一個 String 對象 -- "Monday"!
2.再稍微改動一下程序,會有更奇怪的發現:
public class TestString{
public static voidmain(String[] args) {
String s1 ="Monday";
String s2 = new String("Monday");
if (s1 == s2)
System.out.println("s1 == s2");
else
System.out.println("s1 != s2");
if (s1.equals(s2))System.out.println("s1 equals s2");
else
System.out.println("s1 not equals s2");
}
}
咱們將s2用new操做符建立
程序輸出:
s1 != s2
s1 equals s2
說明:s1 s2分別引用了兩個"Monday"String對象
3. 字符串緩衝池
原來,程序在運行的時候會建立一個字符串緩衝池當使用 s2 = "Monday" 這樣的表達是建立字符串的時候,程序首先會在這個String緩衝池中尋找相同值的對象,在第一個程序中,s1先被放到了池中,因此在s2被建立的時候,程序找到了具備相同值的 s1
將s2引用s1所引用的對象"Monday"
第二段程序中,使用了 new操做符,他明白的告訴程序:"我要一個新的!不要舊的!"因而一個新的"Monday"Sting對象被建立在內存中。他們的值相同,可是位置不一樣,一個在池中游泳一個在岸邊休息。哎呀,真是資源浪費,明明是同樣的非要分開作什麼呢?
4.
再次更改程序:
public class TestString{
public static voidmain(String[] args) {
String s1 ="Monday";
String s2 = new String("Monday");
s2 = s2.intern();
if (s1 == s2)
System.out.println("s1 == s2");
else
System.out.println("s1 != s2");
if (s1.equals(s2))System.out.println("s1 equals s2");
else
System.out.println("s1 not equals s2");
}
}
此次加入:s2 =s2.intern();
程序輸出:
s1 == s2
s1 equals s2
原來,(java.lang.String的intern()方法"abc".intern()方法的返回值仍是字符串"abc",表面上看起來好像這個方法沒什麼用處。但實際上,它作了個小動做:檢查字符串池裏是否存在"abc"這麼一個字符串,若是存在,就返回池裏的字符串;若是不存在,該方法會把"abc"添加到字符串池中,而後再返回它的引用。
)
更好的辦法:
把全部的String都intern()到緩衝池去吧
最好在用到new的時候就進行這個操做
String s2 = newString("Monday").intern();
而後就能夠用==比較兩個字符串的值了
2、簡單數據類型和封裝類中的equals和==
Java爲每個簡單數據類型提供了一個封裝類,每一個基本數據類型能夠封裝成對象類型。
除int(Integer)和char(Character),其他類型首字母大寫即成封裝類類型名。double (Double), float(Float),long(Long),short(Short),byte(Byte),boolean(Boolean).
以int和Integer爲例說明
Java中int和Integer區別以下:
1.int是基本的數據類型,默認值能夠爲0;2.Integer是int的封裝類,默認值爲null;3.int和Integer均可以表示某一個數值;4.int和Integer不可以互用,由於他們兩種不一樣的數據類型;
int a1=1;
int a2=1;
Integer b1 =new Integer(1);
Integer b2 =new Integer(1);
------------------------------
a1==a2 這個是成立的,很簡單,都知道a1==b1 這個是不成立的.表達式的值爲 false,它們是不一樣的數據類型b1==b2 這個也是不成立的.表達式的值爲 false,雖然是相同的數據類型,可是它們是兩個對象,==比較的是2個對象的地址,它們的地址是不相等的,內容相等都是1;
b1.equals(b2)==true 這個是成立的,表達式的值爲 true. 相同數據類型,兩個對象,地址不一樣,內容相同, quals比較的是2個對象的內容,因此成立。
(a.equals(b),由於equals比較的是兩個對象,因此a,b都不能爲基本數據類型,不然會出編譯錯誤。)同理,其它的封裝類和基本類型也是這樣的.
java中equals和==的區別
==比較的是2個對象的地址,而equals比較的是2個對象的內容。
3、其餘類怎麼使用equals和==
API裏的類大部分都重寫了equals方法,沒有重寫的通常是本身寫的類,若是是你本身定義的一個類,比較自定義類用equals和==是同樣的,都是比較句柄地址,由於自定義的類是繼承於object,而object中的equals就是用==來實現的,你能夠看源碼。
4、java裏equals和hashCode之間什麼關係
只是爲了維護hashCode 方法的常規協定,纔要求用equals比較的兩個對象的hashCode相同. equals()和hashCode()都來自java.lang.Object.你固然能夠重寫.
好比a.equals(b).僅當a的內存地址相等時,才返回true.固然如String等類已經對這個方法進行了重寫,比較的就再也不是內存地址了. hashCode()的值也是與內存地址相關的.因此僅當內存地址相等時,hashCode才相等.
一樣不少類也重寫了這個方法,仍是以String爲例:
public int hashCode() {
int h = hash;
if (h == 0) {
int off = offset;
char val[] = value;
int len = count;
for (int i = 0; i <len; i++) {
h = 31*h + val[off++];
}
hash = h;
}
return h;
}
就不在與內存地址相關了.這樣作是爲了保證用equals比較返回爲true的兩個對象,他們的hashCode是相同的.
因此通常重寫equals的時候都會重寫hashCode().固然,這個至關於一個約定,一個協議.你不這麼作並不會錯.
5、hashCode
在通常的應用中你不須要了解hashcode的用法,但當你用到hashmap,hashset等集合類時要注意下hashcode。
你想經過一個object的key來拿hashmap的value,hashmap的工做方法是,經過你傳入的object的hashcode在內存中找地址,當找到這個地址後再經過equals方法來比較這個地址中的內容是否和你原來放進去的同樣,同樣就取出value。
因此這裏要匹配2部分,hashcode和equals但假如說你new一個object做爲key去拿value是永遠得不到結果的,由於每次new一個object,這個object的hashcode是永遠不一樣的,因此咱們要重寫hashcode,你能夠令你的hashcode是object中的一個恆量,這樣永遠能夠經過你的object的hashcode來找到key的地址,而後你要重寫你的equals方法,使內存中的內容也相等。
if 語句適用範圍比較廣,只要是 boolean表達式均可以用 if 判斷;而 switch 只能對基本類型進行數值比較。二者的可比性就僅限在兩個基本類型比較的範圍內。
說到基本類型的數值比較,那固然要有兩個數。而後重點來了——
if 語句每一句都是獨立的,看下面的語句:
if (a == 1) ...
else if (a == 2) ...
這樣 a 要被讀入寄存器兩次,1 和 2 分別被讀入寄存器一次。因而你是否發現其實 a 讀兩次是有點多餘的,在你所有比較完以前只須要一次讀入寄存器就好了,其他都是額外開銷。可是 if 語句必須每次都把裏面的兩個數從內存拿出來讀到寄存器,它不知道你其實比較的是同一個 a。
因而 switch case 就出來了,把上面的改爲 switch case 版本:
switch (a) {
case 0:
break;
case 1:
}
由於特定的規則,他一開始就知道你要比 a,因而 a 一次性讀取,相比 if 節約了不少開銷。
1.三者在執行速度方面的比較:StringBuilder > StringBuffer > String
2.String <(StringBuffer,StringBuilder)的緣由
String:字符串常量
StringBuffer:字符串變量
StringBuilder:字符串變量
從上面的名字能夠看到,String是「字符創常量」,也就是不可改變的對象。對於這句話的理解你可能會產生這樣一個疑問 ,好比這段代碼:
String s = "abcd";
s = s+1;
System.out.print(s);// result : abcd1
咱們明明就是改變了String型的變量s的,爲何說是沒有改變呢?其實這是一種欺騙,JVM是這樣解析這段代碼的:首先建立對象s,賦予一個abcd,而後再建立一個新的對象s用來執行第二行代碼,也就是說咱們以前對象s並無變化,因此咱們說String類型是不可改變的對象了,因爲這種機制,每當用String操做字符串時,其實是在不斷的建立新的對象,而原來的對象就會變爲垃圾被GC回收掉,可想而知這樣執行效率會有多低。而StringBuffer與StringBuilder就不同了,他們是字符串變量,是可改變的對象,每當咱們用它們對字符串作操做時,其實是在一個對象上操做的,這樣就不會像String同樣建立一些而外的對象進行操做了,固然速度就快了。
3.一個特殊的例子:
1 String str = 「This is only a」 + 「 simple」 + 「 test」;
3 StringBuffer builder = new StringBuilder(「This is only a」).append(「 simple」).append(「 test」);
你會很驚訝的發現,生成str對象的速度簡直太快了,而這個時候StringBuffer竟然速度上根本一點都不佔優點。其實這是JVM的一個把戲,實際上:
String str = 「This is only a」 + 「 simple」 + 「test」;
其實就是:
String str = 「This is only a simple test」;
因此不須要太多的時間了。但你們這裏要注意的是,若是你的字符串是來自另外的String對象的話,速度就沒那麼快了,譬如:
String str2 = 「This is only a」;
String str3 = 「 simple」;
String str4 = 「 test」;
String str1 = str2 +str3 + str4;
這時候JVM會規規矩矩的按照原來的方式去作。
4.StringBuilder與 StringBuffer
StringBuilder:線程非安全的
StringBuffer:線程安全的
當咱們在字符串緩衝去被多個線程使用是,JVM不能保證StringBuilder的操做是安全的,雖然他的速度最快,可是能夠保證StringBuffer是能夠正確操做的。固然大多數狀況下就是咱們是在單線程下進行的操做,因此大多數狀況下是建議用StringBuilder而不用StringBuffer的,就是速度的緣由。
對於三者使用的總結: 1.若是要操做少許的數據用 = String
2.單線程操做字符串緩衝區下操做大量數據 = StringBuilder
3.多線程操做字符串緩衝區下操做大量數據 = StringBuffer
在不作編譯優化的狀況下,在循環中,循環條件會被反覆計算,若是不使用複雜表達式,而使循環條件值不變的話,程序將會運行的更快。
例子:
import java.util.vector;
class cel {
void method (vector vector) {
for (int i = 0; i < vector.size (); i++) // violation
; // ...
}
}
更正:
class cel_fixed {
void method (vector vector) {
int size = vector.size ()
for (int i = 0; i < size; i++)
; // ...
}
}
array 數組效率最高,但容量固定,沒法動態改變,ArrayList容量能夠動態增加,但犧牲了效率。
單線程應儘可能使用 HashMap, ArrayList,除非必要,不然不推薦使用HashTable,Vector,她們使用了同步機制,而下降了性能。
例如:
String str="abc";
if(i==1){ list.add(str);}
應修改成:
if(i==1){String str="abc";list.add(str);}
調用方法時傳遞的參數以及在調用中建立的臨時變量都保存在棧(Stack)中,速度較快。其餘變量,如靜態變量,實例變量等,都在堆(Heap)中建立,速度較慢。
取反操做符(!)下降程序的可讀性,因此不要老是使用。
例子:
public class dun {
boolean method (boolean a, boolean b) {
if (!a)
return !a;
else
return !b;
}
}
更正:
若是可能不要使用取反操做符(!)
stringbuffer的構造器會建立一個默認大小(一般是16)的字符數組。在使用中,若是超出這個大小,就會從新分配內存,建立一個更大的數組,並將原先的數組複製過來,再丟棄舊的數組。在大多數狀況下,你能夠在建立stringbuffer的時候指定大小,這樣就避免了在容量不夠的時候自動增加,以提升性能。
例子:
public class rsbc {
void method () {
stringbuffer buffer = new stringbuffer(); // violation
buffer.append ("hello");
}
}
更正:
爲stringbuffer提供寢大小。
public class rsbc {
void method () {
stringbuffer buffer = new stringbuffer(max);
buffer.append ("hello");
}
private final int max = 100;
}
在循環體中實例化臨時變量將會增長內存消耗
例子:
import java.util.vector;
public class loop {
void method (vector v) {
for (int i=0;i < v.size();i++) {
object o = new object();
o = v.elementat(i);
}
}
}
更正:
在循環體外定義變量,並反覆使用
import java.util.vector;
public class loop {
void method (vector v) {
object o;
for (int i=0;i<v.size();i++) {
o = v.elementat(i);
}
}
}
例子:
public class ifas {
void method(boolean istrue) {
if (istrue) {
_value = 0;
} else {
_value = 1;
}
}
private int _value = 0;
}
更正:
public class ifas {
void method(boolean istrue) {
_value = (istrue ? 0 : 1); // compact expression.
}
private int _value = 0;
}
字符串的分析在不少應用中都是常見的。使用indexof()和substring()來分析字符串容易致使 stringindexoutofboundsexception。而使用stringtokenizer類來分析字符串則會容易一些,效率也會高一些。
StringTokenizer token=new StringTokenizer(orginStr,".");
把try/catch塊放入循環體內,會極大的影響性能,若是編譯jit被關閉或者你所使用的是一個不帶jit的jvm,性能會將降低21%之多!
例子:
import java.io.fileinputstream;
public class try {
void method (fileinputstream fis) {
for (int i = 0; i < size; i++) {
try {
_sum += fis.read();
} catch (exception e) {}
}
}
private int _sum;
}
更正:
將try/catch塊移出循環
voidmethod (fileinputstream fis) {
try {
for (int i = 0; i < size; i++) {
_sum += fis.read();
}
} catch (exception e) {}
方法的同步須要消耗至關大的資料,在一個循環中調用它絕對不是一個好主意。
例子:
import java.util.vector;
public class syn {
public synchronized void method (object o) {
}
private void test () {
for (int i = 0; i < vector.size(); i++) {
method (vector.elementat(i)); // violation
}
}
private vector vector = new vector (5, 5);
}
更正:
不要在循環體中調用同步方法,若是必須同步的話,推薦如下方式:
import java.util.vector;
public class syn {
public void method (object o) {
}
private void test () {
synchronized{//在一個同步塊中執行非同步方法
for (int i = 0; i < vector.size(); i++) {
method(vector.elementat(i));
}
}
}
private vector vector = new vector (5, 5);
在不作編譯優化的狀況下,在循環中,循環條件會被反覆計算,若是不使用複雜表達式,而使循環條件值不變的話,程序將會運行的更快。
例子:
import java.util.vector;
class cel {
void method (vector vector) {
for (int i = 0; i < vector.size (); i++) // violation
; // ...
}
}
更正:
class cel_fixed {
void method (vector vector) {
int size = vector.size ()
for (int i = 0; i < size; i++)
; // ...
}
}
這毫無心義,若是代碼中出現」The value of the local variable i is not used」、」The importjava.util is never used」,那麼請刪除這些無用的內容
反射是Java提供給用戶一個很強大的功能,功能強大每每意味着效率不高。不建議在程序運行過程當中使用尤爲是頻繁使用反射機制,特別是Method的invoke方法,若是確實有必要,一種建議性的作法是將那些須要經過反射加載的類在項目啓動的時候經過反射實例化出一個對象並放入內存—-用戶只關心和對端交互的時候獲取最快的響應速度,並不關心對端的項目啓動花多久時間。
這是一個比較常見的小技巧了,若是有如下代碼:
String str = "123";
if (str.equals("123")) {
}
建議修改成:
String str = "123";
if ("123".equals(str)) {
...
}
這麼作主要是能夠避免空指針異常
這毫不會獲得想要的結果:
public static void main(String[] args){
long l = 12345678901234L;
int i = (int)l;
System.out.println(i);
}
咱們可能指望獲得其中的某幾位,可是結果倒是:
1942892530
解釋一下。Java中long是8個字節64位的,因此12345678901234在計算機中的表示應該是:
0000 0000 0000 0000 0000 1011 0011 10100111 0011 1100 1110 0010 1111 1111 0010
一個int型數據是4個字節32位的,從低位取出上面這串二進制數據的前32位是:
0111 0011 1100 1110 0010 1111 1111 0010
這串二進制表示爲十進制1942892530,因此就是咱們上面的控制檯上輸出的內容。從這個例子上還能順便獲得兩個結論:
一、整型默認的數據類型是int,long l =12345678901234L,這個數字已經超出了int的範圍了,因此最後有一個L,表示這是一個long型數。順便,浮點型的默認類型是double,因此定義float的時候要寫成」"float f = 3.5f」
二、接下來再寫一句」int ii = l +i;」會報錯,由於long + int是一個long,不能賦值給int
遇到把一個基本數據類型轉爲String的時候,優先考慮使用toString()方法。至於爲何,很簡單:
一、String.valueOf()方法底層調用了Integer.toString()方法,可是會在調用前作空判斷
二、Integer.toString()方法就不說了,直接調用了
三、i + 「」底層使用了StringBuilder實現,先用append方法拼接,再用toString()方法獲取字符串
三者對比下來,明顯是2最快、1次之、3最慢
意思是,好比我有這麼一段代碼:
try{
XXX.close();
YYY.close();
}catch (Exception e){
}
建議修改成:
try{
XXX.close();
}catch (Exception e) {
...
}try{
YYY.close();
}catch (Exception e) {
...}
雖然有些麻煩,卻能避免資源泄露。咱們想,若是沒有修改過的代碼,萬一XXX.close()拋異常了,那麼就進入了cath塊中了,YYY.close()不會執行,YYY這塊資源就不會回收了,一直佔用着,這樣的代碼一多,是可能引發資源句柄泄露的。而改成下面的寫法以後,就保證了不管如何XXX和YYY都會被close掉。
線程池的原理:
其實線程池的原理很簡單,相似於操做系統中的緩衝區的概念,它的流程以下:先啓動若干數量的線程,並讓這些線程都處於睡眠狀態,當客戶端有一個新請求時,就會喚醒線程池中的某一個睡眠線程,讓它來處理客戶端的這個請求,當處理完這個請求後,線程又處於睡眠狀態。可能你也許會問:爲何要搞得這麼麻煩,若是每當客戶端有新的請求時,我就建立一個新的線程不就完了?這也許是個不錯的方法,由於它能使得你編寫代碼相對容易一些,但你卻忽略了一個重要的問題?那就是性能!就拿我所在的單位來講,個人單位是一個省級數據大集中的銀行網絡中心,高峯期每秒的客戶端請求併發數超過100,若是爲每一個客戶端請求建立一個新線程的話,那耗費的CPU時間和內存將是驚人的,若是採用一個擁有200個線程的線程池,那將會節約大量的系統資源,使得更多的CPU時間和內存用來處理實際的商業應用,而不是頻繁的線程建立與銷燬。
數據庫鏈接池的原理:
數據庫鏈接是一種關鍵的有限的昂貴的資源,這一點在多用戶的網頁應用程序中體現得尤其突出。
一個數據庫鏈接對象均對應一個物理數據庫鏈接,每次操做都打開一個物理鏈接,使用完都關閉鏈接,這樣形成系統的性能低下。數據庫鏈接池的解決方案是在應用程序啓動時創建足夠的數據庫鏈接,並講這些鏈接組成一個鏈接池(簡單說:在一個「池」裏放了好多半成品的數據庫聯接對象),由應用程序動態地對池中的鏈接進行申請、使用和釋放。對於多於鏈接池中鏈接數的併發請求,應該在請求隊列中排隊等待。而且應用程序能夠根據池中鏈接的使用率,動態增長或減小池中的鏈接數。
鏈接池技術儘量多地重用了消耗內存地資源,大大節省了內存,提升了服務器地服務效率,可以支持更多的客戶服務。經過使用鏈接池,將大大提升程序運行效率,同時,咱們能夠經過其自身的管理機制來監視數據庫鏈接的數量、使用狀況等。
1)最小鏈接數是鏈接池一直保持的數據庫鏈接,因此若是應用程序對數據庫鏈接的使用量不大,將會有大量的數據庫鏈接資源被浪費;
2)最大鏈接數是鏈接池能申請的最大鏈接數,若是數據庫鏈接請求超過此數,後面的數據庫鏈接請求將被加入到等待隊列中,這會影響以後的數據庫操做。
帶緩衝的輸入輸出流,即BufferedReader、BufferedWriter、BufferedInputStream、BufferedOutputStream,這能夠極大地提高IO效率
要知道,當某個對象被定義爲static的變量所引用,那麼gc一般是不會回收這個對象所佔有的堆內存的,如:
public class A{
private static B b = new B();
}
此時靜態變量b的生命週期與A類相同,若是A類不被卸載,那麼引用B指向的B對象會常駐內存,直到程序終止
總之:無生命週期的用static,有生命週期的不用static。