在最後一節課的末尾,吳老師帶着咱們作了一個簡單的回顧。對這兩週課程總體進行了總結。java
1、課程核心要義——Java面向對象編程的四個基本特徵正則表達式
1.封裝算法
主要是把過程和數據包圍起來,不對外部公開內部的數據和邏輯,從而保護內部的數據結構不被外界改變,起到保護做用!編程
主要經過對數據進行類型定義private,public,protected來對數據的類型進行區分,讓各類類型的數據的使用範圍有不一樣之處。緩存
訪問位置 | private | protected | public |
本類內部 | 可見 | 可見 | 可見 |
同包的其餘類或者子類 | 不可見 | 可見 | 可見 |
其餘包 | 不可見 | 不可見 | 可見 |
PS:在之後的程序之中全部類之中的成員變量都聲明爲private,而後在類裏面添加多個get方法(返回值爲想要查看的成員變量)以防用戶對成員變量私自進行修改。網絡
舉例以下(以第四次做業中的二元組<String,Integer>組成的類pair爲例)數據結構
public class Pair { private String word; private int times; public Pair(String str,int num){ word=str; times=num; } public String getWord(){ return word; } public int getTimes(){//將time封裝起來 return times; } }
2.抽象框架
抽象就是找出一些事物的類似和共性之處,而後將這些事物歸爲一個類,這個類只考慮這些事物的類似和共性之處,而且會忽略與當前主題和目標無關的那些方面,將注意力集中在與當前目標有關的方面。例如,看到一隻螞蟻和大象,你可以想象出它們的相同之處,那就是抽象。抽象包括行爲抽象和狀態抽象兩個方面。例如,定義一個Person類,以下:編程語言
class Person{ String name; int age; }
忽略主題所有不打算把所有事件描述下來,只是抽取主要部分抽象化描述,能夠理解抽象是一個接口類或一個抽象類!好比:描述一個抽象功能,用接口表示,只要添加、刪除、修改等方法功能!(抽象類和接口類是Java抽象的一個機制)!函數
抽象類(abstract class): 1.能夠實現繼承父類 2.能夠擁有私有的方法或私有的變量, 3.只能單獨繼續一個類!
接口類(interface): 1.不能夠實現繼承 2.不能夠擁有私有的方法或私有的變量 3.一個接口類能夠實現多重繼承(好比A類接口實現B\C\類,那麼B\C\繼承是另外一個類)!
接口是爲了彌補Java單繼承問題。
接口使用的例子(以第二次上課的Box爲例):
//接口的定義 public interface Geometry { public double getVolume(); public String toString(); }
//類加到接口上時要用implements關鍵字 public class Box implements Geometry{ private double width; private double length; private double height; private double scale; private double vol; public Box(double w, double l, double h,double s){ width = w; length = l; height = h; scale = s; vol = volume(); } private double volume() { // TODO Auto-generated method stub return width*scale*height*scale*length*scale; } public double getVolume(){ return vol; } public String toString(){ return "Volume of Box:"+vol; } }
使用的方法:(直接就抽象起來了,再也不區分時box仍是cylinder)
Vector<Geometry> list; for(int i=0;i<list.size();i++){ res+=list.get(i).getVolume(); }
三、繼承
(1)在定義和實現一個類的時候,能夠在一個已經存在的類的基礎之上來進行,把這個已經存在的類所定義的內容做爲本身的內容,並能夠加入若干新的內容,或修改原來的方法使之更適合特殊的須要,這就是繼承。繼承是子類自動共享父類數據和方法的機制,這是類之間的一種關係,提升了軟件的可重用性和可擴展性。
(2)經過 extends 關鍵字讓類與類之間產生繼承關係。
class SubDemo extends Demo{
} //SubDemo是子類,Demo是父類
只支持單類繼承,不支持多個類繼承!!!!
//一個類只能有一個父類,不能夠有多個父類。 class SubDemo extends Demo{} //ok class SubDemo extends Demo1,Demo2...//error
Java支持多層(重)繼承(繼承體系)。
1
2
3
|
class
A{}
class
B
extends
A{}
class
C
extends
B{}
|
(4)super的使用
super是一個關鍵字,表明父類的存儲空間標識。(能夠理解爲父親的引用)
super和this的用法類似。
this表明對象的引用(誰調用就表明誰);
super表明當前子類對父類的引用。
使用場景
(5)方法的重寫與覆蓋(以課上的CharSet爲例)
public class NewCharSet extends CharSet{ int service=0; public NewCharSet(char[] charSet){//構造方法的重寫,下面不須要寫super.CharSet super(charSet); } public void insert(int index, char alpha){//其他方法的重寫都是super.方法名 this.service++; super.myInsert(index, alpha); } }
4.多態(沒有重點講,如下資料來源於網絡)
不一樣類的對象對同一個類的對象作出不一樣的響應信息!(Java提出多態性是對Java單繼承的一個補充)。
多態是指程序中定義的引用變量所指向的具體類型和經過該引用變量發出的方法調用在編程時並不肯定,而是在程序運行期間才肯定,即一個引用變量倒底會指向哪一個類的實例對象,該引用變量發出的方法調用究竟是哪一個類中實現的方法,必須在由程序運行期間才能決定。由於在程序運行時才肯定具體的類,這樣,不用修改源程序代碼,就可讓引用變量綁定到各類不一樣的類實現上,從而致使該引用調用的具體方法隨之改變,即不修改程序代碼就能夠改變程序運行時所綁定的具體代碼,讓程序能夠選擇多個運行狀態,這就是多態性。多態性加強了軟件的靈活性和擴展性。例如,下面代碼中的UserDao是一個接口,它定義引用變量userDao指向的實例對象由daofactory.getDao()在執行的時候返回,有時候指向的是UserJdbcDao這個實現,有時候指向的是UserHibernateDao這個實現,這樣,不用修改源代碼,就能夠改變userDao指向的具體類實現,從而致使userDao.insertUser()方法調用的具體代碼也隨之改變,即有時候調用的是UserJdbcDao的insertUser方法,有時候調用的是UserHibernateDao的insertUser方法:
UserDao userDao=daofactory.getDao();
userDao.insertUser(user);
比喻:人吃飯,你看到的是左手,仍是右手?
2、有關hashmap和String類(正則表達式)
在這兩週的學習之中,給我留下在深入印象的就是String類和hashmap這兩個結構和類庫了。
正則表達式在處理字符串的時候是一大神器!!
有關這三個知識點的總結參見我寫的前幾篇博客。
3、以第3、四次做業爲例來分析優化程序的過程
1.第三次做業(詞頻統計)
A.剛開始看到這個題目的的時候,因爲剛好接觸了hashmap,於是,就想到直接使用hashmap來實現這個功能。具體作法是將字符串做爲key鍵值,將該單詞在文章之中出現的全部位置都存到一個ArrayList之中,再將該list做爲value值存儲到hashmap之中。經過使用split函數將文章之中的全部單詞都提取出來,而後一個個對hashmap裏的值進行更新。
import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.regex.Matcher; import java.util.regex.Pattern; public class TokenManager { HashMap<String,ArrayList<Location>> dictionary; public class Location{ public int row; public int column; public Location(int a,int b){ row=a; column=b; } } public TokenManager(String filename)throws IOException { dictionary=new HashMap<String,ArrayList<Location>> (); BufferedReader reader=new BufferedReader(new FileReader(filename)); String str; int row=-1; while((str = reader.readLine()) != null){ row++; String[] strSet=str.split("\\W+|_+"); //找到使用split方法後獲得的逐個單詞的位置 for(int i=0,index=-1;i<strSet.length;i++){ if(!strSet[i].equals("")){ index=str.indexOf(strSet[i], index); if(!dictionary.containsKey(strSet[i])){ ArrayList<Location> list=new ArrayList<Location>(); dictionary.put(strSet[i], list); } ArrayList<Location> list=dictionary.get(strSet[i]); Location pair=new Location(row,index); list.add(pair); dictionary.put(strSet[i], list); index+=strSet[i].length(); } } } reader.close(); } public void output(String filename)throws IOException{ Object[] key_list=dictionary.keySet().toArray(); ArrayList<String> word_list=new ArrayList<String>(); for(int i=0;i<key_list.length;i++){ word_list.add((String)key_list[i]); } Collections.sort(word_list); String out=""; File fileout=new File(filename); FileWriter writer=new FileWriter(fileout,true); for(int i=0;i<word_list.size();i++){ String word=word_list.get(i); ArrayList<Location> list=dictionary.get(word); int repeat=list.size(); out=word+": "+repeat+" : {"; writer.write(out); for(int j=0;j<repeat-1;j++){ Location loca=list.get(j); out="("+loca.row+"," +loca.column+"),"; writer.write(out); } out="("+list.get(repeat-1).row+"," +list.get(repeat-1).column+")}\r\n"; writer.write(out); } writer.close(); } }
這是最終優化結果以後的代碼,最後總用時是2.5s左右。
在第三次做業之中發現較大的優化之處就是有關文件的讀入以及最後向文件之中寫入大量的數據所花費的時間。
(1)文件的讀入方式(儘可能使用BufferedReader)
最開始的時候進行的讀入嘗試,是使用相似C語言之中的getchar()函數,字符一個一個進行讀入,可是最後的結果倒是十分不理想,運行時間接近於200s左右,在查閱資料以後嘗試更改爲了使用BufferedReader來讀入,速度與效率有了極大的提升。
基本的BufferedReader的用法以下圖所示:
BufferedReader reader=new BufferedReader(new FileReader(filename));
PS:BufferedReader在使用過程之中想比較filereader有了較大的功能進步,好比filereader之中是不支持readLine()這個函數的(不能夠逐行讀入進行處理),可是BufferedReader之中可使用readerLine這個方法實現逐行讀入處理。
(2)向文件之中寫入大量的數據
教訓:
!!!flush不要胡亂用。(注意,直接寫writer.write()你會發現有些時候是寫不進去的,由於可能一直在緩存區之中,當使用flush時能夠將緩存區的內容寫進去,或者在關閉文件的時候回將先前寫入緩存區的內容一次性寫入目標文件之中)。
!!!向文件之中寫數據的時候不要用把全部的數據用字符串拼接所有鏈接到一塊兒再一次性所有輸出,一來拼接過程要建立新的對象並且要屢次遍歷於是效率下降明顯,二來將過量的數據寫到文件之中也會致使輸出效率驟然變慢。
上述最終改好的程序是每次調用函數打開文件而後在函數的末尾將文件關閉,在最開始的時候 ,關鍵的輸出語句是採用
writer.flush();
可是相同的算法運行時間幾乎是100倍左右,所以能夠清晰地看到,在向指定文件之中寫入大量數據時,必定不要過多使用flush語句。
B.在採用上面的作法以後,我對所用的結構進行了分析,通過理論分析能夠知道該算法的時間複雜度爲O(nlogn),主要的複雜度都集中在排序那裏。後來想到數據結構中曾經用到過的字典樹這一數據結構,這種數據結構的理論時間複雜度爲O(kn),k爲字符集之中所含的字符總數,應該來講是理論最優算法,因而開始就按照字典樹的算法理論寫了一份新的代碼。
package homework3; import java.io.BufferedOutputStream; import java.io.BufferedWriter; import java.io.File; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.util.ArrayList; public class TokenManager{ public void close() throws IOException{ writer.close(); } FileWriter writer; int time; public ArrayList<TokenString> wordlist; public Node root; public TokenManager() throws IOException{ time=0; root=new Node(0); wordlist=new ArrayList<TokenString>(); writer=new FileWriter("c:\\Users\\mly\\Desktop\\output.txt",true); } public class Node{// public char alpha; public int end_flag; public Node[] next; public ArrayList<Integer> location; public Node(int k){ alpha=(char)k; end_flag=0; location=new ArrayList<Integer>(); next=new Node[128];//題目中含數字大寫字母因此要求更多 for(int i=0;i<next.length;i++){ next[i]=null; } } } public void insert(String word,int index){ Node p=root; for(int i=0;i<word.length();i++){ if(p.next[(int)word.charAt(i)]==null){ p.next[(int)word.charAt(i)]=new Node((int)word.charAt(i)); } p=p.next[(int)word.charAt(i)]; if(i==word.length()-1){ p.location.add(index); if(p.end_flag==0){ p.end_flag=1; } } } } public void dfs(Node p,String str) throws IOException{//使用遞歸的方法來實現字典樹的的遍歷 if(p!=null){ if(p.end_flag==1){ //System.out.println(str.substring(1)+p.alpha+": "+p.location.size()); //System.out.println(p.location); output(str.substring(1)+p.alpha+": "+p.location.size()+p.location); TokenString wordNode=new TokenString(str.substring(1)+p.alpha,p.location); wordlist.add(wordNode); } for(int i=0;i<root.next.length;i++){ str+=p.alpha; dfs(p.next[i],str); str=str.substring(0, str.length()-1); } } } public void output(String str)throws IOException{ long startTime=System.currentTimeMillis(); this.writer.write(str+"\r\n"); long endTime1=System.currentTimeMillis(); time+=endTime1-startTime; } }
最終的程序運行結果以下圖所示:
咱們能夠看到雖然這種算法理論上速度會快不少,可是實際上因爲種種因素致使執行起來並無預想之中那麼高效。
2.從第四次做業(短語詞頻分析來看理論最優與實際時間的關係問題)
在上面第三次做業優化分析的過程之中我看到了理論最優的算法在寫出來以後的時間效率未必是最優的。在第四次做業之中,要求輸出頻率最高的五個短語。在之前接觸過TOP K問題的 算法,我以爲若是把全部的數據均進行排序會下降程序的效率,因而把程序按照hashmap的value值排序(使用的是系統自帶的sort函數)改爲了相似使用top k算法(k=5,就沒有寫堆,就簡單地依次遍歷)
ArrayList<Integer> numListTop5=new ArrayList<Integer>(); ArrayList<String> wordListTop5=new ArrayList<String>(); Iterator iter = temp.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = (Map.Entry) iter.next(); String key = (String)entry.getKey(); int val = (int)entry.getValue(); sum+=val; int min=val,minIndex=-1; if(numListTop5.size()<5 ){ numListTop5.add(val); wordListTop5.add(key); } else{ for(int i=0;i<5;i++){ if(min>numListTop5.get(i)){ min=numListTop5.get(i); minIndex=i; } } if(minIndex!=-1){ numListTop5.set(minIndex,val); wordListTop5.set(minIndex,key); } } } int len=numListTop5.size(); if(len<5){ System.out.println("Only "+len+" phrase formed by "+word); for(int i=0;i<len;i++){ System.out.println(word+" "+wordListTop5.get(i)+numListTop5.get(i)); } } else{ for(int i=0;i<5;i++){ System.out.println(word+" "+wordListTop5.get(i)+numListTop5.get(i)); } }
採用的從頭至尾依次遍歷的方法,可是最後的優化結果比較差,沒有減小時間,反而使運行時間愈來愈長。
3.總結如何提升程序運行效率
在第四次課堂上,吳老師就經過一個slowJava的例子向咱們展現瞭如何比較方便快捷的經過優化部分模塊來提升程序運行時間。
首先,最重要的一部是要將已寫好的程序進行分塊,而後再每塊都設置時間顯示器,在程序最後打印出來各個模塊所用的時間大小。(只有先了解程序各部分運行狀況,才能對症下藥,優化到最大程度)
其次,找到佔用程序運行大時間的代碼部分,想辦法從中進行優化和改進 。
在改進的過程當中,第一考慮在算法層面上的時間複雜度,第二重點考慮輸入輸出部分,第三考慮若是實現同一個目的有不一樣的方法,將不一樣的方法都實現一次記錄一下時間,找到最優的程序組合。
在編程過程儘可能考慮使用Java類庫裏面的方法,理論最優的算法實際跑出來的的效果可能與理想之中的時間有所誤差。
4、課程感想以及建議
在選這門課的時候,是有些猶豫的,由於感受在這裏由於要上四天的課要多待2周。等到課程開始的時候才發現,四天的課程是不足以讓咱們把Java學會學透,那空餘的時間是用來作做業以及本身查閱資料用的。這門課上吳老師的講解已經再也不像咱們C語言那樣,花大量的時間在語法的講解上,而是將側重點放在了利用這有限的時間花更多的精力來讓咱們去深刻理解面向對象的核心思想。老師給咱們引導出了一種全新的編程語言學習模式,這顛覆了咱們在學習c語言過程之中老師花大量的時間來教授語法知識。就是在遇到一門全新的語言時,先基本瞭解最基礎的語法框架,而後在具體任務(實例)之中不斷查詢本身所須要的語法知識,這種學習方式無疑比直接機械地講解語法更加高效,掌握語言的也會更加熟練。
這門課除了教會我一門新的編程語言,還教會我了一個道理——在實際軟件編寫過程之中必定要考慮到異常的處理畢竟之後軟件的客戶頗有可能會提供一些非法輸入,這些時候就要考慮程序不能老是直接退掉,而是想辦法處理,或着給出體提示信息。(畢竟之後的工做之中不會全部的輸入數據都會像C語言做業那樣有嚴格的範圍以及保證絕對的合法性與正確性)。
建議:在這門課之中老師的課程安排清晰合理,惟一感受難受的地方就是做業,可能大部分緣由仍是出在本身身上,多是本學期C語言的做業有嚴格的測評系統來規範,因此在作Java做業的時候常常會對說明文檔的意思讀不懂,題意常常弄不是很懂,並且做業要求之中沒有很嚴格限定輸入輸出形式,在作做業的時候常常陷入慣性思惟在輸入輸出形式之上糾結許久。我認爲老師能夠在之後開這門課的時候能夠考慮嘗試一下搭建一個簡易的Oj平臺測評。