2006年6月16日

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

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

          如何進行浮點數精度計算?????????????
          ????? Java中的簡單浮點數類型float和double不能夠進行運算。不光是Java,在其它很多編程語言中也有這樣的問題。在大多數情況下,計算的結果是準確的,但是多試幾次(可以做一個循環)就可以試出類似上面的錯誤。現在終于理解為什么要有BCD碼了。
          這個問題相當嚴重,如果你有9.999999999999元,你的計算機是不會認為你可以購買10元的商品的。
          在有的編程語言中提供了專門的貨幣類型來處理這種情況,但是Java沒有。現在讓我們看看如何解決這個問題。
          ?
          四舍五入
          我們的第一個反應是做四舍五入。Math類中的round方法不能設置保留幾位小數,我們只能象這樣(保留兩位):
          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只能用來做科學計算或者是工程計算,在商業計算中我們要用java.math.BigDecimal。BigDecimal一共有4個夠造方法,我們不關心用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簡要描述相當的明確,而且通常情況下,上面的那一個使用起來要方便一些。我們可能想都不想就用上了,會有什么問題呢?等到出了問題的時候,才發現上面哪個夠造方法的詳細說明中有這么一段:
          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.
          原來我們如果需要精確計算,非要用String來夠造BigDecimal不可!在《Effective Java》一書中的例子是用String來夠造BigDecimal的,但是書上卻沒有強調這一點,這也許是一個小小的失誤吧。
          ?
          解決方案
          現在我們已經可以解決這個問題了,原則是使用BigDecimal并且一定要用String來夠造。
          但是想像一下吧,如果我們要做一個加法運算,需要先將兩個浮點數轉為String,然后夠造成BigDecimal,在其中一個上調用add方法,傳入另一個作為參數,然后把運算的結果(BigDecimal)再轉換為浮點數。你能夠忍受這么煩瑣的過程嗎?下面我們提供一個工具類Arith來簡化操作。它提供以下靜態方法,包括加減乘除和四舍五入:
          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的簡單類型不能夠精確的對浮點數進行運算,這個工具類提供精
          ?* 確的浮點數運算,包括加減乘除和四舍五入。
          ?*/
          public class Arith{
          ??? //默認除法運算精度
          ??? private static final int DEF_DIV_SCALE = 10;
          ??? //這個類不能實例化
          ??? private Arith(){
          ??? }
          ?
          ??? /**
          ???? * 提供精確的加法運算。
          ???? * @param v1 被加數
          ???? * @param v2 加數
          ???? * @return 兩個參數的和
          ???? */
          ??? 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 被減數
          ???? * @param v2 減數
          ???? * @return 兩個參數的差
          ???? */
          ??? 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 被乘數
          ???? * @param v2 乘數
          ???? * @return 兩個參數的積
          ???? */
          ??? 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();
          ??? }
          ?
          ??? /**
          ???? * 提供(相對)精確的除法運算,當發生除不盡的情況時,精確到
          ???? * 小數點以后10位,以后的數字四舍五入。
          ???? * @param v1 被除數
          ???? * @param v2 除數
          ???? * @return 兩個參數的商
          ???? */
          ??? public static double div(double v1,double v2){
          ??????? return div(v1,v2,DEF_DIV_SCALE);
          ??? }
          ?
          ??? /**
          ???? * 提供(相對)精確的除法運算。當發生除不盡的情況時,由scale參數指
          ???? * 定精度,以后的數字四舍五入。
          ???? * @param v1 被除數
          ???? * @param v2 除數
          ???? * @param scale 表示表示需要精確到小數點以后幾位。
          ???? * @return 兩個參數的商
          ???? */
          ??? 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();
          ??? }
          ?
          ??? /**
          ???? * 提供精確的小數位四舍五入處理。
          ???? * @param v 需要四舍五入的數字
          ???? * @param scale 小數點后保留幾位
          ???? * @return 四舍五入后的結果
          ???? */
          ??? 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)編輯 收藏

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

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

          我比較傾向另幾種做法:

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

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

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


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

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

          Ioc模式

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

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

            AInterface a = new AInterfaceImp();

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

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

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

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

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

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

            有過EJB開發經驗的人都知道,每個EJB的調用都需要通過JNDI尋找到工廠性質的Home接口,在我的教程EJB是什么章節中,我也是從依賴和工廠模式角度來闡述EJB的使用。

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

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

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

            實現comp實例有兩種途徑:單態工廠模式和Ioc。

          工廠模式實現如下:

          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可根據配置文件XML中定義的C子類實現,通過createInstanceOfC()生成C的具體實例。
          使用Ioc依賴性注射( Dependency Injection )實現Picocontainer如下,B類如同通常POJO類,如下:

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

          假設C接口/類有有一個具體實現CImp類。當客戶端調用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();
             }
          }
          ?

            因此,當客戶端調用B時,分別使用工廠模式和Ioc有不同的特點和區別:

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

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

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

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

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

          首先編制一個記錄接口:

          public interface Logging {

            public void enableLogging(Log log);

          }
          ?

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

          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");
            }
          }

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

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

            ..... //業務功能
          }
          public class PicoXXXX2Manager extends LogSwitcher
          {

            ..... //業務功能
          }
          ?

          注意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


          ?

            由上代碼可見,通過使用簡單一行logging.enableLogging()方法使所有的應用類的記錄功能激活。這是不是類似AOP的advice實現?

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

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

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

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


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

          Copyright © 曹青松

          主站蜘蛛池模板: 桑日县| 加查县| 清涧县| 桂平市| 仙游县| 双流县| 平武县| 嘉定区| 宝山区| 辽宁省| 什邡市| 额济纳旗| 南投县| 胶南市| 成都市| 哈尔滨市| 榆中县| 邵武市| 德化县| 鹤峰县| 志丹县| 乡城县| 青川县| 公安县| 郴州市| 武城县| 武宁县| 青冈县| 平湖市| 成安县| 通渭县| 绥德县| 赣州市| 黄骅市| 阳山县| 伊春市| 保靖县| 朝阳县| 安多县| 博野县| 汨罗市|