JDBC的全稱是Java DataBase Connection,也就是Java數據庫連接,我們可以?它來操作關系型數據庫。JDBC接?及相關類在java.sql 包和javax.sql包?。我們可以?它來連接數據庫,執?SQL查詢,存儲過程,并處理返回的結果。
JDBC接?讓Java程序和JDBC驅動實現了松耦合,使得切換不同的數據庫變得更加簡單。
1.com.mysql.cj.jdbc.Driver是Driver驅動所在的位置,加載驅動
2.Class.forName()是一個反射,但是他沒有返回一個Class對象,因為我們不需要;
這是Driver的代碼:
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}
它除了構造方法,就只有一個靜態代碼塊,當我們反射進行的時候,這個類就開始初始化,他的靜態代碼塊內容就已經被執行了,我們真正需要的是DriverManager.registerDriver(new Driver());這一行代碼
是一個工廠類,我們通過它來創建數據庫連接,當JDBC的Driver類被加載進來時,它會自己注冊到DriverManager類里面
1.注冊驅動
2.獲取連接
3.創建一個Statement語句對象
4.執行SQL語句
5.處理結果集
6.關閉資源
1.PreparedStatement 繼承于 Statement,Statement 一般用于執行固定的沒有參數的SQL。2.PreparedStatement 一般用于執行有?參數預編譯的SQL語句??梢苑乐筍QL注入,安全性高于Statement。3.CallableStatement適用于執行存儲過程。
1)Statement的execute(String query)?法?來執?任意的SQL查詢,如果查詢的結果是?個ResultSet,這個?法就返回true。如果結果不是ResultSet,?如insert或者update查詢,它就會返回false。
2)Statement的executeQuery(String query)接??來執?select查詢,并且返回ResultSet。即使查詢不到記錄返回的ResultSet也不會為null。我們通常使?executeQuery來執?查詢語句,這樣的話如果傳進來的是insert或者update語句的 話,它會拋出錯誤信息為“executeQuery method can not be used for update”的java.util.SQLException。
3)Statement的executeUpdate(String query)?法?來執?insert或者update/delete(DML)語句。
4)只有當你不確定是什么語句的時候才應該使?execute()?法,否則應該使?executeQuery或者executeUpdate?法。
最好的辦法是利用sql語句進行分頁,這樣每次查詢出的結果集中就只包含某頁的數據內容。
sql語句分頁,不同的數據庫下的分頁方案各不一樣,假設一共有38條數據,每頁有10條數據,查詢第3頁的數據,下面是主流的三種數據庫的分頁sql:
Oracle:
select * from
(select *,rownum as tempid from student ) t
where t.tempid between 20 and 30;
mysql:
select * from students limit 20,10;
sql server:
select top 10 * from students where id not in?
(select top 20 id from students order by id)?
order by id;
事務是作為單個邏輯?作單元執?的?系列操作,?個邏輯?作單元必須有四個屬性,稱為原?性、?致性、隔離性和持久性(ACID) 屬性,只有這樣才能成為?個事務 。JDBC處理事務有如下操作:
conn.setAutoComit(false);設置提交?式為??提交。
conn.commit()提交事務。
conn.rollback()回滾事務。
提交與回滾只選擇?個執?。正常情況下提交事務,如果出現異常,則回滾。
數據庫連接是?種關鍵的有限的昂貴的資源,對數據庫連接的管理能顯著影響到整個應?程序的伸縮性和健壯性,影響到程序 的性能指標。數據庫連接池正是針對這個問題提出來的。
數據庫連接池負責分配、管理和釋放數據庫連接,它允許應?程序重復使??個現有的數據庫連接,?不是重新建??個;釋放空閑時間超過最?空閑時間的數據庫連接來避免因為沒有釋放數據庫連接?引起的數據庫連接遺漏。這項技術能明顯提?對數據庫操作的性能。
數據庫連接池在初始化時將創建?定數量的數據庫連接放到連接池中,這些數據庫連接的數量是由最?數據庫連接數來設定的。?論這些數據庫連接是否被使?,連接池都將?直保證?少擁有這么多的連接數量。連接池的最?數據庫連接數量限定了這個連接池能占有的最?連接數,當應?程序向連接池請求的連接數超過最?連接數量時,這些請求將被加?到等待隊列中。
橋接模式,首先DriverManager獲得Connection是通過反射和類加載機制從數據庫驅動包的driver中拿到連接,所以這里真正參與橋接模式的是driver,而DriverManager和橋接模式沒有關系,DriverManager只是對driver的一個管理器。而我們作為使用者只去關心Connection,不會去關心driver,因為我們的操作都是通過操作Connection來實現的。這樣分析下來這個橋接就清晰了邏輯——java.sql.Driver作為抽象橋類,而驅動包如com.mysql.jdbc.Driver具體的實現橋接類,而Connection是被橋接的對象。
默認情況下,我們創建的數據庫連接,是工作在自動提交的模式下的。這意味著只要我們執行完一條查詢語句,就會自動進行提交。因此我們的每條查詢,實際上都是一個事務,如果我們執行的是DML或者DDL,每條語句完成的時候,數據庫就已經完成修改了。有的時候我們希望由一組SQL查詢組成一個事務,如果它們都執行OK我們再進行提交,如果中途出現異常了,我們可以進行回滾。
JDBC接口提供了一個setAutoCommit(boolean flag)方法,我們可以用它來關閉連接自動提交的特性。我們應該在需要手動提交時才關閉這個特性,不然的話事務不會自動提交,每次都得手動提交。數據庫 通過表鎖來管理事務,這個操作非常消耗資源。因此我們應當完成操作后盡快的提交事務。在這里有更多關于事務的示例程序。
CLOB意思是Character Large OBjects,字符大對象,它是由單字節字符組成的字符串數據,有自己專門的代碼頁。這種數據類型適用于存儲超長的文本信息,那些可能會超出標準的VARCHAR數據類型長度限制(上限是32KB)的文本。
BLOB是Binary Larget OBject,它是二進制大對象,由二進制數據組成,沒有專門的代碼頁。它能用于存儲超過VARBINARY限制(32KB)的二進制數據。這種數據類型適合存儲圖片,聲音,圖形,或者其它業務程序特定的數據。
每個類都有一個 Class 對象,包含了與類有關的信息。當編譯一個新類時,會產生一個同名的 .class 文件,該文件內容保存著 Class 對象。類加載相當于 Class 對象的加載,類在第一次使用時才動態加載到 JVM 中。也可以使用 Class.forName,這種方式來控制類的加載,該方法會返回一個 Class 對象。
反射可以提供運行時的類信息,并且這個類可以在運行時才加載進來,甚至在編譯時期該類的 .class 不存在也可以加載進來。Class 和 java.lang.reflect 一起對反射提供了支持,java.lang.reflect 類庫主要包含了以下三個類:
(1)Field :可以使用 get() 和 set() 方法讀取和修改 Field 對象關聯的字段;
(2)Method :可以使用 invoke() 方法調用與 Method 對象關聯的方法;
(3)Constructor :可以用 Constructor 創建新的對象。
應用舉例:工廠模式,使用反射機制,根據全限定類名獲得某個類的 Class 實例。
反射是用來描述類的信息的。對于任意一個類,都能夠知道這個類的所有屬性和方法;對于任意一個對象,都能夠調用它的任意一個方法和屬性;這種動態獲取的信息以及動態調用對象的方法的功能稱為 Java 語言的反射機制。
class:用來描述類本身
Packge:用來描述類所屬的包
Field:用來描述類中的屬性
Method:用來描述類中的方法
Constructor:用來描述類中的構造方法
Annotation:用來描述類中的注解
1)Class clazz=class.forName("包名.類名")
2)Class clazz=類名.class;
3)Class clazz=對象.getClass();
(1)獲取類的權限修飾符--------->int result=getModifiers();
(2)獲取名字------------>string name=clazz.getName();
(3)獲取包名------------>Packge p=clazz.getPackge();
(4)尋找clazz中無參數構造方法:Clazz.getConstructor([String.class]);
執行構造方法創建對象:Con.newInstance([參數]);
(5)Field c=cls.getFields():獲得某個類的所有的公共(public)的字段,包括父類中的字段。
Field c=cls.getDeclaredFields():獲得某個類的所有聲明的字段,即包括public、private和 proteced,但是不包括父類的聲明字段。
(1)反射得經典用法就是在xml或者properties配置文件中,然后在java類里面區解析這些內容,得到一個字符串,然后通過反射機制,通過這些字符串獲得某個類得class實例,這樣的話就可以動態的配置一些東西,而不需要每次都重新去new,要改的話也是直接改配置文件,代碼維護起來方便很多。
(2)當你在做一個軟件開發的插件的時候,你連插件的類型名稱都不知道,你怎么實例化這個對象呢?因為程序是支持插件的(第三方的),在開發的時候并不知道 。所以無法在代碼中 New出來 ,但反射可以,通過反射,動態加載程序集,然后讀出類,檢查標記之后再實例化對象,就可以獲得正確的類實例。
(3)在編碼階段不知道那個類名,要在運行期從配置文件讀取類名, 這時候就沒有辦法硬編碼new ClassName(),而必須用到反射才能創建這個對象.反射的目的就是為了擴展未知的應用。比如你寫了一個程序,這個程序定義了一些接口,只要實現了這些接口的dll都可以作為插件來插入到這個程序中。那么怎么實現呢?就可以通過反射來實現。就是把dll加載進內存,然后通過反射的方式來調用dll中的方法。很多工廠模式就是使用的反射。
getSimpleName:只獲取類名
getName:類的全限定名,jvm中Class的表示,可以用于動態加載Class對象,例如Class.forName。
getCanonicalName:返回更容易理解的表示,主要用于輸出(toString)或log打印,大多數情況下和getName一樣,但是在內部類、數組等類型的表示形式就不同了。
可以;必須只有一個類名與文件名相同。
byte 的取值范圍是 -128 -> 127 之間,一共是 256 位。一個 byte 類型在計算機中占據一個字節,那么就是 8 bit,所以最大就是 2^7 = 1111 1111。
Java 中用補碼來表示二進制數,補碼的最高位是符號位,最高位用 0 表示正數,最高位 1 表示負數,正數的補碼就是其本身,由于最高位是符號位,所以正數表示的就是 0111 1111 ,也就是 127。最大負數就是 1111 1111,這其中會涉及到兩個 0 ,一個 +0 ,一個 -0 ,+0 歸為正數,也就是 0 ,-0 歸為負數,也就是 -128,所以 byte 的范圍就是 -128 – 127。
在最外層循環前加一個標記如outfor,然后用break outfor;可以跳出多重循環。例如以下代碼:
public class TestBreak {
????public static void main(String[] args) {
????????outfor: for (int i = 0; i < 10; i++){
???????? for (int j = 0; j < 10; j++){
???????? if (j == 5){
???????????? break outfor;
???????????? }
???????????? System.out.println("j = " + j);
???????? }
???? }
????}
}
運行結果如下所示:
j = 0
j = 1
j = 2
j = 3
j = 4
早期的 JDK 中,switch(expr)中,expr 可以是 byte、short、char、int。從 1.5 版開始,Java 中引入了枚舉類型(enum),expr 也可以是枚舉,從 JDK 1.7 版開始,還可以是字符串(String)。長整型(long)是不可以的。
&運算符是:邏輯與;&&運算符是:短路與。
1)&和&&在程序中最終的運算結果是完全一致的,只不過&&存在短路現象。如果是&運算符,那么不管左邊的表達式是true還是false,右邊表達式是一定會執行的。當&&運算符左邊的表達式結果為false的時候,右邊的表達式不執行,此時就發生了短路現象,也就是說&&會更加的智能。這就是他們倆的本質區別。
2)當然,&運算符還可以使用在二進制位運算上,例如按位與操作。
char 類型可以存儲一個中文漢字,因為Java中使用的編碼是Unicode編碼,一個char 類型占2個字節(16 比特),所以放一個中文是沒問題的。
補充:使用Unicode 意味著字符在JVM內部和外部有不同的表現形式,在JVM內部都是 Unicode,當這個字符被從JVM內部轉移到外部時(例如存入文件系統中),需要進行編碼轉換。所以 Java 中有字節流和字符流,以及在字符流和字節流之間進行轉換的轉換流,如 InputStreamReader和OutputStreamReader,這兩個類是字節流和字符流之間的適配器類,承擔了編碼轉換的任務。
早期的 JDK 中,switch(expr)中,expr 可以是 byte、short、char、int。從 1.5 版開始,Java 中引入了枚舉類型(enum),expr 也可以是枚舉,從 JDK 1.7 版開始,還可以是字符串(String)。長整型(long)是不可以的。
Math.round(11.5)的返回值是12,Math.round(-11.5)的返回值是-11。四舍五入的原理是在參數上加0.5然后進行取整。
前者不正確,后者正確。
對于 short s1 = 1; s1 = s1 + 1;由于 1 是 int 類型,因此 s1+1 運算結果也是 int 型,需要強制轉換類型才能賦值給 short 型。
而 short s1 = 1; s1 += 1;可以正確編譯,因為 s1+= 1;相當于 s1 = (short)(s1 + 1);其中有隱含的強制類型轉換。
Java中的數組沒有length()方法,但是有length屬性。String有length()方法。
2 << 3,將2左移3位
public class Test{
????public static void main(String[] args) {
???????Integer f1 = 100, f2 = 100, f3 = 150, f4 = 150; System.out.println(f1 == f2);
???????System.out.println(f3 == f4);
????}
}
f1==f2的結果是 true,而f3==f4 的結果是false。為什么呢?先來說說裝箱的本質。當我們給一個Integer 對象賦一個 int 值的時候,會調用 Integer 類的靜態方法 valueOf,如果看看valueOf的源代碼就知道發生了什么。如果整型字面量的值在-128 到 127 之間,那么不會 new 新的 Integer 對象,而是直接引用常量池中的Integer對象,所以上面的面試題中f1==f2的結果是 true,而f3==f4 的結果是false。
Java 的JDK從 1.5 開始引入了自動裝箱/拆箱機制。它為每一個基本數據類型都引入了對應的包裝類型(wrapper class),int的包裝類就是 Integer,其它基本類型對應的包裝類如下:
原始類型: boolean,char,byte,short,int,long,float,double包裝類型:Boolean,Character,Byte,Short,Integer,Long,Float,Double
方法的返回值是指我們獲取到的某個方法體中的代碼執行后產生的結果?。ㄇ疤崾窃摲椒赡?產生結果)。返回值的作用:接收出結果,使得它可以用于其他的操作!
調用數值類型相應包裝類中的方法 parse***(String)或 valueOf(String) 即可返回相應基本類型或包裝類型數值;
將數字與空字符串相加即可獲得其所對應的字符串;另外對于基本類型 數字還可調用 String 類中的 valueOf(…)方法返回相應字符串,而對于包裝類型數字則可調用其 toString()方法獲得相應字符串;
可用該數字構造一 java.math.BigDecimal 對象,再利用其 round()方法 進行四舍五入到保留小數點后兩位,再將其轉換為字符串截取最后兩位。
false,因為有些浮點數不能完全精確的表示出來。
java中有三種移位運算符
<< :左移運算符,x << 1,相當于x乘以2(不溢出的情況下),低位補0
>> :帶符號右移,x >> 1,相當于x除以2,正數高位補0,負數高位補1
>>> :無符號右移,忽略符號位,空位都以0補齊
泛型中類型擦除 Java泛型這個特性是從JDK 1.5才開始加入的,因此為了兼容之前的版本,Java泛型的實現采取了“偽泛型”的策略,即Java在語法上支持泛型,但是在編譯階段會進行所謂的“類型擦除”(Type Erasure),將所有的泛型表示(尖括號中的內容)都替換為具體的類型(其對應的原生態類型),就像完全沒有泛型一樣。
注解是JDK1.5版本開始引入的一個特性,用于對代碼進行說明,可以對包、類、接口、字段、方法參數、局部變量等進行注解。它主要的作用有以下四方面: 生成文檔,通過代碼里標識的元數據生成javadoc文檔。 編譯檢查,通過代碼里標識的元數據讓編譯器在編譯期間進行檢查驗證。 編譯時動態處理,編譯時通過代碼里標識的元數據動態處理,例如動態生成代碼。 運行時動態處理,運行時通過代碼里標識的元數據動態處理,例如使用反射注入實例。
Java自帶的標準注解,包括@Override、@Deprecated和@SuppressWarnings,分別用于標明重寫某個方法、標明某個類或方法過時、標明要忽略的警告,用這些注解標明后編譯器就會進行檢查。
元注解:元注解是用于定義注解的注解,包括@Retention、@Target、@Inherited、@Documented @Retention用于標明注解被保留的階段 @Target用于標明注解使用的范圍 @Inherited用于標明注解可繼承 @Documented用于標明是否生成javadoc文檔 自定義注解,可以根據自己的需求定義注解,并可用元注解對自定義注解進行注解。
整體上是封裝、繼承、多態、抽象。
首先面向對象是一種思想。在java中萬事萬物皆對象。類是對相同事物的一種抽象、是不可見的,對象具體的、可見的。由對象到類的過程是抽象的過程,由類到對象的過程是實例化的過程。面向對象的三大特征分別是封裝、繼承和多態。
封裝隱藏了類的內部實現機制,對外界而言它的內部細節是隱藏的,暴露給外界的只是它的訪問方法。例如在屬性的修飾符上我們往往用的private私有的,這樣其它類要想訪問就通過get和set方法。因此封裝可以程序員按照既定的方式調用方法,不必關心方法的內部實現,便于使用; 便于修改,增強 代碼的可維護性。
繼承在本質上是特殊~一般的關系,即常說的is-a關系。子類繼承父類,表明子類是一種特殊的父類,并且具有父類所不具有的一些屬性或方法。比如從貓類、狗類中可以抽象出一個動物類,具有和貓、狗、虎類的共同特性(吃、跑、叫等)。通過extends關鍵字來實現繼承。Java中的繼承是單繼承,即一個子類只允許有一個父類。
Java多態是指的是首先兩個類有繼承關系,其次子類重寫了父類的方法,最后父類引用指向子類對象。如Animal a=new Dog();這行代碼就體現了多態。
Java中的多態靠的是父類或接口定義的引用變量可以指向子類或具體實現類的實例對象,而程 序調用的方法在運行期才動態綁定,就是引用變量所指向的具體實例對象的方法,也就是內存 里正在運行的那個對象的方法,而不是引用變量的類型中定義的方法。
名字與類名相同;
沒有返回值,但不能用void聲明構造函數;
生成類的對象時自動執行,無需調用。
構造器不能被繼承,因此不能被重寫,但可以被重載。
super可以理解為是指向自己超(父)類對象的一個指針,而這個超類指的是離自己最近的一 個父類。
super也有三種用法:
1.普通的直接引用
與this類似,super相當于是指向當前對象的父類的引用,這樣就可以用super.xxx來引用父類的成員。
2.子類中的成員變量或方法與父類中的成員變量或方法同名時,用super進行區分
class Person{?
? ? protected String name;?
? ? public Person(String name){?
? ? ? ? this.name = name;?
? ? }?
}?
class StudentextendsPerson{?
? ? private String name;?
? ? publicStudent(String name, String name1){?
? ? ? ? super(name);?
? ? ? ? this.name = name1;?
? ? }?
? ? public void getInfo(){?
? ? ? ? System.out.println(this.name);?
? ? ? ? System.out.println(super.name);?
? ? }?
}?
public class Test{?
? ? public static void main(String[] args){?
? ? ? ? Student s1 = new Student("Father", "Child");?
? ? ? ? s1.getInfo();?
? ? }?
}
3.引用父類構造函數
super(參數):調用父類中的某一個構造函數(應該為構造函數中的第一條語句)。
this(參數):調用本類中另一種形式的構造函數(應該為構造函數中的第一條語句)。
super:它引用當前對象的直接父類中的成員(用來訪問直接父類中被隱藏的父類中成員
數據或函數,基類與派生類中有相同成員定義時如:super.變量名 super.成員函數據名 (實參)
this:它代表當前對象名(在程序中易產生二義性之處,應使用this來指明當前對象;如果函數的形參與類中的成員數據同名,這時需用this來指明成員變量名)
super()和this()區別是
[1]super()在子類中調用父類的構造方法,this()在本類內調用本類的其它構造方法。
[2]super()和this()均需放在構造方法內第一行。盡管可以用this調用一個構造器,但卻不能調用兩個。
[3]this和super不能同時出現在一個構造函數里面,因為this必然會調用其它的構造函數,其它的構造函數必然也會有super語句的存在,所以在同一個構造函數里面有相同的語 句,就失去了語句的意義,編譯器也不會通過。
[4]this()和super()都指的是對象,所以,均不可以在static環境中使用。包括:static變
量,static方法,static語句塊。從本質上講,this是一個指向本對象的指針, 然而super是一個Java關鍵字。
方法的重載和重寫本質都是實現多態的方式,區別在于前者實現的是編譯時的多態性,而后者實現的是運行時的多態。
方法重載的規則:
1)方法名一致,
2)參數列表不同(參數順序不同或者參數類型不同或者參數個數不同)。
3)重載與方法的返回值無關,這個很關鍵。
方法重寫的規則:
1)參數列表和返回值類型必須完全與父類的方法一致
2)構造方法不能被重寫,聲明為 final 的方法不能被重寫,聲明為 static 的方法不能被重寫,但是能夠被再次聲明。
3)訪問權限不能比父類中被重寫的方法的訪問權限更低。
4)重寫的方法能夠拋出任何檢查異常(編譯時異常),但是重寫的方法不能拋出比被重寫方法聲明的更廣泛的運行時異常。
1)接口中的所有方法都是抽象的,而抽象類可以有抽象方法,也可以有實例方法。
2)類需要繼承,接口需要實現。一個類可以實現多個接口,但只能繼承一個父類但接口卻可以繼承多接口。
3)接口與實現它的類不構成繼承體系,即接口不是類體系的一部分。因此,不相關的類也可以實現相同的接口,而抽象類是屬于類的繼承體系,并且一般位于類體系的頂層。
接口可以繼承接口。抽象類可以實現(implements)接口,抽象類可繼承具體類,但前提是具體類必須有明確的構造函數。
值傳遞是指在調用函數時將實際參數復制一份到函數中,這樣的話如果函數對其傳遞過來的形式參數進行修改,將不會影響到實際參數。
引用傳遞是指在調用函數時將對象的地址直接傳遞到函數中,如果在對形式參數進行修改,將影響到實際參數的值。
equals 和== 最大的區別是一個是方法一個是運算符。
1)基本類型中,==比較的是數值是否相等。equals方法是不能用于基本數據類型數據比較的,因為基本數據類型壓根就沒有方法。
2)引用類型中,==比較的是對象的地址值是否相等。equals方法比較的是引用類型的變量所指向的對象的地址是否相等。應為String這個類重寫了equals方法,比較的是字符串的內容。
hashCode() 的作用是獲取哈希碼,也稱為散列碼;它實際上是返回一個int整數。這個哈希碼 的作用是確定該對象在哈希表中的索引位置。hashCode() 定義在JDK的Object.java中,這就 意味著Java中的任何類都包含有hashCode()函數。
在Java中,每個對象都可以調用自己的hashCode方法得到自己的哈希值(hashCode),相當于對象的指紋信息,通常說世界上沒有完全一樣的指紋,但是在Java中沒有這么絕對,我們依然可以用hashCode值來做一些提前的判斷。
1)如果兩個對象的hashCode值不一樣,那么他們肯定是不同的兩個對象;
2)如果兩個對象的hashCode值一樣,也不代表就是同一個對象;
3)如果兩個對象的equals方法相等,那么他們的hashCode值一定相等。
在Java的一些集合類的實現中,在比較兩個對象的值是否相等的時候,會根據上面的基本原則,先調用對象的hashCode值來進行比較,如果hashCode值不一樣,就可以認定這是兩個不一樣的數據,如果hashCode值相同,我們會進一步調用equals()方法進行內容的比較。
equals 方法是用來比較對象大小是否相等的方法,hashcode 方法是用來判斷每個對象 hash 值的一種方法。如果只重寫 equals 方法而不重寫 hashcode 方法,很可能會造成兩個不同的對象,它們的 hashcode 也相等,造成沖突。
例如:String str1 = "通話"; String str2 = "重地";
它們兩個的 hashcode 相等,但是 equals 可不相等。
不對,如果兩個對象x和y滿足x.equals(y) == true,它們的哈希碼(hash code)應當相同。
Java對于eqauls方法和hashCode方法是這樣規定的:
(1)如果兩個對象相同(equals方法返回true),那么它們的hashCode值一定要相同;
(2)如果兩個對象的hashCode相同,它們并不一定相同。
當然,你未必要按照要求去做,但是如果你違背了上述原則就會發現在使用容器時,相同的對 象可以出現在Set集合中,同時增加新元素的效率會大大下降(對于使用哈希存儲的系統,如 果哈希碼頻繁的沖突將會造成存取性能急劇下降)。
都不能。
1)抽象方法需要子類重寫,而靜態的方法是無法被重寫的,因此二者是矛盾的。
2)本地方法是由本地代碼(如 C++ 代碼)實現的方法,而抽象方法是沒有實現的,也是矛盾的。
3)synchronized 和方法的實現細節有關,抽象方法不涉及實現細節,因此也是相互矛盾的。
修飾類:當用final修飾一個類時,表明這個類不能被繼承。正如String類是不能被繼承的。final類中的成員變量可以根據需要設為final,但是要注意final類中的所有成員方法都會被隱式地指定為final方法。
修飾方法:使用final修飾方法的原因有兩個。第一個原因是把方法鎖定,以防任何繼承類修改它的含義;第二個原因是效率。在早期的Java實現版本中,會將final方法轉為內嵌調用。但是如果方法過于龐大,可能看不到內嵌調用帶來的任何性能提升。在最近的Java版本中,不需要使用final方法進行這些優化了。因此,只有在想明確禁止該方法在子類中被覆蓋的情況下才將方法設置為final。(注:一個類中的private方法會隱式地被指定為final方法)
修飾變量:對于被final修飾的變量,如果是基本數據類型的變量,則其數值一旦在初始化之后便不能更改;如果是引用類型的變量,則在對其初始化之后便不能再讓其指向另一個對象。雖然不能再指向其他對象,但是它指向的對象的內容是可變的。
public class Demo1 {
????public static void main(String[] args) ?{
????????MyClass myClass1 = new MyClass();
????????MyClass myClass2 = new MyClass();
????????System.out.println(myClass1.i);
????????System.out.println(myClass2.i);
????????System.out.println(myClass1.j);
????????System.out.println(myClass2.j); ?
????}
}
class MyClass {
????public final double i = Math.random();
????public static double j = Math.random();
}
運行結果:
0.3222977275463088
0.2565532218939688
0.36856868882926397
0.36856868882926397
每次打印的兩個j值都是一樣的,而i的值卻是不同的。從這里就可以知道final和static變量的區別了。static屬于類級別的不可變,而final是對象級別的不可變。
1)final:用于聲明屬性,方法和類,分別表示屬性不可變,方法不可覆蓋,被其修飾的類不可繼承。
2)finally:異常處理語句結構的一部分,表示總是執行。
3)finalize:Object 類的一個方法,當java對象沒有更多的引用指向的時候,系統會自動的由垃圾回收器來負責調用此方法進行回收前的準備工作和垃圾回收。
靜態變量: 靜態變量由于不屬于任何實例對象,屬于類的,所以在內存中只會有一份,在類的 加載過程中,JVM只為靜態變量分配一次內存空間。
實例變量: 每次創建對象,都會為每個對象分配成員變量內存空間,實例變量是屬于實例對象 的,在內存中,創建幾次對象,就有幾份成員變量。
靜態方法和實例方法的區別主要體現在兩個方面:
在外部調用靜態方法時,可以使用"類名.方法名"的方式,也可以使用"對象名.方法名"的 方式。而實例方法只有后面這種方式。也就是說,調用靜態方法可以無需創建對象。
靜態方法在訪問本類的成員時,只允許訪問靜態成員(即靜態成員變量和靜態方法), 而不允許訪問實例成員變量和實例方法;實例方法則無此限制。
class A{
static{
System.out.print("1");
}
public A(){
System.out.print("2");
}
}
class B extends A{
static{
System.out.print("a");
}
public B(){
System.out.print("b");
}
}
public class Hello{
public static void main(String[] ars){
A ab = new B(); //執行到此處,結果: 1a2b
ab = new B(); //執行到此處,結果: 1a2b2b
}
}?
輸出結果為 1a2b2b;修飾符 | 當前類 | 同包 | 子類 | 其它包 |
---|---|---|---|---|
public | 是 | 是 | 是 | 是 |
protected | 是 | 是 | 是 | 否 |
默認(缺省) | 是 | 是 | 否 | 否 |
private | 是 | 否 | 否 | 否 |
類的成員不寫訪問修飾時默認為default。默認對于同一個包中的其他類相當于公開 (public),對于不是同一個包中的其他類相當于私有(private)。受保護(protected)對 子類相當于公開,對不是同一包中的沒有父子關系的類相當于私有。Java中,外部類的修飾符 只能是public或默認,類的成員(包括內部類)的修飾符可以是以上四種。
goto 是Java中的保留字,在目前版本的Java中沒有使用。(根據James Gosling(Java之 父)編寫的《The Java Programming Language》一書的附錄中給出了一個Java關鍵字列 表,其中有goto和const,但是這兩個是目前無法使用的關鍵字,因此有些地方將其稱之為保 留字,其實保留字這個詞應該有更廣泛的意義,因為熟悉C語言的程序員都知道,在系統類庫 中使用過的有特殊意義的單詞或單詞的組合都被視為保留字)
由于 Java 不支持多繼承,而有可能某個類或對象要使用分別在幾個類或對象里面的方法或屬性,現有的單繼承機制就不能滿足要求。與繼承相比,接口有更高的靈活性,因為接口中沒有任何實現代碼。當一個類實現了接口以后,該類要實現接口里面所有的方法和屬性,并且接口里面的屬性在默認狀態下面都是public static,所有方法默認情況下是 public.一個類可以實現多個接口。
不是。Java 中的基本數據類型只有 8 個:byte、short、int、long、float、double、char、boolean;除了基本類型(primitive type)外,剩下的都是引用類型(reference type)
1)String是只讀的字符串,因此String引用的字符串內容是不能被改變的。
String str = "abc";
str = "bcd";
如上,第一行str 僅僅是一個引用對象,它指向一個字符串對象“abc”。第二行代碼的含義是讓 str 重新指向了一個新的字符串“bcd”對象,而“abc”對象并沒有任何改變
2)StringBuffer/StringBuilder 表示的字符串對象可以直接進行修改。
3)StringBuilder 是 Java5 中引入的,它和 StringBuffer 的方法完全相同,區別在于它是在單線程環境下使用的,因為它的所有方法都沒有被 synchronized 修飾,因此它的效率理論上也比 StringBuffer 要高。
不一樣,因為內存的分配方式不一樣。String str = "i"的方式JVM會將其分配到常量池中,而 String str = new String("i")JVM會將其分配到堆內存中。
String 類是final類,不可以被繼承。
補充:繼承String本身就是一個錯誤的行為,對String類型最好的重用方式是關聯關系 (Has-A)和依賴關系(Use-A)而不是繼承關系(Is-A)。
兩個對象,一個是靜態存儲區的"xyz",一個是用 new 創建在堆上的對象。
indexof();返回指定字符的的索引。
charAt();返回指定索引處的字符。
replace();字符串替換。
trim();去除字符串兩端空格。
splt();字符串分割,返回分割后的字符串數組。
getBytes();返回字符串byte類型數組。
length();返回字符串長度。
toLowerCase();將字符串轉換為小寫字母。
toUpperCase();將字符串轉換為大寫字母。
substring();字符串截取。
equals();比較字符串是否相等。
數組沒有 length()方法,有 length 的屬性。String 有 length()方法。JavaScript 中,獲得字符串的長度是通過 length 屬性得到的,這一點容易和 Java混淆
String s1 = "你好"; String s2 = newString(s1.getBytes("GB2312"), "ISO-8859-1");
首先會判斷要比較的兩個字符串它們的引用是否相等。如果引用相等的話,直接返回 true ,不相等的話繼續下面的判斷,然后再判斷被比較的對象是否是 String 的實例,如果不是的話直接返回 false,如果是的話,再比較兩個字符串的長度是否相等,如果長度不想等的話也就沒有比較的必要了;長度如果相同,會比較字符串中的每個 字符 是否相等,一旦有一個字符不相等,就會直接返回 false。
用遞歸實現字符串反轉,代碼如下所示:
public static String reverse(String originStr) {
? if(originStr == null || originStr.length() <= 1)
? return originStr;
? return reverse(originStr.substring(1)) + originStr.charAt(0);
}
string.substring(from):相當于從from位置截取到原字符串末尾
charAt() 方法用于返回指定索引處的字符。索引范圍為從 0 到 length() - 1。
public String[] split(String str, int chars){
int n = (str.length()+ chars - 1)/chars;
String ret[] = new String[n];
for(int i=0; i<n; i++){
if(i < n-1){
ret[i] = str.substring(i*chars , (i+1)*chars);
}else{
ret[i] = str.substring(i*chars);
}
}
return ret;
}
代碼如下:
public String subString(String str, int subBytes) {
int bytes = 0; // 用來存儲字符串的總字節數
for (int i = 0; i < str.length(); i++) {
if (bytes == subBytes) {
return str.substring(0, i);
}
char c = str.charAt(i);
if (c < 256) {
bytes += 1; // 英文字符的字節數看作 1
} else {
bytes += 2; // 中文字符的字節數看作 2
if(bytes - subBytes == 1){
return str.substring(0, i);
}
}
}
return str;
}
package test;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
public class RandomSort {
public static void printRandomBySort() {
Random random = new Random(); // 創建隨機數生成器
List list = new ArrayList(); // 生成 10 個隨機數,并放在集合 list 中
for (int i = 0; i < 10; i++) {
list.add(random.nextInt(1000));
}
Collections.sort(list); // 對集合中的元素進行排序
Iterator it = list.iterator();
int count = 0;
while (it.hasNext()) { // 順序輸出排序后集合中的元素
System.out.println(++count + ": " + it.next());
}
}
public static void main(String[] args) {
printRandomBySort();
}
}
public int countWords(String file, String find) throws Exception {
int count = 0;
Reader in = new FileReader(file);
int c;
while ((c = in.read()) != -1) {
while (c == find.charAt(0)) {
for (int i = 1; i < find.length(); i++) {
c = in.read();
if (c != find.charAt(i))
break;
if (i == find.length() - 1)
count++;
}
}
}
return count;
}
Object類提供了如下幾個常用方法:
Class<?> getClass():返回該對象的運行時類。
boolean equals(Object obj):判斷指定對象與該對象是否相等。
int hashCode():返回該對象的hashCode值。在默認情況下,Object類的hashCode()方法根據該對象的地址來計算。但很多類都重寫了Object類的hashCode()方法,不再根據地址來計算其hashCode()方法值。
String toString():返回該對象的字符串表示,當程序使用System.out.println()方法輸出一個對象,或者把某個對象和字符串進行連接運算時,系統會自動調用該對象的toString()方法返回該對象的字符串表示。Object類的toString()方法返回 運行時類名@十六進制hashCode值 格式的字符串,但很多類都重寫了Object類的toString()方法,用于返回可以表述該對象信息的字符串。
另外,Object類還提供了wait()、notify()、notifyAll()這幾個方法,通過這幾個方法可以控制線程的暫停和運行。Object類還提供了一個clone()方法,該方法用于幫助其他對象來實現“自我克隆”,所謂“自我克隆”就是得到一個當前對象的副本,而且二者之間完全隔離。由于該方法使用了protected修飾,因此它只能被子類重寫或調用。
Object類提供的equals()方法默認是用==來進行比較的,也就是說只有兩個對象是同一個對象時,才能返回相等的結果。而實際的業務中,我們通常的需求是,若兩個不同的對象它們的內容是相同的,就認為它們相等。鑒于這種情況,Object類中equals()方法的默認實現是沒有實用價值的,所以通常都要重寫。
垃圾回收器回收對象前,會調用此方法,可以在此方法中做釋放資源等清理操作
這兩個方法用來提示 JVM 要進行垃圾回收。但是,立即開始還是延遲進行垃圾回收是取決于 JVM 的。
利用 java.text.DataFormat 的子類(如 SimpleDateFormat 類)中的 format(Date)方法可將日期格式化。
參考如下源代碼:
public class YesterdayCurrent{
public static void main(String[] args){
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DATE, -1);
System.out.println(cal.getTime());
}
}
使用Math.random()可以生成0.1到1.0范圍內的隨機數字,然后通過數學方法實現生成 符合要求的隨機數。
在Java中,可以將一個類的定義放在另外一個類的定義內部,這就是 內部類 。內部類本身就 是類的一個屬性,與其他屬性定義方式一致。
內部類可以分為四種: 成員內部類、局部內部類、匿名內部類和靜態內部類 。
一個內部類對象可以訪問創建它的外部類對象的成員,包括私有成員。
可以繼承其他類或實現其他接口,在 Swing 編程中常用此方式來實現事件監聽和回調
Throwable 是 Java 語言中所有錯誤與異常的超類。
Error 類及其子類:程序中無法處理的錯誤,表示運行應用程序中出現了嚴重的錯誤。
Exception 程序本身可以捕獲并且可以處理的異常。Exception 這種異常又分為兩類:運行時異常和編譯時異常。
運行時異常 都是RuntimeException類及其子類異常,如NullPointerException(空指針異常)、IndexOutOfBoundsException(下標越界異常)等,這些異常是不檢查異常,程序中可以選擇捕獲處理,也可以不處理。這些異常一般是由程序邏輯錯誤引起的,程序應該從邏輯角度盡可能避免這類異常的發生。 運行時異常的特點是Java編譯器不會檢查它,也就是說,當程序中可能出現這類異常,即使沒有用try-catch語句捕獲它,也沒有用throws子句聲明拋出它,也會編譯通過。
非運行時異常 (編譯異常)是RuntimeException以外的異常,類型上都屬于Exception類及其子類。從程序語法角度講是必須進行處理的異常,如果不處理,程序就不能編譯通過。如IOException、SQLException等以及用戶自定義的Exception異常,一般情況下不自定義檢查異常。
異常需要處理的時機分為編譯時異常(也叫受控異常)也叫 CheckedException 和運行時異常(也叫非受控異常)也叫 UnCheckedException。Java認為Checked異常都是可以被處理的異常,所以Java程序必須顯式處理Checked異常。如果程序沒有處理Checked 異常,該程序在編譯時就會發生錯誤無法編譯。這體現了Java 的設計哲學:沒有完善錯誤處理的代碼根本沒有機會被執行。
對Checked異常處理方法有兩種:
● 第一種:當前方法知道如何處理該異常,則用try...catch塊來處理該異常。
● 第二種:當前方法不知道如何處理,則在定義該方法時聲明拋出該異常。
運行時異常只有當代碼在運行時才發行的異常,編譯的時候不需要try…catch。Runtime如除數是0和數組下標越界等,其產生頻繁,處理麻煩,若顯示申明或者捕獲將會對程序的可讀性和運行效率影響很大。所以由系統自動檢測并將它們交給缺省的異常處理程序。當然如果你有處理要求也可以顯示捕獲它們。
Java 的異常處理是通過 5 個關鍵詞來實現的:try、catch、throw、throws 和 finally。
一般情況下是用 try 來執行一段程序,如果出現異常,系統會拋出(throw)一個異常,這時候你可以通過它的類型來捕捉(catch)它,或最后(finally)由缺省處理器來處理;try 用來指定一塊預防所有“異常”的程序;catch 子句緊跟在 try 塊后面,用來指定你想要捕捉的“異常”的類型;throw 語句用來明確地拋出一個“異常”;throws 用來標明一個成員函數可能拋出的各種“異常”;finally 為確保一段代碼不管發生什么“異常”都被執行一段代碼;可以在一個成員函數調用的外面寫一個 try 語句,在這個成員函數內部寫另一個 try 語句保護其他代碼。每當遇到一個 try 語句,“異常”的框架就放到棧上面,直到所有的try 語句都完成。如果下一級的 try 語句沒有對某種"異常"進行處理,棧就會展開,直到遇到有處理這種"異常"的 try 語句。
會執行,在方法返回調用者前執行。Java 允許在 finally 中改變返回值的做法是不好的,因為如果存在 finally 代碼塊,try 中的 return 語句不會立馬返回調用者,而是記錄下返回值待 finally 代碼塊執行完畢之后再向調用者返回其值,然后如果在 finally 中修改了返回值,這會對程序造成很大的困擾,C#中就從語法上規定不能做這樣的事。
Error 表示系統級的錯誤和程序不必處理的異常,是恢復不是不可能但很困難的情況下的一種嚴重問題;比如內存溢出,不可能指望程序能處理這樣的情況;Exception 表示需要捕捉或者需要程序進行處理的異常,是一種設計或實現問題;也就是說,它表示如果程序運行正常,從不會發生的情況。
public int getNum() {
try {
int a = 1 / 0;
return 1;
} catch (Exception e) {
return 2;
} finally {
return 3;
}
}
分析:代碼走到第3行的時候遇到了一個MathException,這時第4行的代碼就不會執行了,代碼直接跳轉到catch語句中,走到第 6 行的時候,異常機制有一個原則:如果在catch中遇到了return或者異常等能使該函數終止的話那么有finally就必須先執行完finally代碼塊里面的代碼然后再返回值。因此代碼又跳到第8行,可惜第8行是一個return語句,那么這個時候方法就結束了,因此第6行的返回結果就無法被真正返回。因此上面返回值是3。
有如下代碼片斷:
try{
throw new ExampleB(“b”);
}catch(ExampleA e){
System.out.printfln(“ExampleA”);
}catch(Exception e){
System.out.printfln(“Exception”);
}
輸出的內容應該是:ExampleA
●java.lang.NullPointerException 空指針異常;出現原因:調用了未經初始化的對象或者是不存在的對象。
● java.lang.IndexOutOfBoundsException 數組角標越界異常,常見于操作數組對象時發生。
● java.lang.ClassNotFoundException 指定的類找不到;出現原因:類的名稱和路徑加載錯誤;通常都是程序試圖通過字符串來加載某個類時可能引發異常。
● java.lang.ClassCastException 數據類型轉換異常。
● java.lang.SQLException SQL異常,常見于操作數據庫時的 SQL 語句錯誤。
● throw:
throw 語句用在方法體內,表示拋出異常,由方法體內的語句處理。
throw是具體向外拋出異常的動作,所以它拋出的是一個異常實例,執行throw一定是拋出了某種異常。
● throws:
throws語句是用在方法聲明后面,表示如果拋出異常,由該方法的調用者來進行異常的處理。
throws主要是聲明這個方法會拋出某種類型的異常,讓它的使用者要知道需要捕獲的異常的類型。
如果你的資源實現了 AutoCloseable 接口,你可以使用這個語法。大多數的 Java 標準資源都繼承了這個接口。當你在 try 子句中打開資源,資源會在 try 代碼塊執行后或異常處理后自動關閉。
public void automaticallyCloseResource() {
File file = new File("./tmp.txt");
try (FileInputStream inputStream = new FileInputStream(file);) {
// use the inputStream to read a file
} catch (FileNotFoundException e) {
log.error(e);
} catch (IOException e) {
log.error(e);
}
}
public static void simpleTryCatch() {
try {
testNPE();
} catch (Exception e) {
e.printStackTrace();
}
}
使用javap來分析這段代碼(需要先使用javac編譯)。
//javap -c Main
public static void simpleTryCatch();
Code:
0: invokestatic #3 // Method testNPE:()V
3: goto 11
6: astore_0
7: aload_0
8: invokevirtual #5 // Method java/lang/Exception.printStackTrace:()V
11: return
Exception table:
from to target type
0 3 6 Class java/lang/Exception
異常表中包含了一個或多個異常處理者(Exception Handler)的信息,這些信息包含如下
1)from 可能發生異常的起始點
2)to可能發生異常的結束點
3)target上述from和to之前發生異常后的異常處理者的位置
4)type異常處理者處理的異常的類信息
IO流就是以流的方式進行輸入輸出。主要用來處理設備之間的傳輸,文件的上傳,下載和復制。
流分輸入和輸出,輸入流從文件中讀取數據存儲到進程中,輸出流從進程中讀取數據然后寫入到目標文件。
按照流的方向:輸入流(inputStream)和輸出流(outputStream)
按照實現功能分:節點流(可以從或向一個特定的地方(節點)讀寫數據。如 FileReader)和處理流(是對一個已存在的流的連接和封裝,通過所封裝的流的功能調用實現數據讀寫。如 BufferedReader。處理流的構造方法總是要帶一個其他的流對象做參數。一個流對象經過其他流的多次包裝,稱為流的鏈接。)
按照處理數據的單位: 字節流和字符流。字節流繼承于 InputStream 和 OutputStream, 字符流繼承于Reader 和 Writer 。
1)字節流讀取的時候,讀到一個字節就返回一個字節;字符流讀取的時候會讀到一個或多個字節(這個要根據字符流中編碼設置,一般中文對應的字節數是兩個,在UTF-8碼表中是3個字節)
2)字節流可以處理所有類型數據,如:圖片,MP3,AVI視頻文件,而字符流只能處理字符數據。只要是處理純文本數據,就要優先考慮使用字符流,除此之外都用字節流。
3)字節流在操作時本身不會用到緩沖區(內存),是文件本身直接操作的,而字符流在操作時使用了緩沖區,通過緩沖區再操作文件。
案例1:在寫操作的過程中,沒有關閉字節流操作,但是文件中也依然存在了輸出的內容代碼如下:
public static void main(String[] args) throws Exception {
// 第1步:使用File類找到一個文件
File f = new File("d:" + File.separator + "test.txt"); // 聲明File 對象
// 第2步:通過子類實例化父類對象
OutputStream out = new FileOutputStream(f);
// 第3步:進行寫操作
String str = "Hello World!!!"; // 準備一個字符串
byte b[] = str.getBytes(); // 字符串轉byte數組
out.write(b); // 將內容輸出
// 第4步:關閉輸出流
// out.close();
}
案例2:在寫操作的過程中,沒有關閉字符流操作,發現文件中沒有任何內容輸出。代碼如下:
public static void main(String[] args) throws Exception {
// 第1步:使用File類找到一個文件
File f = new File("d:" + File.separator + "test.txt");// 聲明File 對象
// 第2步:通過子類實例化父類對象
Writer out = new FileWriter(f);
// 第3步:進行寫操作
String str = "Hello World!!!"; // 準備一個字符串
out.write(str); // 將內容輸出
out.flush();
// 第4步:關閉輸出流
// out.close();
}
這是因為字符流操作時使用了緩沖區,而在關閉字符流時會強制性地將緩沖區中的內容進行輸出,但是如果程序沒有關閉,則緩沖區中的內容是無法輸出的。當然如果在不關閉字符流的情況下也可以使用Writer類中的flush()強制性的清空緩存,從而將字符流的內容全部輸出。
解題思路:把字節流轉成字符流就要用到適配器模式,需要用到OutputStreamWriter。它繼承了Writer接口,但要創建它必須在構造函數中傳入一個OutputStream的實例,OutputStreamWriter的作用也就是將OutputStream適配到Writer。它實現了Reader接口,并且持有了InputStream的引用。利用轉換流OutputStreamWriter.創建一個字節流對象,將其作為參數傳入轉換流OutputStreamWriter中得到字符流對象.
序列化是指把對象轉換為字節序列的過程,序列化后的字節流保存了對象的狀態以及相關的描述信息,從而方便在網絡上傳輸或者保存在本地文件中,達到對象狀態的保存與重建的目的。
反序列化:客戶端從文件中或網絡上獲得序列化后的對象字節流后,根據字節流中所保存的對象狀態及描述信息,通過反序列化重建對象。
序列化的優勢:一是實現了數據的持久化,通過序列化可以把數據永久地保存到硬盤上(通常存放在文件里),二是,利用序列化實現遠程通信,即在網絡上傳送對象的字節序列。三是通過序列化在進程間傳遞對象;
(1)java.io.ObjectOutputStream:表示對象輸出流;它的writeObject(Object obj)方法可以對參數指定的obj對象進行序列化,把得到的字節序列寫到一個目標輸出流中;
(2)java.io.ObjectInputStream:表示對象輸入流;它的readObject()方法源輸入流中讀取字節序列,再把它們反序列化成為一個對象,并將其返回;
注意:只有實現了Serializable或Externalizable接口的類的對象才能被序列化,否則拋出異常!
序列化和反序列化的示例
public class SerialDemo {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//序列化
FileOutputStream fos = new FileOutputStream("object.out");
ObjectOutputStream oos = new ObjectOutputStream(fos);
User user1 = new User("xuliugen", "123456", "male");
oos.writeObject(user1);
oos.flush();
oos.close();
//反序列化
FileInputStream fis = new FileInputStream("object.out");
ObjectInputStream ois = new ObjectInputStream(fis);
User user2 = (User) ois.readObject();
System.out.println(user2.getUserName()+ " " +
user2.getPassword() + " " + user2.getSex());
//反序列化的輸出結果為:xuliugen 123456 male
}
}
public class User implements Serializable {
private String userName;
private String password;
private String sex;
//全參構造方法、get和set方法省略
}
1. PrintStream 類的輸出功能非常強大,通常如果需要輸出文本內容,都應該將輸出流包裝成PrintStream 后進行輸出。它還提供其他兩項功能。與其他輸出流不同,PrintStream 永遠不會拋出 IOException;而是,異常情況僅設置可通過 checkError 方法測試的內部標志。另外,為了自動刷新,可以創建一個 PrintStream
2.BufferedWriter:將文本寫入字符輸出流,緩沖各個字符從而提供單個字符,數組和字符串的高效寫入。通過 write()方法可以將獲取到的字符輸出,然后通過 newLine()進行換行操作。BufferedWriter 中的字符流必須通過調用 flush 方法才能將其刷出去。并且 BufferedWriter 只能對字符流進行操作。如果要對字節流操作,則使用 BufferedInputStream
3.PrintWriter 的 println 方法自動添加換行,不會拋異常,若關心異常,需要調用 checkError方法看是否有異常發生,PrintWriter 構造方法可指定參數,實現自動刷新緩存(autoflush)。
因為明確說了是對字節流的讀取,所以肯定是InputStream或者他的子類,又因為要大量讀取,肯定要考慮到高效的問題,自然想到緩沖流BufferedInputStream。
原因:BufferedInputStream是InputStream的緩沖流,使用它可以防止每次讀取數據時進行實際的寫操作,代表著使用緩沖區。不帶緩沖的操作,每讀一個字節就要寫入一個字節,由于涉及磁盤的IO操作相比內存的操作要慢很多,所以不帶緩沖的流效率很低。帶緩沖的流,可以一次讀很多字節,但不向磁盤中寫入,只是先放到內存里。等湊夠了緩沖區大小的時候一次性寫入磁盤,這種方式可以減少磁盤操作次數,速度就會提高很多!并且也可以減少對磁盤的損傷。
集合就是一個放數據的容器,準確的說是放數據對象引用的容器;集合類存放的都是對象的引用,而不是對象的本身;集合類型主要有3種:set(集)、list(列表)和map(映射)。
Java1.5引入了泛型,所有的集合接口和實現都大量地使用它。泛型允許我們為集合提供一個可以容納的對象類型,因此,如果你添加其它類型的任何元素,它會在編譯時報錯。這避免了在運行時出現ClassCastException,因為你將會在編譯時得到報錯信息。泛型也使得代碼整潔,我們不需要使用顯式轉換和instanceOf操作符。它也給運行時帶來好處,因為不會產生類型檢查的字節碼指令。
Iterator接口提供遍歷任何Collection的接口。我們可以從一個Collection中使用迭代器方法來獲取迭代器實例。迭代器取代了Java集合框架中的Enumeration。迭代器允許調用者在迭代過程中移除元素。
數組是固定長度的;集合可變長度的。
數組可以存儲基本數據類型,也可以存儲引用數據類型;集合只能存儲引用數據類型。
數組存儲的元素必須是同一個數據類型;集合存儲的對象可以是不同數據類型。
Map接口和Collection接口是所有集合框架的父接口:
Collection接口的子接口包括:Set接口和List接口
Map接口的實現類主要有:HashMap、TreeMap、Hashtable、ConcurrentHashMap以及Properties等
Set接口的實現類主要有:HashSet、TreeSet、LinkedHashSet等
List接口的實現類主要有:ArrayList、LinkedList、Stack以及Vector等
java.util.Collection 是一個集合接口(集合類的一個頂級接口)。它提供了對集合對象進行基本操作的通用接口方法。Collection接口在Java 類庫中有很多具體的實現。Collection接口的意義是為各種具體的集合提供了最大化的統一操作方式,其直接繼承接口有List與Set。
Collections則是集合類的一個工具類/幫助類,其中提供了一系列靜態方法,用于對集合中元素進行排序、搜索以及線程安全等各種操作。
Set 接口實例存儲的是無序的,不重復的數據。List 接口實例存儲的是有序的,可以重復的元素。都可以存儲null值,但是set不能重復所以最多只能有一個空元素。
Set檢索效率低下,刪除和插入效率高,插入和刪除不會引起元素位置改變 <實現類有HashSet,TreeSet>。
List和數組類似,可以動態增長,根據實際存儲的數據的長度自動增長List的長度。查找元素效率高,插入刪除效率低,因為會引起其他元素位置改變 <實現類有ArrayList,LinkedList,Vector> 。
1)Arraylist 底層使用的是Object數組;LinkedList 底層使用的是雙向循環鏈表數據結構;
2)ArrayList 采用數組存儲,所以插入和刪除元素的時間復雜度受元素位置的影響。插入末尾還好,如果是中間,則(add(int index, E element))接近O(n);LinkedList 采用鏈表存儲,所以插入,刪除元素時間復雜度不受元素位置的影響,都是近似 O(1)而數組為近似 O(n)。對于隨機訪問get和set,ArrayList優于LinkedList,因為LinkedList要移動指針。
3)LinkedList 不支持高效的隨機元素訪問,而ArrayList 實現了RandmoAccess 接口,所以有隨機訪問功能??焖匐S機訪問就是通過元素的序號快速獲取元素對象(對應于get(int index)方法)。所以ArrayList隨機訪問快,插入慢;LinkedList隨機訪問慢,插入快。
4)ArrayList的空 間浪費主要體現在在list列表的結尾會預留一定的容量空間,而LinkedList的空間花費則體現在它的每一個元素都需要消耗比ArrayList更多的空間(因為要存放直接后繼和直接前驅以及數據)。
ArrayList和Vector在很多時候都很類似。
(1)兩者都是基于索引的,內部由一個數組支持。
(2)兩者維護插入的順序,我們可以根據插入順序來獲取元素。
(3)ArrayList和Vector的迭代器實現都是fail-fast的。
(4)ArrayList和Vector兩者允許null值,也可以使用索引值對元素進行隨機訪問。
以下是ArrayList和Vector的不同點。
(1)Vector是同步的,而ArrayList不是。然而,如果你尋求在迭代的時候對列表進行改變,你應該使用CopyOnWriteArrayList。
(2)ArrayList比Vector快,它因為有同步,不會過載。
(3)ArrayList更加通用,因為我們可以使用Collections工具類輕易地獲取同步列表和只讀列表。
List<String> strList = new ArrayList<>();
//使用for-each循環
for(String obj : strList){
System.out.println(obj);
}
//using iterator
Iterator<String> it = strList.iterator();
while(it.hasNext()){
String obj = it.next();
System.out.println(obj);
}
當把對象加入到HashSet中時,HashSet會先計算對象的hashCode值來判斷對象加入的下標位置,同時也會與其他的對象的hashCode進行比較,如果沒有相同的,就直接插入數據;如果有相同的,就進一步使用equals來進行比較對象是否相同,如果相同,就不會加入成功。
1.使用foreach循環遍歷
Map<String, String> hashMap = new HashMap<String,String>();
hashMap.put("1", "good");
hashMap.put("2", "study");
hashMap.put("3", "day");
hashMap.put("4", "up");
for (Map.Entry<String, String> entry : hashMap.entrySet()) {
? ? System.out.println(entry.getKey()+":"+entry.getValue());
}
2.使用foreach迭代鍵值對
Map<String, String> hashMap = new HashMap<String,String>();
hashMap.put("1", "good");
hashMap.put("2", "study");
hashMap.put("3", "day");
hashMap.put("4", "up");
for (String key : hashMap.keySet()) {
? ? System.out.println(key);
}
for (String value : hashMap.values()) {
? ? System.out.println(value);
}
3.使用迭代器
Map<String, String> hashMap = new HashMap<String,String>();
hashMap.put("1", "good");
hashMap.put("2", "study");
hashMap.put("3", "day");
hashMap.put("4", "up");
Iterator<Map.Entry<String, String>> iterator = hashMap.entrySet().iterator();
while (iterator.hasNext()) {
? ? Map.Entry<String, String> next = iterator.next();
? ? System.out.println(next.getKey()+":"+next.getValue());
}
4.使用lambda表達式
Map<String, String> hashMap = new HashMap<String,String>();
hashMap.put("1", "good");
hashMap.put("2", "study");
hashMap.put("3", "day");
hashMap.put("4", "up");
hashMap.forEach((k,v)-> System.out.println(k+":"+v));
相同點:
都是實現來Map接口(hashTable還實現了Dictionary 抽象類)。
不同點:
1. 歷史原因:Hashtable 是基于陳舊的 Dictionary 類的,HashMap 是 Java 1.2 引進的 Map 接口
的一個實現,HashMap把Hashtable 的contains方法去掉了,改成containsvalue 和containsKey。因為contains方法容易讓人引起誤解。
2. 同步性:Hashtable 的方法是 Synchronize 的,線程安全;而 HashMap 是線程不安全的,不是同步的。所以只有一個線程的時候使用hashMap效率要高。
3. 值:HashMap對象的key、value值均可為null。HahTable對象的key、value值均不可為null。
4. 容量:HashMap的初始容量為16,Hashtable初始容量為11,兩者的填充因子默認都是0.75。
5. HashMap擴容時是當前容量翻倍即:capacity * 2,Hashtable擴容時是容量翻倍+1 即:capacity * 2+1。
HashSet 底層就是基于 HashMap 實現的。只不過HashSet里面的HashMap所有的value都是同一個Object而已,因此HashSet也是非線程安全的。
1. HashMap通過hashcode對其內容進行快速查找,而TreeMap中所有的元素都保持著某種固定的順序,如果你需要得到一個有序的結果你就應該使用TreeMap(HashMap中元素的排列順序是不固定的)。
2. 在Map 中插入、刪除和定位元素,HashMap是最好的選擇。但如果您要按自然順序或自定義順序遍歷鍵,那么TreeMap會更好。使用HashMap要求添加的鍵類明確定義了hashCode()和 equals()的實現。
每當向數組中添加元素時,都要去檢查添加后元素的個數是否會超出當前數組的長度,如果超出,數組將會進行擴容,以滿足添加數據的需求。數組擴容通過ensureCapacity(int minCapacity)方法來實現。在實際添加大量元素前,我也可以使用ensureCapacity來手動增加ArrayList實例的容量,以減少遞增式再分配的數量。 數組進行擴容時,會將老數組中的元素重新拷貝一份到新的數組中,每次數組容量的增長大約是其原容量的1.5倍。這種操作的代價是很高的,因此在實際使用時,我們應該盡量避免數組容量的擴張。當我們可預知要保存的元素的多少時,要在構造ArrayList實例時,就指定其容量,以避免數組擴容的發生。或者根據實際需求,通過調用ensureCapacity方法來手動增加ArrayList實例的容量。
ArrayList也采用了快速失敗的機制,通過記錄modCount參數來實現。在面對并發的修改時,迭代器很快就會完全失敗,而不是冒著在將來某個不確定時間發生任意不確定行為的風險。