lidwup

2009年5月25日 #

用户是一个绝顶聪明的傻子(转,向原创致敬)

 
从用户能够感受得到的角度进行分析(做用户体验的原则:坚决做用户感受得到,绝不做用户感受不到的——控制成本的有效办法),把用户体验划分为四步(用户体验四步曲):

第一步视觉体验。

视觉体验是用户对界面设计的第一感官体验。它是完全是用户几乎没有经过思考前的一种视觉感受,如何给用户提供一种近于完美的视觉体验,需要考虑到用户看到界面时可能出现的任何一种感受,这就提出UI设计师需要对目标用户偏爱的准确了解和把握,精确传达需要传达的含义,才会获得预期的效果;视觉体验直接影响到用户是否停留在此页面进行一下步的表达理解。注意:任何一种让用户感官视觉不适的界面都可能导致用户产生怨言和流失。

第二步表达体验。

表达体验是用户在视觉体验的基础上对界面内容的理解,界面内容可能是文字,也可能是图案,这些内容是引导用户进行操作的“提示或说明”,是需要用户进行理解的内容,如何给用户提供一种简单直白有趣易懂的表达体验,需要产品设计人员考虑到用户对需要理解的内容可能出现的任何一种理解进行预估,消除一些传达出错误信息的内容,因为这些内容都是单向的表达,没有解释和补充的机会来纠正用户瞬间的理解;表达体验直接影响到用户下一步的操作行为是否合理。注意:任何一种让用户产生误解的表达都可能导致用户产生怨言和流失。

第三步操作体验。

操作体验是用户通过对表达内容的理解后做出的行为动作的体验过程。功能逻辑是操作体验的直接影响因素,如何优化用户的操作过程是产品设计人员需要思考的核心问题之一,既需要考虑正常的逻辑,又需要考虑非正常的逻辑形式,尽可能考虑到用户可能出现的任何一种行为方式,才能保证用户进行正确的愉悦的操作过程;操作体验直接影响到用户下一步的内容体验。注意:任何一种繁琐和复杂的操作逻辑都可能导致用户产生怨言和流失。

第四步结果体验。

结果体验是用户通过操作行为获得的行为结果的一种体验过程。用户的任何一种操作可能出现的任何一个行为结果都直接影响到用户结果体验。如果通过用户的任何行为来预估用户的准确需求是产品设计人员需要思考的核心问题之一,这就要求涉及好每一种用户行为可能出现的任何一种结果,并预测每一种结果是否能够满足用户的需求,如果不能,如何采用其他方式来满足用户的行为需求。注意:任何一种不能满足用户需求的操作结果都可能导致用户产生怨言和流失。

总结:用户是绝顶聪明的天才,因为他可能发现任何一个细微的不足;用户是傻子,因为任何一个微不足道的细节失误都可能导致用户出现挫败感;每一个细节看起来都微不足道,但是每一个细节的综合就会促使整个产品趋于完美,也许用户只是感动于某个细节,但他接受你一定是认同你的全部!

posted @ 2009-08-14 10:53 我爱咖啡 阅读(68) | 评论 (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 我爱咖啡 阅读(145) | 评论 (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 我爱咖啡 阅读(98) | 评论 (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 我爱咖啡 阅读(277) | 评论 (0)编辑 收藏

oracle 支持的递归查询语句

oracle支持的递归查询语句:Connect By Prior

如:select * from module where parentid>-1  Start With ID =621 Connect By Prior ID = ParentId

posted @ 2009-05-25 02:12 我爱咖啡| 编辑 收藏