第二章 2.1 class文件的生成 java文件爲源代碼文件 class爲程序. class文件實時修改. eclipse自動生成. project下面clean. 2.2 jar文件 如何將有用的類傳給別人使用. 1.把*.java文件發給對方. 2.把*.class打包發給對方. 導出爲jar文件. 右鍵export Java JAR file 2.3使用jar文件 new java project test2 右鍵 new folder libs 複製jar 粘貼到libs 右鍵jar build path add to 而後調用裏面的方法,再聲明就好了 庫Library 2.4系統jar文件 String java.lang.String ArrayList java.util.ArrayList 第三方jar 系統庫System Library 三方庫 第三章 3.1 抽象類Abstract Class 建立抽象類 public abstract class XXX //抽象類 { public abstract void yyy(); //抽象方法 不能有大括號(方法體) } 抽象方法能夠沒有定義,稱爲抽象方法 抽象類不可實例化: FunnyThing f=new FunnyThing(); //錯誤 不能夠實例化. 抽象類僅僅用於描述一類事情:應該有什麼,應該能作什麼.它不是具體類,不能建立對象. 全部抽象類方法的定義在子類裏實現. 3.2抽象類的用法 暫時不須要寫抽象類 java.io.InputStream java.io.OutputStream 第四章 4.1接口 接口 interface(或翻作:界面) 定義一個接口 public interface AudioOutput { public void play(AudioData s); //不能寫方法體{} 必須是public 默認接口都是抽象方法. } 使用接口 和抽象類同樣 必須派生一個子類; public class XiaoMi implements AudioOutput { @Override public void play(AudioData s) { } } 接口和抽象類類似,區別爲: 1.用implements 而不是extends(不表示繼承關係) 2.一個類能夠implements多個接口 public class X extends Y implements A,B,C 3接口不該該添加屬性.(能夠添加,但沒有意義) 接口和繼承是兩個不一樣的設計概念 4.2接口的使用 當一個系統與另一個系統對接時. 接口的使用 第五章 內部類 5.1內部類 當一個類寫在另外一個類內部時,稱爲內部類 內部類一般使用private,外部不可見. 若是你想在外部使用內部類,定義爲public. 5.2內部類的使用 在內部類的裏面能夠訪問外部類的全部方法和屬性. 訪問外部類時 + class名.this. public class Example { private String name; private void show() { System.out.println("名字:"+name); } public void test() { ABC abc=new ABC(); abc.work(); } public class ABC { public void work() { Example.this.name="shao fa"; Example.this.show(); } } } 5.3靜態內部類 在外面建立內部類對象 public class Example { public class ABC { } } Example e=new Example(); //實例化a Example.ABC a=e.new ABC(); //實例化a 靜態內部類 public class X { public String name; public static class Y { } } Example.ABC a=new Example.ABC(); //實例化a 使用靜態內部類 就沒法使用Example.this.name="shao fa"; 5.4 匿名內部類 //很是經常使用 內部類的簡化寫法 public interface XXX XXX a=new XXX(){}; 直接在大括號內重寫XXX接口的方法 不用建立子類. 匿名內部類 還有一個寫法 c.xxx(new XXX(){ }) 第六章 靜態對象 靜態static 在Java中表示"全局的" 靜態對象,即全局對象,一直存在的. 定義靜態對象 public class MMM { public static XXX a=new XXX(); } 使用靜態對象. MMM.a.yyy(); //yyy 爲XXX下面的方法 要點 1.在第一次使用時,靜態對象被建立 例如:Example類被使用時,Example.a被建立 若是Example從未被使用,Exampee.a永不建立 2.靜態對象不會被系統回收 3.靜態對象只有一個實例 不管建立多少個Example對象,Example.a只建立一次. 6.2 單例模式 全局&&惟一實例 全局使用static實現,單例使用private實現. 在程序運行期間一直存在,惟一:只有一個實例 public class Earth { public static Earth i=new Earth(); //static成爲了全局實例. private Earth() //使用private 不能在外面添加新的實例,成爲單例. 限制實例的建立. 讓構造方法私有化 { } public void showCountries() { System.out.print("惟一地球"); } } 第七章 出錯處理 7.1出錯處理 考慮2種狀況,若是輸入的字符串有問題. 1.輸入非法字符. 2.用戶輸入過長的字符,超出int的極限. public class Converter { public int status = 0; // 把一個字符串轉成整數 // 例如: "123" -> 123 public int str2int (String str) { status = 0; if(str.length()>11) { status = -2; // 第2種狀況 return 0; } int result = 0; for(int i=0; i<str.length(); i++) { char ch = str.charAt(i); if( ! isValid(ch) ) { status = -1; // 第1種狀況 return 0; } result = result * 10 + (ch - '0'); } return result; } private boolean isValid(char ch) { if(ch >= '0' && ch <= '9')return true; if(ch == '-') return false; return false; } } public class Test { public static void main(String[] args) { Converter conv = new Converter(); int result = conv.str2int("2201234"); if(conv.status == 0) { System.out.println("轉換結果: " + result); } else { if(conv.status == -1) System.out.println("非法字符"); else if(conv.status == -2) System.out.println("超出範圍"); } } } 可預期的錯誤狀況. 7.2異常機制 1.在方法裏,拋出異常 int str2int(String str)throws Exception { if(錯誤1發生) throw new Exception("錯誤1"); if(錯誤2發生) throw new Exception("錯誤2"); } public int str2int (String str) throws Exception //添加throws Exception { status = 0; if(str.length()>11) throw new Exception("超出範圍"); //拋出異常 也能夠寫做Exception ex=new Exception("超出範圍") int result = 0; //throw ex; for(int i=0; i<str.length(); i++) { char ch = str.charAt(i); if( ! isValid(ch) ) throw new Exception("非法字符"); //拋出異常 result = result * 10 + (ch - '0'); } return result; } public static void main(String[] args) throws Exception //在main方法的入口添加throws Exception 2.在調用時,捕獲異常 調用時,用try...catch...來捕獲異常 try:監視若干行代碼,若是裏面拋出異常,則進入catch{} catch:出錯處理,參數爲剛剛拋出的異常對象,全部出錯信息都在異常對象裏. public static void main(String[] args) //或者使用try catch { Converter conv = new Converter(); int result; try { result = conv.str2int("12134"); System.out.println("正常"+result); //正常狀況下走try } catch (Exception e) { System.out.println(e.getMessage()); //異常時走catch } Java裏廣泛使用異常機制來進行出錯處理 Exception對象自己就能夠攜帶全部出錯信息 7.3自定義異常 自定義異常 派生出Exception的子類 try { int result = conv.str2int("2023321238"); System.out.println("正常:" + result); } catch( InvalidCharException e1) { System.out.println(e1.getMessage()); } catch ( TooLargeException e2) { System.out.println(e2.getMessage()); } catch( Exception e) { System.out.println(e.getMessage()); } public class InvalidCharException extends Exception { public int pos; // 非法字符出現的位置 public char ch; // 非法字符 public InvalidCharException(int pos, char ch) { this.pos = pos; this.ch = ch; } @Override public String getMessage() { return "非法字符'" + ch + "',位置:" + pos; } } public int str2int (String str) throws Exception { if(str.length()>11) { Exception ex = new TooLargeException(str.length()); //throw new TooLargeException(str.length()); throw ex; } int result = 0; for(int i=0; i<str.length(); i++) { char ch = str.charAt(i); if( ! isValid(ch) ) throw new InvalidCharException(i, ch); result = result * 10 + (ch - '0'); } return result; } 7.4異常運行規則 1.拋出異常時,退出當前的方法 運行規則:捕獲異常時 1.try{...}中出現異常時,退出try,進入catch 2.若是沒有匹配到catch. 中斷當前方法 繼續向上拋出 上層調用者有義務抓住這個異常 main()-->a()-->b()-->c() 3.若是一個異常最終沒被抓住,程序崩潰. 7.5退出清理 當異常出現時,doCleanJobs沒有機會執行 退出清理finally 使用finally語句能夠保證tyu{}退出時執行某些退出清理代碼 tyu{} catch..... //若干個 finally{} 規則:當退出try時,老是執行finally中的語句. 當異常發生時,跳出當前執行finally後結算. e.printStackTrace(); //相對於e.getMessage有更多的信息 包含:-出錯的代碼行位置 異常的類型 每層方法的調用位置(函數棧) 7.6基本異常 語法類(基本異常) 1.空指針異常 NullPointerException 2.數組越界異常 ArrayIndexOutOfBoundsException 3.除零異常 ArithmeticException 即便沒有throws聲明,也能捕獲異常. Throwable父類 Error子類 Exception子類. 第八章 泛型 8.1泛型 通用的類型, 通用鏈表 GenericList 封裝一個通用的鏈表設計 設計思路 1.不限制節點類型. private static class GenericNode { Object value; //使用Object作爲節點類型. GenericNode next; } 2.迭代器 8.2泛型的定義 泛型通常用於描述一種通用的算法,數據結構,對象類型其實不關心 在Java中泛型的規範寫法. public class Sample<T> { } 在類的定義裏,T表明一個通用的類型,把T稱爲類型參數 T只是一個代號. 泛型的使用 public class Sample<T> { T value; public void setValue(T value) { this.value=value; } public T getValue() { return this.value; } } Simple<Student> sa=new Simple<Student>(); sa.setValue(new Student(1,"s","sd")); Student s=sa.getValue(); System.out.println(s); 8.3經常使用的泛型 List,ArrayList Map,HashMap 一般不須要咱們本身寫泛型,JDK自帶的兩個泛型足夠使用 ArrayList:數組鏈表. ArrayList<Student> ArrayList<String> ArrayList<Integer> 必須使用包裝類. 使用 添加,插入 遍歷:數組形式遍歷,鏈表遍歷 刪除:數組形式刪除,鏈表刪除 1.排序 ArrayList<Student> list = new ArrayList<Student>(); list.add( new Student(1, "shao", "13810012345")); list.add( new Student(2, "wang", "15290908889")); list.add( new Student(3, "li", "13499230340")); list.add( 0,new Student(4, "xxx", "1293023923923")); //從0的位置去插入 2.遍歷 import java.util.Iterator; Iterator<Student> iter=list.iterator(); while(iter.hasNext()) { Student s=iter.next(); System.out.println(s); } 鏈表的方式遍歷 數組的方式遍歷 for(int i=0;i<list.size();i++) { Student s=list.get(i); System.out.println(s); } 3.刪除元素 數組方式刪除 list.remove(index) 鏈表方式刪除 Iterator<Student>iter=list.iterator(); 4.排序 package my; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; public class Test { public static void main(String[] args) { ArrayList<Student> list=new ArrayList<Student>(); list.add(new Student(1,"陳","139")); //添加 list.add(new Student(2,"人","138")); list.add(new Student(-1,"好","137")); list.add(0,new Student(4,"我","136")); //插入 // Iterator<Student> iter=list.iterator();//刪除 // while(iter.hasNext()) // { // Student s=iter.next(); // if(s.id==2) // { // iter.remove(); // } // } //排序 Comparator<Student> comp=new Comparator<Student>() //定義一個比較器 { @Override public int compare(Student a,Student b) { if(a.id<b.id) return -1; if(a.id>b.id) return 1; return 0; } }; //有分號 //排序 Collections.sort(list, comp); Iterator<Student> iter1=list.iterator(); //遍歷 while(iter1.hasNext()) { Student s=iter1.next(); System.out.println(s); } } } 8.4 哈希映射表 HashMap HashMap存儲 key<=>value HashMap的建立 須要制定key和value的類型 1.建立 HashMap<Integer,Student> map=.... //指定key的類型是Interger,value的類型是Student. HashMap<Integer,Student>map=new HashMap(); 2 添加對象 map.put(1, new Student(1,"2","3")); //map.put(key,new value());添加 map.put(2, new Student(4,"5","6")); //key爲1,2,3,3, value爲Student對象 map.put(3, new Student(7,"8","9")); map.put(3, new Student(11,"12","13")); //同key值之後面一個爲準 3.查找 Student s=map.get(3); //查找的是key值 if(s!=null) { System.out.println(s); } 4.刪除 刪除一個對象 map.remove(key); //輸入要刪除的key值 刪除全部 map.clear(); 5遍歷全部key和value 一般狀況下map不遍歷 //遍歷全部的key import java.util.Set; //聲明 Set<Integer>keys=map.keySet(); //遍歷全部的key for(Integer k:keys) { System.out.println("key:"+k); } //遍歷全部的value for(Student v:map.values()) { System.out.println("values:"+v); } 重點可否以姓名作爲key? key的要求 1.能夠比較 //String能夠比較 2.惟一不重複 //若是姓名不重複,則能夠. Map的查找比list快? HashMap查找更快 第九章JDK和JRE 9.1jdk jre jdk Java開發工具包 jre Java運行環境 JDK是開發時的環境,JRE時程序運行環境. JRE時JDK的子集; 9.2Java命令行 類的加載 類加載器ClassLoader ClassLoader裏尋找並加載須要的類 加載主類:my.Hello 根據import聲明加載須要的類. 9.3可執行JAR包 cmd cd /d d:\eclipse-workspace //移動到d盤目錄下 java -jar example804.jar //執行jar包 jar包中有哪些東西 全部的class,所依賴的jar包裏的class 清單文件META-INF/MANIFEST.MF 可發現,在清單文件裏已經指定了Main Class 打包jar包 右鍵JAVA -Runnable JAR file Java應用程序發佈 -JRE環境 -class,jar -執行腳本(雙擊運行).bat .cmd .sh 9.4命令行參數 命令行裏的參數將被傳遞給main方法 public static void main(String[] args) { } main方法的參數:來自命令行參數; java -cp bin my.Hello 2 3 9.5JVM JVM Java虛擬機 一個虛擬主機能夠運行class文件 當Java程序運行時 打開jconsole,鏈接到java進程(JVM),觀測CPU/內存/線程等信息 jdk1.8下面 作網站時會用到jconsole工具. 第十章 Java官方文檔 https://docs.oracle.com/en/ 經常使用api Java API即java再帶的類庫 經常使用的 lang util io>net math>sql security nio>....... java官方文檔至關於字典. 左上package 左下class 10.2集成文檔和源碼 多看文檔,少看源碼 10.3 文檔的使用方法 整數1234,轉換成2進制字符串 第11章 11.1時間的表示 long 時間值 java.util.Date 時間對象 java.util.Calendar 時間操做工具 java.text.SimpleDateFormat 格式化工具類. 時間值long 單位:毫秒 從1970開始的時間值 運行一段代碼花費的時間 public class Test { public void someBusyWork() { long start =System.currentTimeMillis(); //初始時間 for(int i=0;i<2002000;i++) { double a=Math.sin(i); } long duration=System.currentTimeMillis()-start; //求時間 System.out.println(duration); } public static void main(String[] args) { long now =System.currentTimeMillis(); Test t=new Test(); t.someBusyWork(); } } java.util.Date 基本都是Deprecated的類.不同意使用. java.util.Calendar 計算年月日時分秒 封裝的時間工具. Calendar cal=Calendar.getInstance(); int year=cal.get(Calendar.YEAR); int month=cal.get(Calendar.MONTH); //月份的時間是從0開始計算,其餘都是正常. int day=cal.get(Calendar.DAY_OF_MONTH); int house=cal.get(Calendar.HOUR); int minute=cal.get(Calendar.MINUTE); int second=cal.get(Calendar.SECOND); Calendar與long的轉換. //Calendar ->long long ms=cal.getTimeInMillis(); //long->Calendar cal.setTimeInMillis(ms); Calendar與Date轉換. //Calendar->Date Dated=cal.getTime(); //Date->Calendar cal.setTime(d); 11.2時間的處理 -時間與日期的差值 計算2018年2月10日-2018年3月10日之間多少天. public static void main(String[] args) { Calendar c1=Calendar.getInstance(); //建立一個c1的實例 getInstance實例化. Calendar c2=Calendar.getInstance(); c1.set(2018, 1, 10, 0, 0, 0); //日期轉爲long的毫米 月份的起始數爲0,0表示1月,1表示2月. c2.set(2018, 2, 10, 0, 0, 0); long ms=c2.getTimeInMillis()-c1.getTimeInMillis(); //相差的毫米 long days=ms/(24*3600*1000); //由於結果是毫米因此*1000 System.out.println("相差天數爲: "+days); } 日期的推算. public static void main(String[] args) { Calendar c1=Calendar.getInstance(); c1.set(2018, 2, 2, 0, 0, 0); //3月2日 c1.add(Calendar.DAY_OF_MONTH,+32); //第一個參數表示要修改哪一個字段,第二個參數表示差值 System.out.printf("結果爲:%d-%d-%d ", c1.get(Calendar.YEAR), c1.get(Calendar.MONTH)+1, c1.get(Calendar.DAY_OF_MONTH)); //月份由於初始值爲0全部要+1顯示. } 其中,add()的第一個參數表示要修改哪一個字段,第二個參數表示差值 時間和日期的格式化 Date->String public static void main(String[] args) { SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//大寫的HH 24小時制,小寫的hh12小時制. Date now =new Date(); String str=sdf.format(now); System.out.println("日期:"+str); } String->Date public static void main(String[] args) { SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); try { String s1="2018-3-2 11:29:00"; Date t1=sdf.parse(s1); } catch(ParseException e) { e.printStackTrace(); } 第12章文件的操做 File文件,用於進行文件/目錄相關操做 java.io.File 在Java文檔裏找到此類的說明. 1.File對象用於指向一個文件或者目錄 "e:/examples/abc.txt" "e\\examples\\abc.txt" (要轉義) 並未建立實際對象,只是建立File對象. public static void main(String[] args) { File f=new File("e:/examples/abc.txt"); } 2.判斷對象是否存在. public static void main(String[] args) { File f=new File("e:/examples/abc.txt"); if(f.exists()) //判斷語句 f.exists { System.out.println("文件存在"+f); } else { System.out.println("文件不存在"); } } 3.獲取文件的屬性 是文件仍是目錄isFile() isDirectory() 文件長度length() 最後修改時間lastModified() public static void main(String[] args) { File a=new File("D:\\eclipse\\abc.txt"); //文件是否存在判斷 if(a.isFile()) { System.out.println("是文件"); } long size=a.length(); //長度判斷 System.out.println(size); long s=a.lastModified(); //最後的修改時間獲取和輸出 SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String timestr =sdf.format(s); System.out.println(timestr); } 可讀,可寫,可執行? 4文件的操做 重命名 File.renameTo public static void main(String[] args) { File a=new File("D:\\eclipse\\abc.txt"); if(a.isFile()) { System.out.println("是文件"); } a.renameTo(new File("D:\\eclipse\\abcd.txt")); } 5.絕對路徑與相對路徑 相對路徑 "../other/video.mp4" "subdir/xy.doc" 絕對路徑 便是完整路徑 .getAbsolutePath(); "D:\\eclipse\\abc.txt" 12.2目錄的操做 File類 if(f.exists)判斷目錄是否存在 if(f.isDirectory) 判斷是不是目錄 建立層級目錄 public static void main(String[] args) { File d=new File("E:\\abc\\a\\b\\c"); d.mkdirs(); } 會建立沒有的目錄 遍歷子項 使用listFile()能夠遍歷目錄下的子目錄/文件 僅掃描一級目錄 public static void main(String[] args) { File d=new File("D:\\eclipse"); File[]subFiles=d.listFiles(); //設立一個數組subFiles存儲 for(File f:subFiles) { if(f.isDirectory()) { System.out.println("掃描到目錄"+f); } else { System.out.println("掃描到文件"+f); } } 能夠在listFilter的時候設置一個過濾器 File d=new File("D:\\eclipse"); FileFilter filter=new FileFilter() //過濾器fliter 建立一個匿名類 { @Override public boolean accept(File pathname) { String filePath=pathname.getAbsolutePath(); //建立一個字符創filePath得到他的絕對路徑 if(filePath.endsWith(".rar")) //判斷結尾是不是.rar .endsWith(); return true; return false; } }; File[]subFiles=d.listFiles(filter); //設立一個數組subFiles存儲,把filter過濾器參數傳給他 for(File f:subFiles) { if(f.isDirectory()) { System.out.println("掃描到目錄"+f); } else { System.out.println("掃描到文件"+f); } } 3思考如何作遞歸查找,繼續查找子目錄 如何建立一個子目錄 File homeDir =new File("e:/examples"); //先寫出父目錄 File f=new File(homeDir,"some.txt"); //再建立子目錄 newFile時能夠直接指定它的父目錄. File時個輕量級對象,沒必要轉爲String 12.3 相對路徑 絕對路徑從根目錄開始指定. 相對路徑,指定一個相對於當前目錄的路徑 File f=new File("src/my/Test.java"); 當前目錄 E:/JavaProjects/example1203 全路徑 E:/JavaProjects/example1203/src/my/Test.java 相對路徑示例 ./data/config.txt ../example1101/src ../../WebProjects/ ./src/../data/config.txt .表示本目錄 ..表示父目錄 工做目錄 咱們所說的當前路徑,指的是程序的工做目錄 默認的,工做目錄時程序運行時的起始目錄 注意: 工做目錄不是class文件所在的目錄 注意: 在Eclipse|Run Configuration 裏能夠指定 就是運行這個程序時的起始目錄. String workDir=System.getProperty("user.dir"); //查詢當前的工做目錄在哪 //修改工做目錄 System.setProperty("user.dir","d:"); 12.4 複製與移動 apache commons io 使用第三方的庫來完成文件和目錄操做 新建一個項目,添加commons-io-2.4.jar (1)建立目錄libs (2)拷貝commons-io-2.4.jar到libs (3)右鍵jar文件,選Add to Build Path 經常使用文件操做 FileUtils類支持的文件和目錄 FileUtils.deleteQuietly()刪除文件 FileUtils.copyFile()拷貝文件 FileUtils.copyFileToDirectory()複製文件到目錄 FileUtils.moveFile()移動文件 FileUtils.moveFileDirectory()移動目錄 //拷貝文件示例 public static void main(String[] args) { File src=new File("E:\\abc.txt"); File dst=new File("E:\\abc\\a.txt"); try { FileUtils.copyFile(src, dst); }catch(IOException e) { e.printStackTrace(); } System.out.println("完成"); } 第13章 13.1 文件的存儲 文件:視頻,音頻,圖片,文檔,程序.... 數據存儲在文件中,關閉電腦後,數據不丟失. 不管是各類文件裏面存儲的都是byte[]字節數據 字符串的存儲 String<=>byte[] String=>byte[] String text="abcdefg"; byte[] data=text.getBytes("UTF-8"); byte[]=>String String text2=new String(data,"UTF-8"); 寫入文件 output寫入 public static void main(String[] args) { File dir=new File("e:/examples"); dir.mkdirs(); //建立目錄 File f=new File(dir,"123.txt"); String text="人人人1234aaaa"; try { FileOutputStream outputStream=new FileOutputStream(f); byte[] data=text.getBytes("UTF-8"); //data把字符串編碼成字節數據 outputStream.write(data); //將字節數據寫入文件 outputStream.close(); //關閉文件 }catch(Exception e) { e.printStackTrace(); } } win10下面不能再分區根目錄下面建立文件. 讀取文件 input讀取 public static void main(String[] args) { File dir=new File("e:/examples"); File f=new File(dir,"123.txt"); try { FileInputStream inputStream=new FileInputStream(f); int size=(int)f.length(); byte[]data=new byte[size]; //設立data[]的大小根據f.length inputStream.read(data); //讀取數據 inputStream.close(); //將讀取來的數據轉換成String String text=new String(data,"UTF-8"); System.out.println(text); }catch(Exception e) { e.printStackTrace(); } } 13.2數據的格式化存儲 整數,小數,布爾,字符串,日期.... 統一的解決方法:把各項數據轉換爲String 寫入 public static void main(String[] args) { Student stu=new Student(2018,"仁豪","1399009900"); String text=""; text+=("id:"+stu.id+","); text+=("姓名:"+stu.name+","); text+=("電話:"+stu.phone+","); //存儲到文件// File f=new File("e:/examples/student.txt"); f.getParentFile().mkdirs(); try { FileOutputStream outputStream=new FileOutputStream(f); byte[]data=text.getBytes("UTF-8"); outputStream.write(data); outputStream.close(); }catch(Exception e) { e.printStackTrace(); } System.out.println("exit"); } 讀取 //沒徹底理解 public class ReadFile { public static String readTextFile(File f) //讀取文檔 { try { FileInputStream inputStream = new FileInputStream(f); int size = (int) f.length(); byte[] data = new byte[size]; inputStream.read(data); inputStream.close(); // 將讀取來的數據轉成String String text = new String(data, "UTF-8"); return text; }catch(Exception e) { e.printStackTrace(); } return ""; } public static HashMap parseText (String text) //HashMap { HashMap<String,String> values = new HashMap(); String[] abc = text.split(","); for(String k : abc) { k = k.trim(); if(k.length() == 0) continue; String[] nv = k.split(":"); String name = nv[0]; String value = nv[1]; values.put(name, value); } return values; } public static void main(String[] args) { File dir = new File("e:/examples"); File f = new File(dir, "student.txt"); // 從文件讀出字符串 String text = readTextFile(f); // 將字符串解析爲 key-value HashMap<String,String> values = parseText(text); // 提取出Student信息 Student stu = new Student(); stu.id = Integer.valueOf( values.get("id")); stu.name = values.get("name"); stu.phone = values.get("phone"); System.out.println("exit"); } } 13.3 XML Extensible Markup Language 可擴展標記語言 (相似HTML), 一種用文本化表示的數據格式 特色:簡單,使用普遍. 一個Student對象的存儲 <?xml version="1.0" encoding="UTF-8"?> //示例1 <student> <id> 20180001</id> <name> 邵發 </name> <sex> true </sex> <cellphone> 13810012345</cellphone> </student> version 表示版本號 - 第一行是固定不變的,叫XML的聲明 - 後面內容主體是一個樹狀層次的元素節點樹。 -每一個元素成對出現都是閉合的,以<XXX>開始,以</XXX>結束. <?xml version="1.0" encoding="UTF-8"?> //示例2 <student> <id> 20180001</id> <name> 邵發 </name> <sex> true </sex> <cellphone> 13810012345</cellphone> <score> <chinese> 99 </chinese> <math> 98 </math> <english> 97 </english> </score> </student> <?xml version="1.0" encoding="UTF-8"?> //示例3 <data> <student> //同級元素可重名,能夠保存數組類型. <id> 20180001</id> <name> shao </name> <sex> true </sex> <cellphone> 13810012345</cellphone> </student> <student> <id> 20180002</id> <name> wang </name> <sex> true </sex> <cellphone> 13822244452</cellphone> </student> </data> 13.3.2 建立XML文件 1.用notepad++建立 直接建立xml後綴的文件打開,把編碼改成utf8格式編碼. 2在eclipse中建立XML 右鍵project new file 建立.xml 右鍵properties other UTF-8 XML教程3:dom4j 在dom4j中,定義瞭如下幾個術語: Document : 指整個XML文檔 Element : 指元素 Element Text : 指元素的值 Element Attribute : 元素的屬性 ( 後面介紹) 如何建立和添加元素 public class MyTest { public static void haha() throws Exception { // 建立一個空的Document Document x_doc = DocumentHelper.createDocument(); // 添加根元素: <root> Element x_root = x_doc.addElement( "root" ); // 添加兩個子元素: <server>, <port> Element e1 = x_root.addElement( "server" ) .addText( "www.afanihao.cn" ); Element e2 = x_root.addElement( "port" ) .addText( String.valueOf(80)); Element e3 = x_root.addElement("xxxx"); e3.addText("你好"); // 輸出到文件 File xmlFile = new File("output.xml"); OutputStream outputStream = new FileOutputStream(xmlFile); try { OutputFormat format = OutputFormat.createPrettyPrint(); format.setEncoding("UTF-8"); // 字符編碼爲UTF-8 XMLWriter writer = new XMLWriter(outputStream , format); writer.write( x_doc ); // 把xml文檔輸出到文件裏 writer.close(); }finally { // 確保文件句柄被關閉 try { outputStream.close();}catch(Exception e) {} } } public static void main(String[] args) { try { haha(); } catch (Exception e) { e.printStackTrace(); } System.out.println("hahha exit"); } } 13.3.4生成XML public static void writeXML( Student s, File xmlFile) throws Exception { // 建立一個空的Document Document x_doc = DocumentHelper.createDocument(); // 添加根元素: <root> Element x_root = x_doc.addElement( "root" ); // 添加兩個子元素: <server>, <port> x_root.addElement( "id" ) .addText(String.valueOf(s.id)); x_root.addElement( "name" ) .addText( s.name); x_root.addElement( "sex" ) .addText( s.sex ? "男" : "女"); // 男 male , 女 female 三目運算符b?x:y //先計算條件b,而後進行判斷。若是b的值爲true,計算x的值,運算結果爲x的值;不然,計算y的值,運算結果爲y的值。 x_root.addElement( "cellphone" ) .addText(s.cellphone); // 輸出到文件 OutputStream outputStream = new FileOutputStream(xmlFile); try { OutputFormat format = OutputFormat.createPrettyPrint(); format.setEncoding("UTF-8"); // 字符編碼爲UTF-8 XMLWriter writer = new XMLWriter(outputStream , format); writer.write( x_doc ); // 把xml文檔輸出到文件裏 writer.close(); }finally { // 確保文件句柄被關閉 try { outputStream.close(); } catch(Exception e) { } } } public static void main(String[] args) { try { Student s = new Student(20180001, "邵", true, "13810012345"); writeXML ( s , new File("output1.xml")); } catch (Exception e) { e.printStackTrace(); } System.out.println("exit"); } 添加數組元素 public static void writeXML( List<Student> sss, File xmlFile) throws Exception { // 建立一個空的Document Document x_doc = DocumentHelper.createDocument(); // 添加根元素: <root> Element x_root = x_doc.addElement( "root" ); Element x_student_list = x_root.addElement( "student_list" ); for( Student s:sss ) { Element x_student = x_student_list.addElement( "student" ); x_student.addElement( "id" ) .addText( String.valueOf(s.id)); x_student.addElement( "name" ) .addText( s.name); x_student.addElement( "sex" ) .addText( s.sex ? "male" : "female"); // 男 male , 女 female x_student.addElement( "cellphone" ) .addText(s.cellphone); } // 輸出到文件 OutputStream outputStream = new FileOutputStream(xmlFile); try { OutputFormat format = OutputFormat.createPrettyPrint(); format.setEncoding("UTF-8"); // 字符編碼爲UTF-8 XMLWriter writer = new XMLWriter(outputStream , format); writer.write( x_doc ); // 把xml文檔輸出到文件裏 writer.close(); }finally { // 確保文件句柄被關閉 try { outputStream.close();}catch(Exception e) {} } } public static void main(String[] args) { try { List<Student> sss = new ArrayList<>(); sss.add( new Student(20180001, "shao", true, "13810012345") ); sss.add( new Student(20180002, "wang", true, "17489938290") ); sss.add( new Student(20180003, "li", false, "19982998898") ); writeXML ( sss , new File("output3.xml")); } catch (Exception e) { e.printStackTrace(); } System.out.println("exit"); } 解析數據 public static Student readXML (File xmlFile) throws Exception { FileInputStream inputStream = new FileInputStream(xmlFile); SAXReader xmlReader = new SAXReader(); // SAXReader 用於解析XML Document x_doc = xmlReader.read(inputStream);// 獲得一個Document對象 inputStream.close(); // 獲得Document後便可關閉文件 Element x_root = x_doc.getRootElement(); Student s = new Student(); s.id = Integer.valueOf( x_root.element("id").getText()); s.name = x_root.element("name").getText().trim(); //.trim()的做用是清除兩邊的空格. String sex = x_root.elementText("sex"); s.sex = sex.equals("male"); s.cellphone = x_root.elementText("cellphone"); return s; } public static void main(String[] args) { File xmlFile = new File("data1.xml"); try { Student s = readXML (xmlFile); System.out.println("read complete"); } catch (Exception e) { e.printStackTrace(); } System.out.println("exit"); } (1)取值時要注意兩邊的空白 String name = x_root.elementText ("name").trim(); (2)因爲XML裏只存儲文本,因此在讀取Text以後,須要本身轉成int, boolean 或其餘類型的變量。 例如, int id = Integer.valueOf( x_root.element("id").getText()); (3)取值時通常要注意 null 的判斷 Element sex = x_root.element("sex"); if ( sex != null) { } (4)注意關閉文件句柄 (高級話題) 在Java程序裏,全部的文件FileInputStream/FileOutputStream在用完以後,都得確保close掉。不然會引發句柄泄露。 public static Document parseXmlDocument(File xmlFile) throws Exception { FileInputStream inputStream = new FileInputStream(xmlFile); try { SAXReader xmlReader = new SAXReader(); Document doc = xmlReader.read(inputStream); return doc; }finally { // 確保關閉文件句柄 try{ inputStream.close(); }catch(Exception e) {} } } 假設從<parent>元素下查找子元素<id>的值,則有3種寫法: 第一種寫法: Element e = parent.element("id"); String text = e.getText(); 第二種寫法: String text = parent.element("id").getText(); 第三種寫法: String text = parent.elementText("id"); XML教程6 元素的屬性 使用第三方庫dom4j來操做XML 先前咱們已經學到的,若是用XML來表示一個學生的信息,能夠表示爲: <student> <id> 20180001</id> <name> 邵發 </name> <sex> true </sex> <cellphone> 13810012345</cellphone> </student> 也能夠表示爲: <student id="20180001" > <name> 邵發 </name> <sex> true </sex> <cellphone> 13810012345</cellphone> </student> 其中,id做爲元素<student>的屬性出現。屬性的名字爲id,值爲"20180001",必須使用雙引號。 也能夠表示爲: <student id="20180001" name="邵發" sex="true" cellphone="13810012345"> </student> 其中,<student>元素有4個屬性。 因爲<student>元素沒有Text內容,因此能夠簡寫爲: <student id="20180001" name="邵發" sex="true" cellphone="13810012345" /> 添加元素 Element x_student = x_root.addElement("student"); x_student.addAttribute("id", String.valueOf(s.id)); x_student.addAttribute( "name" , s.name); x_student.addAttribute( "sex" , s.sex ? "male" : "female"); x_student.addAttribute( "cellphone", s.cellphone); 也能夠連在一塊兒寫, Element x_student = x_root.addElement("student") .addAttribute("id", String.valueOf(s.id)) .addAttribute( "name" , s.name) .addAttribute( "sex" , s.sex ? "male" : "female") .addAttribute( "cellphone", s.cellphone); 之因此能夠連在一塊兒寫,是由於addAttribute() 的返回值仍然是當前的Element對象。 讀取元素 public static Student readXML (File xmlFile) throws Exception { FileInputStream inputStream = new FileInputStream(xmlFile); SAXReader xmlReader = new SAXReader(); // SAXReader 用於解析XML Document x_doc = xmlReader.read(inputStream);// 獲得一個Document對象 inputStream.close(); // 獲得Document後便可關閉文件 Element x_root = x_doc.getRootElement(); Element x_student = x_root.element("student"); Student s = new Student(); s.id = Integer.valueOf( x_student.attributeValue("id").trim()); s.name = x_student.attributeValue("name").trim(); String sex = x_student.attributeValue("sex").trim(); s.sex = sex.equals("male"); s.cellphone = x_student.attributeValue("cellphone").trim(); return s; } public static void main(String[] args) { File xmlFile = new File("student.xml"); try { Student s = readXML (xmlFile); System.out.println("read complete"); } catch (Exception e) { e.printStackTrace(); } System.out.println("exit"); } .addAttribute輸入 .attributeValue讀取 經常使用工具類.AfXml 13.4 JSON XML多用於配置文件,JSON多用於數據傳輸 { "id":20111111, //示例 "name":"仁豪", "phone":"1300099"; } 使用JSON-ORG 或者jsonlib來操做JSON 其中,大括號表示一個JSON對象。在這個對象裏,包含4個字段。 例如, "id" 字段的值爲 20180001,是數字類型 ( Number ) "name" 字段的值爲 "邵", 是字符串類型 ( String ) "sex" 字段的值爲 true,是布爾類型 (Boolean) 能夠很直觀的發現, 若是值是一個String,則應該用雙引號,如"邵發" 若是值是一個Number,則不加雙引號,如20180001 若是值是一個Boolean,則應該是 true 或者是 false, 不加雙引號 加入JSON-java JSON-lib JSONObject jobj = new JSONObject(); jobj.put("id", 20180001); // Integer jobj.put("name", "邵發"); // String jobj.put("sex", true); // Boolean jobj.put("cellphone", "13810012345"); // String String jsonstr = jobj.toString(2); System.out.println(jsonstr); JSONObject裏的字段顯示順序是無關的,誰先上、誰在下不影響最終結果。 JSON的語法格式 用JSON能夠表示Object信息,以一對大括號包圍,裏面能夠多個字段。 例如: { "name": "邵發", "id": 1208439, "sex": true, "phone": "13810012345" } 語法要點: - 以大括號包圍 - 字段名稱要加雙引號 - 字段的值能夠爲String, Integer, Boolean, Array, null 等類型 - 每一個字段以逗號分隔 (最後一個字段末尾不能加逗號) - 字段的順序無關 注:JSONObject在概念上對應於Java裏的Map JSON Array 例如 [ { "name": "shao", "id": 2018001, "phone": "13810012345", "sex": true }, { "name": "wang", "id": 2018002, "phone": "13810043245", "sex": true }, { "name": "li", "id": 2018003, "phone": "1345015445", "sex": true } ] 語法要點: - 以中括號包圍 - 元素類型能夠是任意類型。例如,元素類型能夠是String,也能夠是Object 注:JSONObject在概念上對應於Java裏的數組 嵌套 Object的字段類型能夠是Array 例如 { "classId": 201801, "className": "計科1801", "members": [ "shaofa", "wang", "li" ] } 也就是說,Object 和 Array 是能夠互相嵌套的。 JSON的生成與解析 JSON的生成 public class CreateJSON { // 生成 JSONObject public static void test1() { JSONObject j1 = new JSONObject(); j1.put("name", "邵發"); j1.put("id", 1239349); j1.put("sex", true); j1.put("phone", "13810012345"); String jsonstr = j1.toString(2); // 縮進2 System.out.println(jsonstr); } // 生成JSONArray : 元素是字符串 public static void test2() { JSONArray j = new JSONArray(); j.put("shao"); j.put("wang"); j.put("li"); j.put("chen"); String jsonstr = j.toString(2); // 縮進2 System.out.println(jsonstr); } // POJO -> JSONObject // 若是是POJO對象 ( 已經添加了 getter ),則能夠直接轉成JSONObject public static void test3() { Student stu = new Student("邵發", 1238909, true, "13810012345"); JSONObject j = new JSONObject(stu); String jsonstr = j.toString(2); // 縮進2 System.out.println(jsonstr); } // List -> JSONArray public static void test4() { List<Student> sss = new ArrayList<Student>(); sss.add(new Student("shao", 1238901, true, "13810012345")); sss.add(new Student("wang", 1238902, true, "13456678895")); sss.add(new Student("qian", 1238903, false, "1381432435")); sss.add(new Student("chen", 1238904, true, "13342353446")); JSONArray jarray = new JSONArray(); for( Student s : sss) { JSONObject j1 = new JSONObject(s); jarray.put( j1 ); } String jsonstr = jarray.toString(2); // 縮進2 System.out.println(jsonstr); } // List -> JSONArray public static void test5() { List<Student> sss = new ArrayList(); sss.add(new Student("shao", 1238901, true, "13810012345")); sss.add(new Student("wang", 1238902, true, "13456678895")); sss.add(new Student("qian", 1238903, false, "1381432435")); sss.add(new Student("chen", 1238904, true, "13342353446")); // 直接把ArrayList轉成JSON,前提是ArrayList裏的元素是能夠轉化的 JSONArray jarray = new JSONArray(sss); String jsonstr = jarray.toString(2); // 縮進2 System.out.println(jsonstr); } JSON存儲到文件13.4_05網盤 json.org庫文件 13.5 Properties 另一種配置文件 #開頭是註釋 port=80 server=127.0.0.1 *.properties文件的格式; 1.#開頭是註釋行 2.key=value 顯然*.properties裏的key不容許重複. 不適合保存樹狀形式的數據 public class Config { public String server; public int port; public void load(File f) throws Exception { Properties props = new Properties(); // 從 *.properties 文件加載數據 InputStream inputStream = new FileInputStream(f); try { props.load( new FileInputStream(f)); }finally { inputStream.close(); } // 獲取 Property 配置 server = props.getProperty("server"); port = Integer.valueOf( props.getProperty("port", "80")); } public void save (File f) throws Exception { Properties props = new Properties(); props.put("server", server); props.put("port", String.valueOf(port)); // 存儲到 *.properties 文件 OutputStream outputStream = new FileOutputStream(f); try { props.store(outputStream, "just test"); }finally { outputStream.close(); } } } java.util.Properties Properties工具類,能夠存儲多個key-value -getProperties/-setProperties -load/store 讀取文件/寫入文件 第14章 反射機制 java.lang.Class 它是Java的基礎類,用於描述一個class對象. 在文件系統中,class以文件的形式存在Student.class在運行時的JVM(Java虛擬機)中, 該*.class文件被加載到內存中成爲一個對象,對象的類型是java.lang.Class Class cls = Student.class; System.out.println("Name: " + cls.getName()); 其中,cls這個對象就是這個Class的描述。 Student obj = new Student(); Class cls = obj.getClass(); 其中,obj是一個對象,obj.getClass()則是獲取它的Class 描述。 Class有什麼用 用於判斷一個運行時對象的類型 public static void test1(Object obj) { Class cls = Student.class; if(cls.isInstance(obj)) { // 判斷obj是否是Student類型 System.out.println("is an instance of Student"); } else { System.out.println("不是 an instance of Student"); } } 其中,cls.Instance(obj)意思是判斷obj是否爲my.Student 的一個實例。 另外一種寫法,也能夠判斷一個運行時對象的類型 public static void test2(Object obj) { String clsName = obj.getClass().getName(); if(clsName.equals("my.Student")) { System.out.println("is an instance of Student"); } else { System.out.println("不是 an instance of Student"); } } 比較S1和s2 Student s1 = new Student(123, "shaofa", "199900000"); Student s2 = new Student(124, "shaofa", "199900000"); if(s1.equals(s2)) { System.out.println("equal"); } else { System.out.println("not equal"); } public boolean equals(Object obj) { // 與一個Student對象比較 if(this.getClass().isInstance(obj)) { Student other = (Student) obj; return other.id == this.id; } // 與一個String對象比較 if(String.class.isInstance(obj)) { String other = (String)obj; return other.equals(this.name); } // 與一個Integer對象比較 if(Integer.class.isInstance(obj)) { Integer other = (Integer)obj; return this.id == other; } return false; } 14.2反射 Reflection 給定一個*.class文件中,咱們能夠獲得如下信息: 類名 (含package路徑) 函數 (名稱,參數類型,返回值) 域 (名稱,類型) 實現的接口 (interfaces) …… 使用java.lang.reflect.*下的類來實現。。。 注意:不須要源文件,只須要*.class文件 public static Class loadClass() throws Exception { // 加載my/Student.class Class cls = Class.forName("my.Student"); // 獲取函數列表 Method[] methods = cls.getMethods(); // 獲取成員變量列表 Field[] fields = cls.getFields(); for(Method m : methods) { System.out.println("函數: " + m.toString()); } return cls; } public static void main(String[] args) { try { // 獲得Class Class cls = loadClass(); //test2(); }catch(Exception e) { e.printStackTrace(); } } 也就是說,雖然咱們不知道Student類的代碼,可是這個 class文件自己能夠反映(reflect)出這些信息。。。 結論:經過Reflection機制,咱們能夠直接 從class文件反推出它有哪一個成員變量、有哪 些函數 遍歷Method 已經函數名,找到對象的 // 從Class中獲取Method : 按名稱查找 (假設沒有重名的函數) public static void findMethod(Class cls) throws Exception { String methodName = "setId"; // 獲取全部Method列表,順序比對 Method[] methods = cls.getMethods(); for(Method m : methods) //遍歷 { if(m.getName().equals(methodName)) //比較 { break; } } } 例2 查找Method 已經函數名,參數列表,尋找Method public static void findMethod2(Class cls) throws Exception { String methodName = "setId"; Class[] parameterTypes = { int.class }; Method m = cls.getMethod(methodName, parameterTypes); System.out.println("got the method"); } 例3 Reflection的運用 // 一個完整的reflection測試 public static void test1() throws Exception { // 加載my/Student.class Class cls = Class.forName("my.Student"); // 建立一個實例, 要求有一個不帶參數的構造函數 Object obj = cls.newInstance(); // 找到method Class[] parameterTypes = { int.class }; Method m1 = cls.getMethod("setId", parameterTypes); //"setId"參數是函數名,parameterTypes是函數值類型. // 調用method Object[] paramters = { 123 }; m1.invoke(obj, paramters); // 顯示結果 System.out.println("result: " + obj.toString()); } public static void main(String[] args) { try { // 獲得Class Class cls = loadClass(); test1(); }catch(Exception e) { e.printStackTrace(); } } Class.forName(…) 能夠加載一個class文件。。 (1)由誰負責加載? 由Class Loader負責加載,具體地講,JVM提供了一 個內置的 bootstrap class loader。 (2)從哪裏加載? 從classpath下尋找class,以及該class裏面import 的class 小結 1.Reflection: 從*.class文件中,能夠reflect獲得它 的描述信息:類名,函數名等 2. 經過Class,能夠建立一個實例instance 3. 經過Class,能夠找到它的某個Method,進而就 能夠調用這個函數。 4. 須要知道Class Loader的存在及其做用 反射機制 Reflection 應用框架 通常地,當設計一個應用框架時纔有可能用 到Reflection技術。 例如,著名的Struts Spring Hibernate框架 AfShell: Command Ui Framework AfShell: 一個自定義的Framework,使用這個 Framework能夠很方便的寫出基於命令行界面的應用 程序。 添加AfShell框架支持 (1)加入afshell.jar到BuildPath (2)添加afshell.properties (3)在main中調用 AfShell.start() 完成! AfShell: Command Ui Framework 添加命令處理 (1)自定義Action類 此類必須有一個函數public int execute() (2)配置afshell.properties login=my.LoginAction AfShell: Command Ui Framework 添加命令行參數的支持 例如,用戶輸入 login username=shaofa password=123456 (1)添加成員變量 username password (2)添加setter setUsername() setPassword() 則AfShell框架會自動的把參數值傳進來,而後再調用 execute()函數。。。 設計方法 那麼,這樣的一個Framework該如何實現? 例如,用戶輸入了 login username=shaofa password=123456 (1)獲得命令名 "login" (2)經過配置文件,找到my.LoginAction (3)加載my.LoginAction,建立一個實例 (4)根據username=shaofa,找到setter函數setUsername, 調用此函數 (5)根據password=123456,找到setter函數並調用 (6)調用excute()函數,獲得返回值 完成! 小結 介紹一個應用框架AfShell的使用方法 若是要使用這個框架,並不須要知道Reflection技術 若是本身設計一個框架給別人使用,則須要精確掌 握和運用Reflection技術。 請試着使用Reflection技術,本身來實現這個框架。 反射4 (1)獲得命令名 "login" (2)經過配置文件,找到my.LoginAction (3)加載my.LoginAction,建立一個實例 (4)根據username=shaofa,找到setter函數setUsername, 調用此函數 (5)根據password=123456,找到setter函數並調用 (6)調用excute()函數,獲得返回值 完成! 瞭解AfShell框架的架構方法. 15.1線程 :你手下管理着兩名員工,一個是Confucian,一 個是Buddhist,如何讓他們同時工做? Confucian e1 = new Confucian(); e1.doJob(); Buddhist e2 = new Buddhist(); e2.doJob(); 咱們發現,只有e1先工做完成了,才能開始e2的工做, 二者沒法「同時」進行。。。 線程 Thread 引入線程機制,能夠並行地作多件事情。。。 繼承於Thread類,重寫run(),填上它要作的工做。。 extends Thread public class Buddhist extends Thread { @Override public void run() { for(int i=1; i<=500; i++) { System.out.println("ma mi ma mi hong ..." + i); try { Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } public static void main(String[] args) { Confucian e1 = new Confucian(); e1.start(); } 線程 Thread 啓動線程 Confucian e1 = new Confucian(); e1.start(); Buddhist e2 = new Buddhist(); e2.start(); 啓動一個線程 start() 線程主函數 run() : 描述了這個線程的工做任務 線程的調度 CPU只有一個,而系統中同時運行的進程有幾十 個。每一個進程又同時運行n個線程。忙得過來? 時間片劃分:將1秒鐘劃分爲N個小的時間片,你們 輪流運行。 好比,以5ms爲一個時間片,每一個線程運行5ms以後 就輪到下一個線程運行。 這樣,全部的線程都有機會被運行,因爲切換的速 度很快,給用戶的感受是:它們好像同時在運行. Sleep暫歇 線程能夠執行Sleep操做,用於告訴操做系統: 我要 休息一會,接下來的一段時間內不要管我。。。 例如, Thread.sleep(10000); 表示接下來將休息10秒鐘,在這10秒鐘內該線程主 動放棄CPU的使用權。。。 排隊機制 假設線程①休息了10秒以後,那麼醒來以後就能立 即得到CPU的使用權嗎?不能。醒來以後要排隊! 系統中有一個隊列,全部須要CPU的線程在這裏排隊。 操做系統根據特定的算法,來決定下一個是誰。 (比始說,有的線程優先級較高,而有的較低) 使用sleep 當建立線程時,sleep是一個常常要使用的函數 原則:儘量少的佔用CPU,讓別的線程也有機制運行。 試想,若是一個線程裏是這樣的: while(true) { System.out.println("我就賴着CPU。。。"); } 那其餘的線程會高興嗎? 雖然咱們看到在任務管理器下不少線程,但在同一時刻,大多數線程都在sleep 引入線程的概念 (多條線並行發展) 介紹了線程調度的機制,由操做系統負責調度 介紹了sleep的做用,及其重要意義 15.2 線程的建立 第一種方法繼承Thread extends Thread 重寫run方法,在主函數入口用start()啓動. 第二章方法:實現Runnable接口 public class Buddhist implements Runnable { @Override public void run() { for(int i=1;i<=500;i++) { System.out.println("emmmmmmmm."); try { Thread.sleep(100); }catch(InterruptedException e) { e.printStackTrace(); } } } } public static void main(String[] args) { Buddhist e2 = new Buddhist(); Thread t=new Thread(e2); t.start(); } 這是由於java不容許多重繼承,若是你的類已經繼承於別的類,又要用線程來運行,則使用這種方法. 有時候使用匿名類更方便一些 Thread t=new Thread(){ public void run() { } }; t.start(); public static void main(String[] args) { Thread t2=new Thread() { @Override public void run() { for(int i=1;i<=500;i++) { System.out.println("emmmmmmmm."); try { Thread.sleep(100); }catch(InterruptedException e) { e.printStackTrace(); } } } }; t2.start(); } 15.3線程 線程的終止 當一個線程的start()調用後,線程爲Alive狀態。 當一個線程的主函數run()退出後,線程死亡(Dead)。 public void run() { for(int i=1; i<=10; i++) { System.out.println("..." + i); } } 該線程打印10句後終止。 (能夠類比一下主線程,當main()中退出時,主程序終止。。。) 讓一個線程終止,就是要想辦法讓它從run()中退出。 例如,設置一個標識變量, public boolean quitflag = false; public void run() { for(int i=1; i<=10; i++) { if(quitflag) break; // 退出循環 System.out.println("..." + i); } } 例子 public class Confucian extends Thread { public boolean quitflag = false; @Override public void run() { for(int i=1; i<=10; i++) { if(quitflag) break; System.out.println("人之初,性本善 ..." + i); try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } System.out.println("Confucian exit."); } } public class Test1 { public static void main(String[] args) { // 建立並啓動線程 Confucian t1 = new Confucian(); t1.start(); // 按回車後,停止線程,而後退出程序 InputStreamReader m = new InputStreamReader(System.in); BufferedReader reader = new BufferedReader(m); try{ reader.readLine(); //由這句來讀回車 reader.close(); // 控制線程的退出 t1.quitflag = true; } catch(Exception e) { } } } sleep與interrupt 背景:Buddhist中每5秒輸出一次,sleep的時間很長,如何讓它當即退出?? 方法: 調用 t2.interrupt()來中斷目標線程。打斷線程的sleep 則sleep()函數將拋出異常,結束sleep狀態 public static void main(String[] args) { // 建立並啓動線程 Buddhist t2 = new Buddhist(); t2.start(); // 按回車後,停止線程,而後退出程序 InputStreamReader m = new InputStreamReader(System.in); BufferedReader reader = new BufferedReader(m); try{ reader.readLine(); reader.close(); // 控制線程的退出 t2.quitflag = true; t2.interrupt(); //打斷sleep } catch(Exception e) { } 等待線程的退出 Buddhist t3 = new Buddhist(); t3.start(); // 等待t4退出 t3.join(); join() 函數會阻塞住當前線程,等待目標線程退出後, 再繼續執行。。。 注:在Java裏, join()只起等待做用(線程死亡後不需 要手工回收,由JVM自動回收) public static void main(String[] args) { // 建立並啓動線程 Buddhist t3 = new Buddhist(); t3.start(); // 等待t3退出 try { t3.join(); //阻塞住當前線程,等待目標線程退出後,再繼續執行. } catch (InterruptedException e) { e.printStackTrace(); } // 在t3線程完成以後,再執行下面的事情 for(int i=0; i<3; i++) { System.out.println("我本身的事情"); } System.out.println("program exit"); } 注意事項 本質上,線程只有一種退出方式:從run()函數天然退出。 咱們須要經過程序設計,控制它從run()中退出。 不能 stop() ,不能destroy()… deprecated 不少初學者會有一個想法:有沒有簡單暴力的手段, 直接殺死一個線程? 好比kill() ? 千萬不要這麼想! (正在使用ATM存錢的時候,斷電了能夠嗎?) 小結 線程只有一種終止方式:從run()中退出。 介紹如何控制一個線程的退出。 15.4 引例 密鑰Key :一個16字節的數組 密鑰更新線程KeyUpdater:按期更新密鑰 密鑰獲取線程KeyPrinter:打印顯示密鑰 密鑰的完整性:密鑰的16字節必須相同。 例如, 下面是有效密鑰 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 下面是無效密鑰 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1B 1B 1B 密鑰更新時應保證密鑰的完整性。 什麼樣的狀況算是出錯?KeyPrinter獲取密鑰,發現密鑰 不完整(16個字節不全相同),則認爲此密鑰無效! 分析代碼,推斷有沒有可能出錯。。。 運行代碼,看看沒有出錯。。。 構造出錯條件:假設 key.update()須要10ms 才能更新完成。。。 線程的同步機制 兩個線程同時訪問一個對象時,可能發生數據不一樣步的現象。 數據不一樣步: 好比,線程KeyUpdater正在更新密鑰。。。 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A xx xx xx xx 當更新過程完成了75%的時候。。。 另外一個線程KeyPrinter要求獲取當前密鑰。。。 它拿走了密 鑰,發現它是一個不完整的密鑰。。。 出錯 爲了實現多線程對同一對象的同步訪問,引入互斥鎖的 概念。 synchronized (lock) // 申請鎖 { // 上鎖 locked … 關鍵代碼 … } // 解鎖 unlocked (1)若別的線程正持有該鎖(locked),則本線程阻塞等待 (2)若此鎖空閒(unlocked),則本線程持有鎖(locked), 進入大括號執行,完畢以後釋放鎖(unlocked)。 synchronized (key) { seed ++; if(seed > 100) seed = 0; key.update(seed); } 形象理解: 比如一間房子,若是是空的,就能進入使用裏面的資源, 並在裏面把門反鎖,此時別人沒法進入。 (保證了資源的獨 佔使用)。此時有人想用房子,就得門口等着。。。 synchronized ( lock ) // 等待進入 { // 進入,從裏面反鎖 … 使用資源res … } // 出來 synchronized ( lock ) 在Java裏, synchronized (lock) 括號中的鎖能夠是任意 對象,甚至是數組對象。 好比, Object obj = new Object(); synchronized (obj) { } byte[] data = new byte[]; synchronized (data) { } 爲了加強可讀性,能夠直接把要訪問的那個對象當成鎖, 相似下面這樣: synchronized (key) { seed ++; if(seed > 100) seed = 0; key.update(seed); } 則能夠一目瞭然地代表:大括號中要鎖定的資源是key 對象。 介紹了線程同步的概念:多個線程訪問同一對象時, 一個讀,一個寫,則容易發生不一樣步的現象。 線程同步:就是讓兩個線程步調一致,有序地訪問 的訪問同一對象。 synchronized代碼塊用於同步訪問。 寫在內部此時,外部就能夠放心地調用update和get,沒必要擔憂不一樣步的問題。 public void update(byte seed) { synchronized (this) { for(int i=0; i<data.length; i++) { data[i] = seed; } } } // 獲取密鑰 public synchronized byte[] get() { synchronized (this) { // 複製一份傳出去 byte[] copy = new byte[data.length]; System.arraycopy(data, 0, copy, 0, data.length); return copy; } } 因爲要鎖定的資源對象就是本身,因此也可使用 synchronized (this) { } 進一步簡化的寫法 public synchronized void update() { } 至關於 public void update() { synchronized (this) { } } 若是函數內的全部語句都被synchronized括起來,則能夠這麼簡寫。 死鎖 deadlock 死鎖:由於不恰當地使用鎖,致使線程鎖死。 第一種狀況: synchronized (lock) { synchronized (lock) { } } 本身就在房子裏,另外一個本身在房子外面敲門 死鎖 deadlock 第二種狀況:兩個線程互相等待。。。 線程① synchronized (lock1) { synchronized (lock2) { } } 線程② synchronized (lock2) { synchronized (lock1) { } } 大括內的代碼儘量快地完成,以提升多線程的運行效率。 (讓別人少等一會) synchronized (lock) { } 建議:把不影響共享資源訪問的耗時操做都移出去 引例 雞蛋Egg 籃子ArrayList<Egg> 母雞Hen : 每隔一段時間產出一個雞蛋,放入籃子 男孩Boy : 負責將籃子裏的雞蛋取出來吃掉 要求:確保籃子裏的雞蛋被及時取走。 (若是取走不及時,雞蛋就會堆積在籃子裏。。。) 生產者 – 消費者 模型 因爲Boy不肯定母雞何時會產出雞蛋,只好頻繁的 去檢查籃子裏有沒有雞蛋。有則取出,沒有則空跑一趟。 輪詢機制 假設母雞每5-10秒會隨機產出一個雞蛋 則小明爲了確保能及時取走雞蛋,能夠每隔1秒去檢查 一次。。。這樣, 雖然會空跑不少次,但可以完成目標。 這種實現機制稱爲:輪詢機制。(反覆查詢) 特色:效率低,但實現起來簡單。 通知機制 爲了提高小明的效率,不讓小明空跑,則可使用通知機制。 小明等待通知 basket.wait … 母雞在產出雞蛋後,當即通知小明來取 basket.notify … 這樣,小明就可以在第一時間取走雞蛋。 這種實現機制稱爲:通知機制 特色:效率高,但實現起來複雜 wait / notify wait/notify 使用注意事項: (1)必須結合synchronized使用 (2)synchronized對象和wait/notify的對象必須是同一 個 這是Java特定的機制,必須這麼使用(官方說法是,在 wait/notify以前,必須先成爲該對象的監視者Monitor) 固然,也可使用別的方法來實現通知機制。但這套機 制是Java自帶的。 notify / notifyAll notify : 通知一個線程 若是有不少線程都在wait,則由系統選擇一個 notifyAll : 通知全部線程 wait / notify wait / notify 是Object類的方法 發出通知 synchronized (basket) { basket.add(egg); basket.notify(); } 等待通知 synchronized (basket) { try { basket.wait(); } catch (InterruptedException e) { } if(basket.size() > 0) egg = basket.remove(0); } 實例代碼 public class Test2 { // 雞蛋 class Egg { int id; // 每一個雞蛋有個編號 public Egg(int id) { this.id = id; } public String toString() { return "Egg("+id+")"; } } // 母雞 (生產者) class Hen extends Thread { ArrayList<Egg> basket ; public Hen(ArrayList<Egg> basket) { this.basket = basket; } public void run() { Random r = new Random(); int count = 0; while(true) { // 生產一個蛋,放在籃子裏 Egg egg = new Egg(++count); System.out.println("產出: " + egg); synchronized (basket) { basket.add(egg); basket.notify(); //通知,notify和wait的對象必須是統一的對象. } // 休息一段時間: 1~5秒 (隨機) int interval = 1 + r.nextInt(4); try { Thread.sleep(interval * 1000); } catch (InterruptedException e1) { } } } } // 小孩 (消費者) class Boy extends Thread { ArrayList<Egg> basket ; public Boy(ArrayList<Egg> basket) { this.basket = basket; } public void run() { while(true) { // 查看籃子裏有沒有雞蛋 Egg egg = null; synchronized (basket) { try { basket.wait(); //等待通知,阻塞線程.notify和wait的對象必須是統一的對象. } catch (InterruptedException e) { } if(basket.size() > 0) { // 從籃子裏取出雞蛋 egg = basket.remove(0); } } if(egg != null) { // 吃掉這個雞蛋 System.out.println(" 吃掉 :" + egg); } } } } public void test() { ArrayList<Egg> basket = new ArrayList<Egg>(); Hen xiaoji = new Hen(basket); Boy xiaoming = new Boy(basket); xiaoji.start(); xiaoming.start(); } public static void main(String[] args) { Test2 t = new Test2(); t.test(); } } 註解Annotation @開頭的都是註解 @Deprecated 意思是「廢棄的,過期的」 @Override 意思是「重寫、覆蓋」 @SuppressWarnings 意思是「壓縮警告」 註解和註釋的區別 //Comment是給程序員看的,不會被編譯到class文件 註解@是給編譯器和IDE看的