lidwup

          2009年7月1日 #

          用戶是一個絕頂聰明的傻子(轉,向原創致敬)

           
          從用戶能夠感受得到的角度進行分析(做用戶體驗的原則:堅決做用戶感受得到,絕不做用戶感受不到的——控制成本的有效辦法),把用戶體驗劃分為四步(用戶體驗四步曲):

          第一步視覺體驗。

          視覺體驗是用戶對界面設計的第一感官體驗。它是完全是用戶幾乎沒有經過思考前的一種視覺感受,如何給用戶提供一種近于完美的視覺體驗,需要考慮到用戶看到界面時可能出現的任何一種感受,這就提出UI設計師需要對目標用戶偏愛的準確了解和把握,精確傳達需要傳達的含義,才會獲得預期的效果;視覺體驗直接影響到用戶是否停留在此頁面進行一下步的表達理解。注意:任何一種讓用戶感官視覺不適的界面都可能導致用戶產生怨言和流失。

          第二步表達體驗。

          表達體驗是用戶在視覺體驗的基礎上對界面內容的理解,界面內容可能是文字,也可能是圖案,這些內容是引導用戶進行操作的“提示或說明”,是需要用戶進行理解的內容,如何給用戶提供一種簡單直白有趣易懂的表達體驗,需要產品設計人員考慮到用戶對需要理解的內容可能出現的任何一種理解進行預估,消除一些傳達出錯誤信息的內容,因為這些內容都是單向的表達,沒有解釋和補充的機會來糾正用戶瞬間的理解;表達體驗直接影響到用戶下一步的操作行為是否合理。注意:任何一種讓用戶產生誤解的表達都可能導致用戶產生怨言和流失。

          第三步操作體驗。

          操作體驗是用戶通過對表達內容的理解后做出的行為動作的體驗過程。功能邏輯是操作體驗的直接影響因素,如何優化用戶的操作過程是產品設計人員需要思考的核心問題之一,既需要考慮正常的邏輯,又需要考慮非正常的邏輯形式,盡可能考慮到用戶可能出現的任何一種行為方式,才能保證用戶進行正確的愉悅的操作過程;操作體驗直接影響到用戶下一步的內容體驗。注意:任何一種繁瑣和復雜的操作邏輯都可能導致用戶產生怨言和流失。

          第四步結果體驗。

          結果體驗是用戶通過操作行為獲得的行為結果的一種體驗過程。用戶的任何一種操作可能出現的任何一個行為結果都直接影響到用戶結果體驗。如果通過用戶的任何行為來預估用戶的準確需求是產品設計人員需要思考的核心問題之一,這就要求涉及好每一種用戶行為可能出現的任何一種結果,并預測每一種結果是否能夠滿足用戶的需求,如果不能,如何采用其他方式來滿足用戶的行為需求。注意:任何一種不能滿足用戶需求的操作結果都可能導致用戶產生怨言和流失。

          總結:用戶是絕頂聰明的天才,因為他可能發現任何一個細微的不足;用戶是傻子,因為任何一個微不足道的細節失誤都可能導致用戶出現挫敗感;每一個細節看起來都微不足道,但是每一個細節的綜合就會促使整個產品趨于完美,也許用戶只是感動于某個細節,但他接受你一定是認同你的全部!

          posted @ 2009-08-14 10:53 我愛咖啡 閱讀(79) | 評論 (0)編輯 收藏

          用Windows內置功能輕松查看端口使用情況 (轉)

          用Windows內置功能輕松查看端口使用情況   [閱讀: 564]

          在局域網使用中,我們常常會發現系統中開放了一些莫名其妙的端口,這就給系統的安全帶來了一些隱患。為了讓端口的使用盡在掌握之中,一些朋友就會使用第三方工具來進行檢查出使用端口的特定程序究竟是誰,但實際上我們完全不必這樣興師動眾,因為Windows已經內置了這個功能,下面讓我們來學習一下吧!
            查看端口開放情況
           稍有經驗的網管就會知道使用Netstat命令可以查看系統當前開放的端口有哪些,但你知道嗎?如果在使用Netstat命令的同時加上參數“-o”的話,就可以讓我們進一步知曉端口的關聯進程標識符 (PID)了,也就是說這個PID可用來確定哪個進程(程序)在使用特定的端口。例如,現在使用“netstat-ano”命令后可以發現端口3026、 3030、3728在開放(如圖1所示)。
            從上圖中并不能直接看出這三個端口的關聯進程是誰,但我們卻可以通過PID信息知道是628這個進程在使用這三個端口。

          1 查看端口
            激活進程PID
           既然知道系統中有個PID為628的進程,那么就來看看它究竟是誰。大家都知道,查看系統進程可以使用同時按“Ctrl+Alt+Delete”組合鍵的方法,在打開的“Windows任務管理器”的“進程”選項卡中進行。但默認狀態下,“進程”選項卡中是沒有PID這一項存在的,這樣我們就無法知道進程相對應的PID是什么了。因此,要首先激活進程對應的PID項顯示狀態才行。方法如下:
            單擊“Windows任務管理器”窗口中“查看”下的“選擇列”菜單項,然后單擊選中“PID”(進程標識符)復選框(如圖2所示)。
          2 選擇進程PID
            稍后在“Windows任務管理器”窗口“進程”選項卡的列表中就會發現多出了PID這一項,接下來只需按從大到小的順序查找到PID為628的進程后,就可以發現“幕后程序”究竟是誰了(如圖3所示)。

          3 Windows任務管理器
            通過上圖中顯示信息可以發現,原來使用端口3026、3030、3728的關聯PID是628,而使用PID為628的應用程序是“MSN Messenger”!!瞧,我們已經輕易地為端口找到的“另一半”了!

          posted @ 2009-07-30 11:06 我愛咖啡 閱讀(158) | 評論 (0)編輯 收藏

          【轉】淺談Hibernate的flush機制

          隨著Hibernate在Java開發中的廣泛應用,我們在使用Hibernate進行對象持久化操作中也遇到了各種各樣的問題。這些問題往往都是我們對Hibernate缺乏了解所致,這里我講個我從前遇到的問題及一些想法,希望能給大家一點借鑒。

                  這是在一次事務提交時遇到的異常。
                  an assertion failure occured (this may indicate a bug in Hibernate, but is more likely due to unsafe use of the session)
          net.sf.hibernate.AssertionFailure: possible nonthreadsafe access to session
          注:非possible non-threadsafe access to the session (那是另外的錯誤,類似但不一樣)

                  這個異常應該很多的朋友都遇到過,原因可能各不相同。但所有的異常都應該是在flush或者事務提交的過程中發生的。這一般由我們在事務開始至事務提交的過程中進行了不正確的操作導致,也會在多線程同時操作一個Session時發生,這里我們僅僅討論單線程的情況,多線程除了線程同步外基本與此相同。

                  至于具體是什么樣的錯誤操作那?我給大家看一個例子(假設Hibernate配置正確,為保持代碼簡潔,不引入包及處理任何異常)
            

          SessionFactory sf = new Configuration().configure().buildSessionFactory() ;
          Session s = sf.openSession();
          Cat cat = new Cat();

          Transaction tran = s.beginTransaction(); (1)
          s.save(cat); (2)(此處同樣可以為update delete)
          s.evict(cat); (3)
          tran.commit(); (4)
          s.close();(5)


                  這就是引起此異常的典型錯誤。我當時就遇到了這個異常,檢查代碼時根本沒感覺到這段代碼出了問題,想當然的認為在Session上開始一個事務,通過Session將對象存入數據庫,再將這個對象從Session上拆離,提交事務,這是一個很正常的流程。如果這里正常的話,那問題一定在別處。

                   問題恰恰就在這里,我的想法也許沒有錯,但是一個錯誤的論據所引出的觀點永遠都不可能是正確的。因為我一直以為直接在對數據庫進行操作,忘記了在我與數據庫之間隔了一個Hibernate,Hibernate在為我們提供持久化服務的同時,也改變了我們對數據庫的操作方式,這種方式與我們直接的數據庫操作有著很多的不同,正因為我們對這種方式沒有一個大致的了解造成了我們的應用并未得到預先設想的結果。

          那Hibernate的持久化機制到底有什么不同那?簡單的說,Hibernate在數據庫層之上實現了一個緩存區,當應用save或者update一個對象時,Hibernate并未將這個對象實際的寫入數據庫中,而僅僅是在緩存中根據應用的行為做了登記,在真正需要將緩存中的數據flush入數據庫時才執行先前登記的所有行為。

          在實際執行的過程中,每個Session是通過幾個映射和集合來維護所有與該Session建立了關聯的對象以及應用對這些對象所進行的操作的,與我們這次討論有關的有entityEntries(與Session相關聯的對象的映射)、insertions(所有的插入操作集合)、deletions(刪除操作集合)、updates(更新操作集合)。下面我就開始解釋在最開始的例子中,Hibernate到底是怎樣運作的。
          (1)生成一個事務的對象,并標記當前的Session處于事務狀態(注:此時并未啟動數據庫級事務)。
          (2)應用使用s.save保存cat對象,這個時候Session將cat這個對象放入entityEntries,用來標記cat已經和當前的會話建立了關聯,由于應用對cat做了保存的操作,Session還要在insertions中登記應用的這個插入行為(行為包括:對象引用、對象id、Session、持久化處理類)。
          (3)s.evict(cat)將cat對象從s會話中拆離,這時s會從entityEntries中將cat這個對象移出。
          (4)事務提交,需要將所有緩存flush入數據庫,Session啟動一個事務,并按照insert,update,……,delete的順序提交所有之前登記的操作(注意:所有insert執行完畢后才會執行update,這里的特殊處理也可能會將你的程序搞得一團糟,如需要控制操作的執行順序,要善于使用flush),現在cat不在entityEntries中,但在執行insert的行為時只需要訪問insertions就足夠了,所以此時不會有任何的異常。異常出現在插入后通知Session該對象已經插入完畢這個步驟上,這個步驟中需要將entityEntries中cat的existsInDatabase標志置為true,由于cat并不存在于entityEntries中,此時Hibernate就認為insertions和entityEntries可能因為線程安全的問題產生了不同步(也不知道Hibernate的開發者是否考慮到例子中的處理方式,如果沒有的話,這也許算是一個bug吧),于是一個net.sf.hibernate.AssertionFailure就被拋出,程序終止。

          我想現在大家應該明白例子中的程序到底哪里有問題了吧,我們的錯誤的認為s.save會立即的執行,而將cat對象過早的與Session拆離,造成了Session的insertions和entityEntries中內容的不同步。所以我們在做此類操作時一定要清楚Hibernate什么時候會將數據flush入數據庫,在未flush之前不要將已進行操作的對象從Session上拆離。

          對于這個錯誤的解決方法是,我們可以在(2)和(3)之間插入一個s.flush()強制Session將緩存中的數據flush入數據庫(此時Hibernate會提前啟動事務,將(2)中的save登記的insert語句登記在數據庫事務中,并將所有操作集合清空),這樣在(4)事務提交時insertions集合就已經是空的了,即使我們拆離了cat也不會有任何的異常了。
          前面簡單的介紹了一下Hibernate的flush機制和對我們程序可能帶來的影響以及相應的解決方法,Hibernate的緩存機制還會在其他的方面給我們的程序帶來一些意想不到的影響。看下面的例子:
            

          (name為cat表的主鍵)
          Cat cat = new Cat();
          cat.setName(“tom”);
          s.save(cat);

          cat.setName(“mary”);
          s.update(cat);(6)

          Cat littleCat = new Cat();
          littleCat.setName(“tom”);
          s.save(littleCat);

          s.flush();


          這個例子看起來有什么問題?估計不了解Hibernate緩存機制的人多半會說沒有問題,但它也一樣不能按照我們的思路正常運行,在flush過程中會產生主鍵沖突,可能你想問:“在save(littleCat)之前不是已經更改cat.name并已經更新了么?為什么還會發生主鍵沖突那?”這里的原因就是我在解釋第一個例子時所提到的緩存flush順序的問題,Hibernate按照insert,update,……,delete的順序提交所有登記的操作,所以你的s.update(cat)雖然在程序中出現在s.save(littleCat)之前,但是在flush的過程中,所有的save都將在update之前執行,這就造成了主鍵沖突的發生。

          這個例子中的更改方法一樣是在(6)之后加入s.flush()強制Session在保存littleCat之前更新cat的name。這樣在第二次flush時就只會執行s.save(littleCat)這次登記的動作,這樣就不會出現主鍵沖突的狀況。

          再看一個例子(很奇怪的例子,但是能夠說明問題)

          Cat cat = new Cat();
          cat.setName(“tom”);
          s.save(cat); (7)
          s.delete(cat);(8)

          cat.id=null;(9)
          s.save(cat);(10)
          s.flush();


          這個例子在運行時會產生異常net.sf.hibernate.HibernateException: identifier of an instance of Cat altered from 8b818e920a86f038010a86f03a9d0001 to null

          這里例子也是有關于緩存的問題,但是原因稍有不同:
          (7)和(2)的處理相同。
          (8)Session會在deletions中登記這個刪除動作,同時更新entityEntries中該對象的登記狀態為DELETED。
          (9)Cat類的標識符字段為id,將其置為null便于重新分配id并保存進數據庫。
          (10)此時Session會首先在entityEntries查找cat對象是否曾經與Session做過關聯,因為cat只改變了屬性值,引用并未改變,所以會取得狀態為DELETED的那個登記對象。由于第二次保存的對象已經在當前Session中刪除,save會強制Session將緩存flush才會繼續,flush的過程中首先要執行最開始的save動作,在這個save中檢查了cat這個對象的id是否與原來執行動作時的id相同。不幸的是,此時cat的id被賦為null,異常被拋出,程序終止(此處要注意,我們在以后的開發過程盡量不要在flush之前改變已經進行了操作的對象的id)。

          這個例子中的錯誤也是由于緩存的延時更新造成的(當然,與不正規的使用Hibernate也有關系),處理方法有兩種:
          1、在(8)之后flush,這樣就可以保證(10)處save將cat作為一個全新的對象進行保存。
          2、刪除(9),這樣第二次save所引起的強制flush可以正常的執行,在數據庫中插入cat對象后將其刪除,然后繼續第二次save重新插入cat對象,此時cat的id仍與從前一致。

          這兩種方法可以根據不同的需要來使用,呵呵,總覺得好像是很不正規的方式來解決問題,但是問題本身也不夠正規,只希望能夠在應用開發中給大家一些幫助,不對的地方也希望各位給與指正。

            總的來說,由于Hibernate的flush處理機制,我們在一些復雜的對象更新和保存的過程中就要考慮數據庫操作順序的改變以及延時flush是否對程序的結果有影響。如果確實存在著影響,那就可以在需要保持這種操作順序的位置加入flush強制Hibernate將緩存中記錄的操作flush入數據庫,這樣看起來也許不太美觀,但很有效。

          posted @ 2009-07-23 17:41 我愛咖啡 閱讀(109) | 評論 (0)編輯 收藏

          java中的排序1:comparable和comparator(轉)

          分三種情況:

          簡單類型排序。

          內部對象實現comparable

          外部對象實現comparator

          1、簡單類型的排序

          簡單類型不外是byte, char, short, int, long, float, double等數據類型,這些類型不能放在聚集中,只能使用數組。java.util.Arrays方法提供了對這些類型的sort方法(實際上還有很多其他有用的方法),下面是對一個簡單的int數組排序:

                      int[] arr = {2, 3, 1,10,7,4};

                        System.out.print("before sort: ");

                        for (int i = 0; i< arr.length; i++)

                               System.out.print(arr[i] + " ");

                        System.out.println();            

                        Arrays.sort(arr);

                        System.out.print("after sort: ");

                        for (int i = 0; i< arr.length; i++)

                               System.out.print(arr[i] + " ");

                        System.out.println();      

          輸出結果:

          before sort: 2 3 1 10 7 4

          after sort: 1 2 3 4 7 10

          我們看到排序結果是按照升序排列的,下面的排序都是如此。

          Comparable & Comparator 都是用來實現集合中的排序的,只是Comparable是在集合內部定義的方法實現的排序,Comparator是在集合外部實現的排序,所以,如想實現排序,就需要在集合外定義Comparator接口的方法或在集合內實現Comparable接口的方法。

          2、內部對象實現comparable

          案例:

          class Programmer implements Comparable{

                 private String name;

                 private String language;

                 private double pay;

                

                 public Programmer(String name, String language, double pay) {

                        this.name = name;

                        this.language = language;

                        this.pay = pay;

                 }

                 public int compareTo(Object o) {

                        Programmer other = (Programmer)o;

                        return (int)pay - (int)other.pay;

                 }

                 public String toString(){

                        return "{name: " + name + ", language: " + language + ", money: " + pay + "}";

                 }

          }

          對其進行排序:

                        ArrayList list = new ArrayList();

                        list.add(new Programmer("張三", "C", 12000));

                        list.add(new Programmer("李四", "Java", 200));

                        list.add(new Programmer("王五", "C++", 5000));

                        list.add(new Programmer("錢六", "VB", 3000));

                        System.out.println("before sort: " + list);

                        Collections.sort(list);

                        System.out.println("after sort: " + list);

          3、外部對象實現comparator

          案例:

          import java.util.Arrays;
          import java.util.Comparator;

          public class SampleComparator implements Comparator {

             public int compare(Object o1, Object o2) {
               return toInt(o1) - toInt(o2);
             }

             private int toInt(Object o) {
               String str = (String) o;
               str = str.replaceAll("一
          ", "1");
               str = str.replaceAll("二
          ", "2");
               str = str.replaceAll("三
          ", "3");
               //
               return Integer.parseInt(str);
             }

             /**
              *
          測試方法
              */
             public static void main(String[] args) {
               String[] array = new String[] { "
          一二", "三", "" };
               Arrays.sort(array, new SampleComparator());
               for (int i = 0; i < array.length; i++) {
                 System.out.println(array[i]);
               }
             }

          posted @ 2009-07-01 15:30 我愛咖啡 閱讀(288) | 評論 (0)編輯 收藏

          主站蜘蛛池模板: 广南县| 南皮县| 苏尼特右旗| 商丘市| 富阳市| 高尔夫| 赣榆县| 株洲市| 绥德县| 庄浪县| 彭水| 贺兰县| 灵璧县| 亳州市| 洮南市| 阿图什市| 商洛市| 贵南县| 惠水县| 莲花县| 徐水县| 玉林市| 卢氏县| 松原市| 德钦县| 安陆市| 凉山| 闽清县| 施秉县| 和林格尔县| 九江市| 道孚县| 南投县| 东港市| 平江县| 罗定市| 秦安县| 通辽市| 常熟市| 阿克| 大城县|