2006年8月31日

          今天在數(shù)值計算時碰到一個問題.程序如下:
          ??double a = (3.3-2.4)/0.1;
          ??System.out.println(a);
          你可能認為結(jié)果很簡單,不就是9嘛,是事實上,結(jié)果為:8.999999998,為什么呢?我翻閱了一些資料,終于找出了原因.
          為什么浮點數(shù)會丟失精度??
          1. 十進制數(shù)的二進制表示可能不夠精確

            浮點數(shù)或是雙精度浮點數(shù)無法精確表示的情況并不少見。浮點數(shù)值沒辦法用十進制來精確表示的原因要歸咎于CPU表示浮點數(shù)的方法。這樣的話您就可能會犧牲一些精度,有些浮點數(shù)運算也會引入誤差。以上面提到的情況為例,2.4的二進制表示并非就是精確的2.4。反而最為接近的二進制表示是 2.3999999999999999。原因在于浮點數(shù)由兩部分組成:指數(shù)和尾數(shù)。浮點數(shù)的值實際上是由一個特定的數(shù)學(xué)公式計算得到的。您所遇到的精度損失會在任何操作系統(tǒng)和編程環(huán)境中遇到。
            注意: 您可以使用Binary Coded Decimal (BCD)庫來保持精度。BCD數(shù)字編碼方法會把每一個十進制數(shù)字位單獨編碼。?
          2. 類型失配
            您可能混合了浮點數(shù)和雙精度浮點數(shù)類型。請確定您在進行數(shù)學(xué)運算的時候所有的數(shù)據(jù)類型全部相同。
            注意:float類型的變量只有7位的精度,而double類型的變量有15位的精度。

          如何進行浮點數(shù)精度計算?????????????
          ????? Java中的簡單浮點數(shù)類型float和double不能夠進行運算。不光是Java,在其它很多編程語言中也有這樣的問題。在大多數(shù)情況下,計算的結(jié)果是準(zhǔn)確的,但是多試幾次(可以做一個循環(huán))就可以試出類似上面的錯誤。現(xiàn)在終于理解為什么要有BCD碼了。
          這個問題相當(dāng)嚴重,如果你有9.999999999999元,你的計算機是不會認為你可以購買10元的商品的。
          在有的編程語言中提供了專門的貨幣類型來處理這種情況,但是Java沒有。現(xiàn)在讓我們看看如何解決這個問題。
          ?
          四舍五入
          我們的第一個反應(yīng)是做四舍五入。Math類中的round方法不能設(shè)置保留幾位小數(shù),我們只能象這樣(保留兩位):
          public double round(double value){
          ??? return Math.round(value*100)/100.0;
          }
          非常不幸,上面的代碼并不能正常工作,給這個方法傳入4.015它將返回4.01而不是4.02,如我們在上面看到的
          4.015*100=401.49999999999994
          因此如果我們要做到精確的四舍五入,不能利用簡單類型做任何運算
          java.text.DecimalFormat也不能解決這個問題:
          System.out.println(new java.text.DecimalFormat("0.00").format(4.025));
          輸出是4.02
          ?
          BigDecimal
          在《Effective Java》這本書中也提到這個原則,float和double只能用來做科學(xué)計算或者是工程計算,在商業(yè)計算中我們要用java.math.BigDecimal。BigDecimal一共有4個夠造方法,我們不關(guān)心用BigInteger來夠造的那兩個,那么還有兩個,它們是:
          BigDecimal(double val)
          ????????? Translates a double into a BigDecimal.
          BigDecimal(String val)
          ????????? Translates the String repre sentation of a BigDecimal into a BigDecimal.
          上面的API簡要描述相當(dāng)?shù)拿鞔_,而且通常情況下,上面的那一個使用起來要方便一些。我們可能想都不想就用上了,會有什么問題呢?等到出了問題的時候,才發(fā)現(xiàn)上面哪個夠造方法的詳細說明中有這么一段:
          Note: the results of this constructor can be somewhat unpredictable. One might assume that new BigDecimal(.1) is exactly equal to .1, but it is actually equal to .1000000000000000055511151231257827021181583404541015625. This is so because .1 cannot be represented exactly as a double (or, for that matter, as a binary fraction of any finite length). Thus, the long value that is being passed in to the constructor is not exactly equal to .1, appearances nonwithstanding.
          The (String) constructor, on the other hand, is perfectly predictable: new BigDecimal(".1") is exactly equal to .1, as one would expect. Therefore, it is generally recommended that the (String) constructor be used in preference to this one.
          原來我們?nèi)绻枰_計算,非要用String來夠造BigDecimal不可!在《Effective Java》一書中的例子是用String來夠造BigDecimal的,但是書上卻沒有強調(diào)這一點,這也許是一個小小的失誤吧。
          ?
          解決方案
          現(xiàn)在我們已經(jīng)可以解決這個問題了,原則是使用BigDecimal并且一定要用String來夠造。
          但是想像一下吧,如果我們要做一個加法運算,需要先將兩個浮點數(shù)轉(zhuǎn)為String,然后夠造成BigDecimal,在其中一個上調(diào)用add方法,傳入另一個作為參數(shù),然后把運算的結(jié)果(BigDecimal)再轉(zhuǎn)換為浮點數(shù)。你能夠忍受這么煩瑣的過程嗎?下面我們提供一個工具類Arith來簡化操作。它提供以下靜態(tài)方法,包括加減乘除和四舍五入:
          public static double add(double v1,double v2)
          public static double sub(double v1,double v2)
          public static double mul(double v1,double v2)
          public static double div(double v1,double v2)
          public static double div(double v1,double v2,int scale)
          public static double round(double v,int scale)
          附錄
          源文件Arith.java:
          import java.math.BigDecimal;
          /**
          ?* 由于Java的簡單類型不能夠精確的對浮點數(shù)進行運算,這個工具類提供精
          ?* 確的浮點數(shù)運算,包括加減乘除和四舍五入。
          ?*/
          public class Arith{
          ??? //默認除法運算精度
          ??? private static final int DEF_DIV_SCALE = 10;
          ??? //這個類不能實例化
          ??? private Arith(){
          ??? }
          ?
          ??? /**
          ???? * 提供精確的加法運算。
          ???? * @param v1 被加數(shù)
          ???? * @param v2 加數(shù)
          ???? * @return 兩個參數(shù)的和
          ???? */
          ??? public static double add(double v1,double v2){
          ??????? BigDecimal b1 = new BigDecimal(Double.toString(v1));
          ??????? BigDecimal b2 = new BigDecimal(Double.toString(v2));
          ??????? return b1.add(b2).doubleValue();
          ??? }
          ??? /**
          ???? * 提供精確的減法運算。
          ???? * @param v1 被減數(shù)
          ???? * @param v2 減數(shù)
          ???? * @return 兩個參數(shù)的差
          ???? */
          ??? public static double sub(double v1,double v2){
          ??????? BigDecimal b1 = new BigDecimal(Double.toString(v1));
          ??????? BigDecimal b2 = new BigDecimal(Double.toString(v2));
          ??????? return b1.subtract(b2).doubleValue();
          ??? }
          ??? /**
          ???? * 提供精確的乘法運算。
          ???? * @param v1 被乘數(shù)
          ???? * @param v2 乘數(shù)
          ???? * @return 兩個參數(shù)的積
          ???? */
          ??? public static double mul(double v1,double v2){
          ??????? BigDecimal b1 = new BigDecimal(Double.toString(v1));
          ??????? BigDecimal b2 = new BigDecimal(Double.toString(v2));
          ??????? return b1.multiply(b2).doubleValue();
          ??? }
          ?
          ??? /**
          ???? * 提供(相對)精確的除法運算,當(dāng)發(fā)生除不盡的情況時,精確到
          ???? * 小數(shù)點以后10位,以后的數(shù)字四舍五入。
          ???? * @param v1 被除數(shù)
          ???? * @param v2 除數(shù)
          ???? * @return 兩個參數(shù)的商
          ???? */
          ??? public static double div(double v1,double v2){
          ??????? return div(v1,v2,DEF_DIV_SCALE);
          ??? }
          ?
          ??? /**
          ???? * 提供(相對)精確的除法運算。當(dāng)發(fā)生除不盡的情況時,由scale參數(shù)指
          ???? * 定精度,以后的數(shù)字四舍五入。
          ???? * @param v1 被除數(shù)
          ???? * @param v2 除數(shù)
          ???? * @param scale 表示表示需要精確到小數(shù)點以后幾位。
          ???? * @return 兩個參數(shù)的商
          ???? */
          ??? public static double div(double v1,double v2,int scale){
          ??????? if(scale<0){
          ??????????? throw new IllegalArgumentException(
          ??????????????? "The scale must be a positive integer or zero");
          ??????? }
          ??????? BigDecimal b1 = new BigDecimal(Double.toString(v1));
          ??????? BigDecimal b2 = new BigDecimal(Double.toString(v2));
          ??????? return b1.divide(b2,scale,BigDecimal.ROUND_HALF_UP).doubleValue();
          ??? }
          ?
          ??? /**
          ???? * 提供精確的小數(shù)位四舍五入處理。
          ???? * @param v 需要四舍五入的數(shù)字
          ???? * @param scale 小數(shù)點后保留幾位
          ???? * @return 四舍五入后的結(jié)果
          ???? */
          ??? public static double round(double v,int scale){
          ??????? if(scale<0){
          ??????????? throw new IllegalArgumentException(
          ??????????????? "The scale must be a positive integer or zero");
          ??????? }
          ??????? BigDecimal b = new BigDecimal(Double.toString(v));
          ??????? BigDecimal one = new BigDecimal("1");
          ??????? return b.divide(one,scale,BigDecimal.ROUND_HALF_UP).doubleValue();
          ??? }
          };

          posted @ 2006-08-31 11:16 曹青松 閱讀(2945) | 評論 (4)編輯 收藏


          2006年8月28日

          ??? 可以把普通的 Java 程序做成真正的 exe, 也就是單一個 exe 就可以在沒有安裝 JVM 的機器上運行。這樣的工具常見的有 JET gcj. 前者是收費的,而且做出來的 exe 還是需要一堆 dll 。推薦使用 gcj. 他有 windows Linux 版,直接下載 zip 包,不需要安裝,里面有不少例子,一些 build 的批處理文件。從原理來說 gcj 自己實現(xiàn)了 JVM 規(guī)范,也就是你編寫一個 HelloWorld.java, 其中的 main 方法為 System.out.println("foo");
          當(dāng)使用 gcj 把它做成 exe( 大約 2M ) ,運行這個 exe 時,會啟動里面的一個小型 jvm, 在這上面跑 HelloWorld

          其實,把 Java 做成純 exe 實在是吃力不討好,有很多限制,文件又大。

          我比較傾向另幾種做法:

          . 使用 InstallAnywhere 等工具,制作一個 exe 的安裝包
          用戶可以選擇使用他機器上的 JRE 或是這個安裝包內(nèi)的 JRE 來運行程序
          這是很常見的一種做法,如 JBuilder 就是這么做的。
          這樣的好處是不要求對方機器上裝有 JRE ,而且你原來的程序不需要任何改動。
          InstallAnywhere
          中一個壓縮的 JRE 大概是 8M

          . 制作成可執(zhí)行的 jar, 也就是在 META-INF MANIFEST 文件制定 Main-Class
          可以通過命令行 java -jar jarfile.jar 來執(zhí)行, windows 默認的把 *.jar 使用 javaw -jar 打開,所以有些機器上可以直接雙擊 jar 運行。

          . 制作偽 exe, 其實和上一種做法是一樣的,只不過做成 exe, 調(diào)用系統(tǒng)的 java.exe 來運行它,這樣的工具有 nativeJ,exe4j


          其實 Java 不像 VB,Delphi 只是一個語言,而是一個平臺。
          jar
          是最常用的部署單元,做成 exe 沒什么意思。
          一、 exe4j
          ???
          說明: exe4j 可以將 Jar 文件制作成 exe 文件,但需 jre 支持,也可將 Jar 文件放在外面。
          ???
          軟件性質(zhì):共享軟件
          ???
          下載地址: http://www.ej-technologies.com/products/exe4j/overview.html
          二、 JBuilder
          ???
          說明:新版本的 JBuilder 可以直接把工程制作成各系統(tǒng)的可執(zhí)行文件,包括 Windows 系統(tǒng)。
          ???
          軟件性質(zhì):商業(yè)軟件
          ???
          下載地址:略。我是從 eMule 下載的。
          三、 NativeJ
          ???
          說明:與 exe4j 功能類似。
          ???
          軟件性質(zhì):共享軟件
          ???
          下載地址: http://www.dobysoft.com/products/nativej/download.html
          四、 Excelsior JET
          ???
          說明:可以直接將 Java 類文件制作成 exe 文件,除 AWT Swing 及第三方圖形接口外可不需 jre 支持( Java5.0 不行)。
          ???
          軟件性質(zhì):共享軟件
          ???
          下載地址: http://excelsior-usa.com/home.html
          五、 jshrink
          ???
          說明:可將 Jar 文件打包進 exe 文件。同時具有混淆功能(這才是它的主要功能)。
          ???
          軟件性質(zhì):共享軟件
          ???
          下載地址: http://www.e-t.com/jshrink.html
          六、 InstallAnywhere
          ???
          說明:打包工具,對 Java 打包最好用。可打包成各操作系統(tǒng)運行包。包括 Windows 系統(tǒng)。
          ???
          軟件性質(zhì):商業(yè)軟件。
          ???
          下載地址: http://www.zerog.com/
          七、 InstallShieldX
          ???
          說明:與 InstallAnywhere 類似,但比 InstallAnywhere 功能強大。相對的,比較復(fù)雜,不易上手,我現(xiàn)在還沒學(xué)會。
          ???
          軟件性質(zhì):商業(yè)軟件。
          ???
          下載地址: http://www.installshield.com/

          posted @ 2006-08-28 20:37 曹青松 閱讀(1102) | 評論 (0)編輯 收藏


          2006年8月11日

          Ioc模式

            分離關(guān)注( Separation of Concerns : SOC)是Ioc模式和AOP產(chǎn)生最原始動力,通過功能分解可得到關(guān)注點,這些關(guān)注可以是 組件Components, 方面Aspects或服務(wù)Services。

            從GoF設(shè)計模式中,我們已經(jīng)習(xí)慣一種思維編程方式:Interface Driven Design 接口驅(qū)動,接口驅(qū)動有很多好處,可以提供不同靈活的子類實現(xiàn),增加代碼穩(wěn)定和健壯性等等,但是接口一定是需要實現(xiàn)的,也就是如下語句遲早要執(zhí)行:

            AInterface a = new AInterfaceImp();

            AInterfaceImp是接口AInterface的一個子類,Ioc模式可以延緩接口的實現(xiàn),根據(jù)需要實現(xiàn),有個比喻:接口如同空的模型套,在必要時,需要向模型套注射石膏,這樣才能成為一個模型實體,因此,我們將人為控制接口的實現(xiàn)成為"注射"。

            Ioc英文為 Inversion of Control,即反轉(zhuǎn)模式,這里有著名的好萊塢理論:你呆著別動,到時我會找你。

            其實Ioc模式也是解決調(diào)用者和被調(diào)用者之間的一種關(guān)系,上述AInterface實現(xiàn)語句表明當(dāng)前是在調(diào)用被調(diào)用者AInterfaceImp,由于被調(diào)用者名稱寫入了調(diào)用者的代碼中,這產(chǎn)生了一個接口實現(xiàn)的原罪:彼此聯(lián)系,調(diào)用者和被調(diào)用者有緊密聯(lián)系,在UML中是用依賴 Dependency 表示。

            但是這種依賴在分離關(guān)注的思維下是不可忍耐的,必須切割,實現(xiàn)調(diào)用者和被調(diào)用者解耦,新的Ioc模式 Dependency Injection 模式由此產(chǎn)生了, Dependency Injection模式是依賴注射的意思,也就是將依賴先剝離,然后在適當(dāng)時候再注射進入。

          Ioc模式(Dependency Injection模式)有三種:

          第一種類型 從JNDI或ServiceManager等獲得被調(diào)用者,這里類似ServiceLocator模式。 1. EJB/J2EE
          2. Avalon(Apache的一個復(fù)雜使用不多的項目)
          第二種類型 使用JavaBeans的setter方法 1. Spring Framework,
          2. WebWork/XWork
          第三種類型 在構(gòu)造方法中實現(xiàn)依賴 1. PicoContainer,
          2. HiveMind

            有過EJB開發(fā)經(jīng)驗的人都知道,每個EJB的調(diào)用都需要通過JNDI尋找到工廠性質(zhì)的Home接口,在我的教程EJB是什么章節(jié)中,我也是從依賴和工廠模式角度來闡述EJB的使用。

            在通常傳統(tǒng)情況下,為了實現(xiàn)調(diào)用者和被調(diào)用者解耦,分離,一般是通過工廠模式實現(xiàn)的,下面將通過比較工廠模式和Ioc模式不同,加深理解Ioc模式。

          工廠模式和Ioc
            假設(shè)有兩個類B 和 C:B作為調(diào)用者,C是被調(diào)用者,在B代碼中存在對C的調(diào)用:

          public class B{
             private C comp;
            ......
          }
          ?

            實現(xiàn)comp實例有兩種途徑:單態(tài)工廠模式和Ioc。

          工廠模式實現(xiàn)如下:

          public class B{
             private C comp;
            private final static MyFactory myFactory = MyFactory.getInstance();

            public B(){
              this.comp = myFactory.createInstanceOfC();

            }
             public void someMethod(){
              this.comp.sayHello();
            }
            ......
          }
          ?

          特點:

          每次運行時,MyFactory可根據(jù)配置文件XML中定義的C子類實現(xiàn),通過createInstanceOfC()生成C的具體實例。
          使用Ioc依賴性注射( Dependency Injection )實現(xiàn)Picocontainer如下,B類如同通常POJO類,如下:

          public class B{
             private C comp;
            public B(C comp){
              this.comp = comp;
             }
             public void someMethod(){
              this.comp.sayHello();
             }
            ......
          }
          ?

          假設(shè)C接口/類有有一個具體實現(xiàn)CImp類。當(dāng)客戶端調(diào)用B時,使用下列代碼:

          public class client{
             public static void main( String[] args ) {
              DefaultPicoContainer container = new DefaultPicoContainer();
              container.registerComponentImplementation(CImp.class);
              container.registerComponentImplementation(B.class);
              B b = (B) container.getComponentInstance(B.class);
              b.someMethod();
             }
          }
          ?

            因此,當(dāng)客戶端調(diào)用B時,分別使用工廠模式和Ioc有不同的特點和區(qū)別:

            主要區(qū)別體現(xiàn)在B類的代碼,如果使用Ioc,在B類代碼中將不需要嵌入任何工廠模式等的代碼,因為這些工廠模式其實還是與C有些間接的聯(lián)系,這樣,使用Ioc徹底解耦了B和C之間的聯(lián)系。

            使用Ioc帶來的代價是:需要在客戶端或其它某處進行B和C之間聯(lián)系的組裝。

            所以,Ioc并沒有消除B和C之間這樣的聯(lián)系,只是轉(zhuǎn)移了這種聯(lián)系。
            這種聯(lián)系轉(zhuǎn)移實際也是一種分離關(guān)注,它的影響巨大,它提供了AOP實現(xiàn)的可能。

          Ioc和AOP
            AOP我們已經(jīng)知道是一種面向切面的編程方式,由于Ioc解放自由了B類,而且可以向B類實現(xiàn)注射C類具體實現(xiàn),如果把B類想像成運行時的橫向動作,無疑注入C類子類就是AOP中的一種Advice

            通過下列代碼說明如何使用Picocontainer實現(xiàn)AOP,該例程主要實現(xiàn)是記錄logger功能,通過Picocontainer可以使用簡單一行,使所有的應(yīng)用類的記錄功能激活。

          首先編制一個記錄接口:

          public interface Logging {

            public void enableLogging(Log log);

          }
          ?

          有一個LogSwitcher類,主要用來激活具體應(yīng)用中的記錄功能:

          import org.apache.commons.logging.Log;
          public class LogSwitcher
          {
            protected Log m_log;
            public void enableLogging(Log log) {
              m_log = log;
              m_log.info("Logging Enabled");
            }
          }

          一般的普通應(yīng)用JavaBeans都可以繼承這個類,假設(shè)PicoUserManager是一個用戶管理類,代碼如下:

          public class PicoUserManager extends LogSwitcher
          {
            ..... //用戶管理功能
          }
          public class PicoXXXX1Manager extends LogSwitcher
          {

            ..... //業(yè)務(wù)功能
          }
          public class PicoXXXX2Manager extends LogSwitcher
          {

            ..... //業(yè)務(wù)功能
          }
          ?

          注意LogSwitcher中Log實例是由外界賦予的,也就是說即將被外界注射進入,下面看看使用Picocontainer是如何注射Log的具體實例的。


          DefaultPicoContainer container = new DefaultPicoContainer();
          container.registerComponentImplementation(PicoUserManager.class);
          container.registerComponentImplementation(PicoXXXX1Manager.class);
          container.registerComponentImplementation(PicoXXXX2Manager.class);
          .....

          Logging logging = (Logging) container.getComponentMulticaster();

          logging.enableLogging(new SimpleLog("pico"));//激活log


          ?

            由上代碼可見,通過使用簡單一行l(wèi)ogging.enableLogging()方法使所有的應(yīng)用類的記錄功能激活。這是不是類似AOP的advice實現(xiàn)?

            總之,使用Ioc模式,可以不管將來具體實現(xiàn),完全在一個抽象層次進行描述和技術(shù)架構(gòu),因此,Ioc模式可以為容器、框架之類的軟件實現(xiàn)提供了具體的實現(xiàn)手段,屬于架構(gòu)技術(shù)中一種重要的模式應(yīng)用。

          posted @ 2006-08-11 15:43 曹青松 閱讀(242) | 評論 (0)編輯 收藏


          2006年6月16日

               摘要: 摘? 要: ? ? 目前 J2EE 應(yīng)用中,普遍使用了多層架構(gòu)。本文提出了基于 J2EE 的輕量級多層架構(gòu),即業(yè)界比較成熟的 Stru...  閱讀全文

          posted @ 2006-06-16 09:52 曹青松 閱讀(4503) | 評論 (4)編輯 收藏


          僅列出標(biāo)題  

          posts - 4, comments - 8, trackbacks - 0, articles - 0

          Copyright © 曹青松

          主站蜘蛛池模板: 洛川县| 桦甸市| 汉阴县| 北宁市| 兴仁县| 大田县| 黎平县| 东莞市| 门源| 革吉县| 和顺县| 辽中县| 秭归县| 鲜城| 崇明县| 温泉县| 大余县| 望城县| 定结县| 双牌县| 江安县| 即墨市| 威海市| 南康市| 法库县| 永平县| 巫溪县| 南漳县| 阿拉善盟| 高清| 定安县| 芜湖市| 聂荣县| 甘南县| 获嘉县| 曲水县| 巴里| 班戈县| 阳泉市| 蒲江县| 琼海市|