posts - 29, comments - 0, trackbacks - 0, articles - 0
            BlogJava :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理

          2007年5月28日

          (1):  linux command


          Mount USB:

          mount -t vfat /dev/sda1 /mnt/cdrom


          Unmount USB:

          umount /mnt/cdrom


          copy directory recyle:

          cp -r apache-ant-1.7.0 /usr/local


          unpackage file:

          tar -zxvf apache-ant-1.7.0-bin.tar.gz


          ctr+alt+f4 swich to dos interface

          alt+f7 return to graphic


          find / -name filename


          ping local ip : ifconfig





          vi usage :

          [esc] [insert]

          :w write

          :q quit



          (2):  software install

          From the Ant FAQ:

          How do you get ant-1.6.x (or any version later than 1.5.2) to work on on RedHat ES 3?

          Redhat ES 3.0 comes installed with ant 1.5.2. Even if you have your PATH and ANT_HOME variables set correctly to a later version of ant, you will always be forced to use the preinstalled version.

          To use a later version of ant on this OS you could do the following:

          $ ant -version
          Apache Ant version 1.5.2-23 compiled on November 12 2003
          $ su -l
          # rpm -e ant ant-libs
          # exit
          $ hash -r
          $ ant -version
          Apache Ant version 1.6.2 compiled on July 16 2004

          But on my computer similar do like this:
          [root@localhost TestAntBuld]# ant -version
          Apache Ant version 1.5.2-20 compiled on September 25 2003
          [root@localhost root]# sudo -l
          User root may run the following commands on this host:
          (ALL) ALL
          [root@localhost root]# rpm -e ant ant-libs
          [root@localhost root]# exit
          logout

          [root@localhost root]# bash -r
          [root@localhost root]# exit
          exit
          [root@localhost root]# ant -version
          Apache Ant version 1.7.0 compiled on December 13 2006

          Now, That is ok!

          Install Junit
          1 unzip the download zip file
          2 set path (/etc/profile and /etc/profile.d/java.sh)

          Install PostgreSQL
          1 gunzip postgresql-8.2.3.tar.gz
          tar xf postgresql-8.2.3.tar

          This will create a directory postgresql-8.2.3 under the current directory with the PostgreSQL sources. Change into that directory for the rest of the installation procedure.

          2 ./configure

          3 gmake

          4 gmake check

          5 gmake install

          6 To undo the installation use the command gmake uninstall. However, this will not remove any created directories.

          LD_LIBRARY_PATH=/usr/local/pgsql/lib

            export LD_LIBRARY_PATH
          8 set enviroment path

          PATH=/usr/local/pgsql/bin:$PATH

           export PATH

          JUnit Test:

          *** You must first compile the java classes,afer that, then you can test it!

          run the test:

              *

                To run the test from the console, type:

                    java org.junit.runner.JUnitCore junitfaq.SimpleTest

              *

                To run the test with the test runner used in main(), type:

                    java junitfaq.SimpleTest

          The passing test results in the following textual output:
                  .
          Time: 0

          OK (1 tests)

           (3):  intro_postgreSQL_startup

          Before you can do anything, you must initialize a database storage area on disk. We call this a database cluster.
          The desired file system location of your database cluster is indicated by the -D option, for example:

          $ initdb -D /usr/local/pgsql/data

          anyone can access the database, you must start the database server. The database server program is called postgres.  The postgres program must know where to find the data it is supposed to use. This is done with the -D option. Thus, the simplest way to start the server is:

          $ postgres -D /usr/local/pgsql/data

          which will leave the server running in the foreground. This must be done while logged into the PostgreSQL user account. Without -D, the server will try to use the data directory named by the environment variable PGDATA. If that variable is not provided either, it will fail.

          Normally it is better to start postgres in the background. For this, use the usual shell syntax:

          $ postgres -D /usr/local/pgsql/data >logfile 2>&1 &


          
          



           


          posted @ 2007-06-08 09:54 change| 編輯 收藏

          測試網速的命令:

          ping -n 5 -l 1472 -f www.sina.com.cn


          LDAP:

          如果你把 ldap 安裝為 windows 服務,你可以像我一樣啟動:

          net start OpenLDAP-slapd

          net stop OpenLDAP-slapd

          ldapadd -x -D "cn=Manager,dc=grid,dc=nwu" -W -f example.ldif

          ** 注意: 在第一次添加的時候要講默認的庫 添加進去才可以,否則會報錯誤 Object 找不到!!!
          dc=example,dc=com is not there by default, you have to add it as the first entry.
          Your when you are trying to add you are assuming dc=example,dc=com is there,
          just remove the fred walter and try again

          將每一個目錄ENTRY看作是一個統一表的一行,然后把屬性看作列

          FAQ:
          1:
          windows下的openldap,filter 不能家單引號 如下(去掉單引號)
          ldapsearch -x -b dc=grid,dc=nwu (objectclass=*)

          2:
          用到的objectclass (處在schema中定義) 必須在 slapd.conf 中include進來,否則報錯誤

          3:
          dn: cn=JSmith,ou=Managerment,dc=grid,dc=nwu
          cn: James Smith,Jim Smith,Jimmy Smith
          這里的dn中的cn  和屬性中的cn  必須是一樣的,否則報錯誤:alue of naming attribute 'cn' is not present in entry

          4:擴展schema
          http://blog.csdn.net/wcy1323/archive/2006/04/26/678459.aspx

          posted @ 2007-05-28 15:56 change| 編輯 收藏


          ThreadLocal通過一個Map來為每個線程都持有一個變量副本。
          這個map以當前線程為key。與synchronized相比,ThreadLocal是以空間換時間的策略來實現多線程程序。

          synchronized是利用鎖的機制,使變量或代碼塊在某一時該只能被一個線程訪問。
          而ThreadLocal為每一個線程都提供了變量的副本,使得每個線程在某一時間訪問到的并不是同一個對象,
          這樣就隔離了多個線程對數據的數據共享。
          而Synchronized卻正好相反,它用于在多個線程間通信時能夠獲得數據共享。
          Synchronized用于線程間的數據共享,而ThreadLocal則用于線程間的數據隔離。
          當然ThreadLocal并不能替代synchronized,它們處理不同的問題域。
          Synchronized用于實現同步機制,比ThreadLocal更加復雜。

          posted @ 2007-05-28 15:53 change| 編輯 收藏

          from: :OReilly.JavaScript.The.Definitive.Guide.5th.Edition.Aug.2006

           It turns out that every JavaScript object includes an internal reference to another object, known as its prototype object. Any properties of the prototype appear to be properties of an object for which it is the prototype. Another way of saying this is that a JavaScript object inherits properties from its prototype.

          In the previous section, I showed that the new operator creates a new, empty object and then invokes a constructor function as a method of that object. This is not the complete story, however. After creating the empty object, new sets the prototype of that object. The prototype of an object is the value of the prototype property of its constructor function. All functions have a prototype property that is automatically created and initialized when the function is defined. The initial value of the prototype property is an object with a single property. This property is named constructor and refers back to the constructor function with which the prototype is associated. (You may recall the constructor property from Chapter 7 ; this is why every object has a constructor property.) Any properties you add to this prototype object will appear to be properties of objects initialized by the constructor.

           

          posted @ 2007-05-28 15:50 change| 編輯 收藏

          java 是不直接支持 信號量的,我們必須自己來定義我們所需要的信號量

          class Semaphore {
          private int count;
          public Semaphore(int count) {
          this.count = count;
          }

          public synchronized void acquire() {
          while(count == 0) {
          try {
          wait();
          } catch (InterruptedException e) {
          //keep trying
          }
          }
          count--;
          }

          public synchronized void release() {
          count++;
          notify(); //alert a thread that´s blocking on this semaphore
          }
          }

          對要訪問的同步資源進行 同步計數控制,來達到同步訪問資源的目的。

          posted @ 2007-05-28 15:49 change| 編輯 收藏

          線程死鎖條件:

          1:互斥條件 ,即資源是不能夠被共享的。

          2:至少有一個進程在使用一個資源卻在等待另外一個線程所持有的一個資源

          3:資源部能夠被進程搶占。

          4 :必須有循環的等待

          那么要解除死鎖,只要讓這幾個條件中的一個不成立就可以了。例如:

          哲學家問題,如果每個哲學家都是先 取左邊的筷子,在取右邊的筷子,那么很有可能就會出現死鎖了,那么我們可以讓最后的一個哲學家是先取右邊的筷子,然后再取左邊的筷子,那么就破壞了循環等待的原則,那么死鎖自然也就不會成立了 。當然線程也可以一次獲得所有的所需資源來實現了。

          posted @ 2007-05-28 15:49 change| 編輯 收藏

           What does volatile do?

          This is probably best explained by comparing the effects that volatile and synchronized have on a method. volatile is a field modifier, while synchronized modifies code blocks and methods. So we can specify three variations of a simple accessor using those two keywords:

                   int i1;              int geti1() {return i1;}
          volatile int i2;              int geti2() {return i2;}
          int i3; synchronized int geti3() {return i3;}
          

          geti1() accesses the value currently stored in i1 in the current thread. Threads can have local copies of variables, and the data does not have to be the same as the data held in other threads. In particular, another thread may have updated i1 in it's thread, but the value in the current thread could be different from that updated value. In fact Java has the idea of a "main" memory, and this is the memory that holds the current "correct" value for variables. Threads can have their own copy of data for variables, and the thread copy can be different from the "main" memory. So in fact, it is possible for the "main" memory to have a value of 1 for i1, for thread1 to have a value of 2 for i1 and for thread2 to have a value of 3 for i1 if thread1 and thread2 have both updated i1 but those updated value has not yet been propagated to "main" memory or other threads.

          On the other hand, geti2() effectively accesses the value of i2 from "main" memory. A volatile variable is not allowed to have a local copy of a variable that is different from the value currently held in "main" memory. Effectively, a variable declared volatile must have it's data synchronized across all threads, so that whenever you access or update the variable in any thread, all other threads immediately see the same value. Of course, it is likely that volatile variables have a higher access and update overhead than "plain" variables, since the reason threads can have their own copy of data is for better efficiency.

          Well if volatile already synchronizes data across threads, what is synchronized for? Well there are two differences. Firstly synchronized obtains and releases locks on monitors which can force only one thread at a time to execute a code block, if both threads use the same monitor (effectively the same object lock). That's the fairly well known aspect to synchronized. But synchronized also synchronizes memory. In fact synchronized synchronizes the whole of thread memory with "main" memory. So executing geti3() does the following:

           

          1. The thread acquires the lock on the monitor for object this (assuming the monitor is unlocked, otherwise the thread waits until the monitor is unlocked).
          2. The thread memory flushes all its variables, i.e. it has all of its variables effectively read from "main" memory (JVMs can use dirty sets to optimize this so that only "dirty" variables are flushed, but conceptually this is the same. See section 17.9 of the Java language specification).
          3. The code block is executed (in this case setting the return value to the current value of i3, which may have just been reset from "main" memory).
          4. (Any changes to variables would normally now be written out to "main" memory, but for geti3() we have no changes.)
          5. The thread releases the lock on the monitor for object this.

          So where volatile only synchronizes the value of one variable between thread memory and "main" memory, synchronized synchronizes the value of all variables between thread memory and "main" memory, and locks and releases a monitor to boot. Clearly synchronized is likely to have more overhead than volatile.

          posted @ 2007-05-28 15:48 change| 編輯 收藏

           
          Visual C++線程同步技術剖析:臨界區,時間,信號量,互斥量
          xuefeifei 發表于 2006-1-12 10:43:00

          摘要: 多線程同步技術是計算機軟件開發的重要技術,本文對多線程的各種同步技術的原理和實現進行了初步探討。

          關鍵詞: VC++6.0; 線程同步;臨界區;事件;互斥;信號量;

          正文

          使線程同步

            在程序中使用多線程時,一般很少有多個線程能在其生命期內進行完全獨立的操作。更多的情況是一些線程進行某些處理操作,而其他的線程必須對其處理結果進行了解。正常情況下對這種處理結果的了解應當在其處理任務完成后進行。

            如果不采取適當的措施,其他線程往往會在線程處理任務結束前就去訪問處理結果,這就很有可能得到有關處理結果的錯誤了解。例如,多個線程同時訪問同一個全局變量,如果都是讀取操作,則不會出現問題。如果一個線程負責改變此變量的值,而其他線程負責同時讀取變量內容,則不能保證讀取到的數據是經過寫線程修改后的。

            為了確保讀線程讀取到的是經過修改的變量,就必須在向變量寫入數據時禁止其他線程對其的任何訪問,直至賦值過程結束后再解除對其他線程的訪問限制。象這種保證線程能了解其他線程任務處理結束后的處理結果而采取的保護措施即為線程同步。

            線程同步是一個非常大的話題,包括方方面面的內容。從大的方面講,線程的同步可分用戶模式的線程同步和內核對象的線程同步兩大類。用戶模式中線程的同步方法主要有原子訪問和臨界區等方法。其特點是同步速度特別快,適合于對線程運行速度有嚴格要求的場合。

            內核對象的線程同步則主要由事件、等待定時器、信號量以及信號燈等內核對象構成。由于這種同步機制使用了內核對象,使用時必須將線程從用戶模式切換到內核模式,而這種轉換一般要耗費近千個CPU周期,因此同步速度較慢,但在適用性上卻要遠優于用戶模式的線程同步方式。

          臨界區

            臨界區(Critical Section)是一段獨占對某些共享資源訪問的代碼,在任意時刻只允許一個線程對共享資源進行訪問。如果有多個線程試圖同時訪問臨界區,那么在有一個線程進入后其他所有試圖訪問此臨界區的線程將被掛起,并一直持續到進入臨界區的線程離開。臨界區在被釋放后,其他線程可以繼續搶占,并以此達到用原子方式操作共享資源的目的。

            臨界區在使用時以CRITICAL_SECTION結構對象保護共享資源,并分別用EnterCriticalSection()和LeaveCriticalSection()函數去標識和釋放一個臨界區。所用到的CRITICAL_SECTION結構對象必須經過InitializeCriticalSection()的初始化后才能使用,而且必須確保所有線程中的任何試圖訪問此共享資源的代碼都處在此臨界區的保護之下。否則臨界區將不會起到應有的作用,共享資源依然有被破壞的可能。

          圖1 使用臨界區保持線程同步

          下面通過一段代碼展示了臨界區在保護多線程訪問的共享資源中的作用。通過兩個線程來分別對全局變量g_cArray[10]進行寫入操作,用臨界區結構對象g_cs來保持線程的同步,并在開啟線程前對其進行初始化。為了使實驗效果更加明顯,體現出臨界區的作用,在線程函數對共享資源g_cArray[10]的寫入時,以Sleep()函數延遲1毫秒,使其他線程同其搶占CPU的可能性增大。如果不使用臨界區對其進行保護,則共享資源數據將被破壞(參見圖1(a)所示計算結果),而使用臨界區對線程保持同步后則可以得到正確的結果(參見圖1(b)所示計算結果)。代碼實現清單附下:

          // 臨界區結構對象
          CRITICAL_SECTION g_cs;
          // 共享資源
          char g_cArray[10];
          UINT ThreadProc10(LPVOID pParam)
          {
           // 進入臨界區
           EnterCriticalSection(&g_cs);
           // 對共享資源進行寫入操作
           for (int i = 0; i < 10; i++)
           {
            g_cArray[i] = 'a';
            Sleep(1);
           }
           // 離開臨界區
           LeaveCriticalSection(&g_cs);
           return 0;
          }
          UINT ThreadProc11(LPVOID pParam)
          {
           // 進入臨界區
           EnterCriticalSection(&g_cs);
           // 對共享資源進行寫入操作
           for (int i = 0; i < 10; i++)
           {
            g_cArray[10 - i - 1] = 'b';
            Sleep(1);
           }
           // 離開臨界區
           LeaveCriticalSection(&g_cs);
           return 0;
          }
          ……
          void CSample08View::OnCriticalSection()
          {
           // 初始化臨界區
           InitializeCriticalSection(&g_cs);
           // 啟動線程
           AfxBeginThread(ThreadProc10, NULL);
           AfxBeginThread(ThreadProc11, NULL);
           // 等待計算完畢
           Sleep(300);
           // 報告計算結果
           CString sResult = CString(g_cArray);
           AfxMessageBox(sResult);
          }


            在使用臨界區時,一般不允許其運行時間過長,只要進入臨界區的線程還沒有離開,其他所有試圖進入此臨界區的線程都會被掛起而進入到等待狀態,并會在一定程度上影響。程序的運行性能。尤其需要注意的是不要將等待用戶輸入或是其他一些外界干預的操作包含到臨界區。如果進入了臨界區卻一直沒有釋放,同樣也會引起其他線程的長時間等待。換句話說,在執行了EnterCriticalSection()語句進入臨界區后無論發生什么,必須確保與之匹配的LeaveCriticalSection()都能夠被執行到。可以通過添加結構化異常處理代碼來確保LeaveCriticalSection()語句的執行。雖然臨界區同步速度很快,但卻只能用來同步本進程內的線程,而不可用來同步多個進程中的線程。

            MFC為臨界區提供有一個CCriticalSection類,使用該類進行線程同步處理是非常簡單的,只需在線程函數中用CCriticalSection類成員函數Lock()和UnLock()標定出被保護代碼片段即可。對于上述代碼,可通過CCriticalSection類將其改寫如下:

          // MFC臨界區類對象
          CCriticalSection g_clsCriticalSection;
          // 共享資源
          char g_cArray[10];
          UINT ThreadProc20(LPVOID pParam)
          {
           // 進入臨界區
           g_clsCriticalSection.Lock();
           // 對共享資源進行寫入操作
           for (int i = 0; i < 10; i++)
           {
            g_cArray[i] = 'a';
            Sleep(1);
           }
           // 離開臨界區
           g_clsCriticalSection.Unlock();
           return 0;
          }
          UINT ThreadProc21(LPVOID pParam)
          {
           // 進入臨界區
           g_clsCriticalSection.Lock();
           // 對共享資源進行寫入操作
           for (int i = 0; i < 10; i++)
           {
            g_cArray[10 - i - 1] = 'b';
            Sleep(1);
           }
           // 離開臨界區
           g_clsCriticalSection.Unlock();
           return 0;
          }
          ……
          void CSample08View::OnCriticalSectionMfc()
          {
           // 啟動線程
           AfxBeginThread(ThreadProc20, NULL);
           AfxBeginThread(ThreadProc21, NULL);
           // 等待計算完畢
           Sleep(300);
           // 報告計算結果
           CString sResult = CString(g_cArray);
           AfxMessageBox(sResult);
          }


          管理事件內核對象

            在前面講述線程通信時曾使用過事件內核對象來進行線程間的通信,除此之外,事件內核對象也可以通過通知操作的方式來保持線程的同步。對于前面那段使用臨界區保持線程同步的代碼可用事件對象的線程同步方法改寫如下:

          // 事件句柄
          HANDLE hEvent = NULL;
          // 共享資源
          char g_cArray[10];
          ……
          UINT ThreadProc12(LPVOID pParam)
          {
           // 等待事件置位
           WaitForSingleObject(hEvent, INFINITE);
           // 對共享資源進行寫入操作
           for (int i = 0; i < 10; i++)
           {
            g_cArray[i] = 'a';
            Sleep(1);
           }
           // 處理完成后即將事件對象置位
           SetEvent(hEvent);
           return 0;
          }
          UINT ThreadProc13(LPVOID pParam)
          {
           // 等待事件置位
           WaitForSingleObject(hEvent, INFINITE);
           // 對共享資源進行寫入操作
           for (int i = 0; i < 10; i++)
           {
            g_cArray[10 - i - 1] = 'b';
            Sleep(1);
           }
           // 處理完成后即將事件對象置位
           SetEvent(hEvent);
           return 0;
          }
          ……
          void CSample08View::OnEvent()
          {
           // 創建事件
           hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
           // 事件置位
           SetEvent(hEvent);
           // 啟動線程
           AfxBeginThread(ThreadProc12, NULL);
           AfxBeginThread(ThreadProc13, NULL);
           // 等待計算完畢
           Sleep(300);
           // 報告計算結果
           CString sResult = CString(g_cArray);
           AfxMessageBox(sResult);
          }


            在創建線程前,首先創建一個可以自動復位的事件內核對象hEvent,而線程函數則通過WaitForSingleObject()等待函數無限等待hEvent的置位,只有在事件置位時WaitForSingleObject()才會返回,被保護的代碼將得以執行。對于以自動復位方式創建的事件對象,在其置位后一被WaitForSingleObject()等待到就會立即復位,也就是說在執行ThreadProc12()中的受保護代碼時,事件對象已經是復位狀態的,這時即使有ThreadProc13()對CPU的搶占,也會由于WaitForSingleObject()沒有hEvent的置位而不能繼續執行,也就沒有可能破壞受保護的共享資源。在ThreadProc12()中的處理完成后可以通過SetEvent()對hEvent的置位而允許ThreadProc13()對共享資源g_cArray的處理。這里SetEvent()所起的作用可以看作是對某項特定任務完成的通知。

            使用臨界區只能同步同一進程中的線程,而使用事件內核對象則可以對進程外的線程進行同步,其前提是得到對此事件對象的訪問權。可以通過OpenEvent()函數獲取得到,其函數原型為:

          HANDLE OpenEvent(
           DWORD dwDesiredAccess, // 訪問標志
           BOOL bInheritHandle, // 繼承標志
           LPCTSTR lpName // 指向事件對象名的指針
          );


            如果事件對象已創建(在創建事件時需要指定事件名),函數將返回指定事件的句柄。對于那些在創建事件時沒有指定事件名的事件內核對象,可以通過使用內核對象的繼承性或是調用DuplicateHandle()函數來調用CreateEvent()以獲得對指定事件對象的訪問權。在獲取到訪問權后所進行的同步操作與在同一個進程中所進行的線程同步操作是一樣的。

            如果需要在一個線程中等待多個事件,則用WaitForMultipleObjects()來等待。WaitForMultipleObjects()與WaitForSingleObject()類似,同時監視位于句柄數組中的所有句柄。這些被監視對象的句柄享有平等的優先權,任何一個句柄都不可能比其他句柄具有更高的優先權。WaitForMultipleObjects()的函數原型為:

          DWORD WaitForMultipleObjects(
           DWORD nCount, // 等待句柄數
           CONST HANDLE *lpHandles, // 句柄數組首地址
           BOOL fWaitAll, // 等待標志
           DWORD dwMilliseconds // 等待時間間隔
          );


            參數nCount指定了要等待的內核對象的數目,存放這些內核對象的數組由lpHandles來指向。fWaitAll對指定的這nCount個內核對象的兩種等待方式進行了指定,為TRUE時當所有對象都被通知時函數才會返回,為FALSE則只要其中任何一個得到通知就可以返回。dwMilliseconds在飫锏淖饔糜朐赪aitForSingleObject()中的作用是完全一致的。如果等待超時,函數將返回WAIT_TIMEOUT。如果返回WAIT_OBJECT_0到WAIT_OBJECT_0+nCount-1中的某個值,則說明所有指定對象的狀態均為已通知狀態(當fWaitAll為TRUE時)或是用以減去WAIT_OBJECT_0而得到發生通知的對象的索引(當fWaitAll為FALSE時)。如果返回值在WAIT_ABANDONED_0與WAIT_ABANDONED_0+nCount-1之間,則表示所有指定對象的狀態均為已通知,且其中至少有一個對象是被丟棄的互斥對象(當fWaitAll為TRUE時),或是用以減去WAIT_OBJECT_0表示一個等待正常結束的互斥對象的索引(當fWaitAll為FALSE時)。 下面給出的代碼主要展示了對WaitForMultipleObjects()函數的使用。通過對兩個事件內核對象的等待來控制線程任務的執行與中途退出:

          // 存放事件句柄的數組
          HANDLE hEvents[2];
          UINT ThreadProc14(LPVOID pParam)
          {
           // 等待開啟事件
           DWORD dwRet1 = WaitForMultipleObjects(2, hEvents, FALSE, INFINITE);
           // 如果開啟事件到達則線程開始執行任務
           if (dwRet1 == WAIT_OBJECT_0)
           {
            AfxMessageBox("線程開始工作!");
            while (true)
            {
             for (int i = 0; i < 10000; i++);
             // 在任務處理過程中等待結束事件
             DWORD dwRet2 = WaitForMultipleObjects(2, hEvents, FALSE, 0);
             // 如果結束事件置位則立即終止任務的執行
             if (dwRet2 == WAIT_OBJECT_0 + 1)
              break;
            }
           }
           AfxMessageBox("線程退出!");
           return 0;
          }
          ……
          void CSample08View::OnStartEvent()
          {
           // 創建線程
           for (int i = 0; i < 2; i++)
            hEvents[i] = CreateEvent(NULL, FALSE, FALSE, NULL);
            // 開啟線程
            AfxBeginThread(ThreadProc14, NULL);
            // 設置事件0(開啟事件)
            SetEvent(hEvents[0]);
          }
          void CSample08View::OnEndevent()
          {
           // 設置事件1(結束事件)
           SetEvent(hEvents[1]);
          }


            MFC為事件相關處理也提供了一個CEvent類,共包含有除構造函數外的4個成員函數PulseEvent()、ResetEvent()、SetEvent()和UnLock()。在功能上分別相當與Win32 API的PulseEvent()、ResetEvent()、SetEvent()和CloseHandle()等函數。而構造函數則履行了原CreateEvent()函數創建事件對象的職責,其函數原型為:

          CEvent(BOOL bInitiallyOwn = FALSE, BOOL bManualReset = FALSE, LPCTSTR lpszName = NULL, LPSECURITY_ATTRIBUTES lpsaAttribute = NULL );


            按照此缺省設置將創建一個自動復位、初始狀態為復位狀態的沒有名字的事件對象。封裝后的CEvent類使用起來更加方便,圖2即展示了CEvent類對A、B兩線程的同步過程:

          圖2 CEvent類對線程的同步過程示意

          B線程在執行到CEvent類成員函數Lock()時將會發生阻塞,而A線程此時則可以在沒有B線程干擾的情況下對共享資源進行處理,并在處理完成后通過成員函數SetEvent()向B發出事件,使其被釋放,得以對A先前已處理完畢的共享資源進行操作。可見,使用CEvent類對線程的同步方法與通過API函數進行線程同步的處理方法是基本一致的。前面的API處理代碼可用CEvent類將其改寫為:

          // MFC事件類對象
          CEvent g_clsEvent;
          UINT ThreadProc22(LPVOID pParam)
          {
           // 對共享資源進行寫入操作
           for (int i = 0; i < 10; i++)
           {
            g_cArray[i] = 'a';
            Sleep(1);
           }
           // 事件置位
           g_clsEvent.SetEvent();
           return 0;
          }
          UINT ThreadProc23(LPVOID pParam)
          {
           // 等待事件
           g_clsEvent.Lock();
           // 對共享資源進行寫入操作
           for (int i = 0; i < 10; i++)
           {
            g_cArray[10 - i - 1] = 'b';
            Sleep(1);
           }
           return 0;
          }
          ……
          void CSample08View::OnEventMfc()
          {
           // 啟動線程
           AfxBeginThread(ThreadProc22, NULL);
           AfxBeginThread(ThreadProc23, NULL);
           // 等待計算完畢
           Sleep(300);
           // 報告計算結果
           CString sResult = CString(g_cArray);
           AfxMessageBox(sResult);
          }

           

            信號量內核對象

            信號量(Semaphore)內核對象對線程的同步方式與前面幾種方法不同,它允許多個線程在同一時刻訪問同一資源,但是需要限制在同一時刻訪問此資源的最大線程數目。在用CreateSemaphore()創建信號量時即要同時指出允許的最大資源計數和當前可用資源計數。一般是將當前可用資源計數設置為最大資源計數,每增加一個線程對共享資源的訪問,當前可用資源計數就會減1,只要當前可用資源計數是大于0的,就可以發出信號量信號。但是當前可用計數減小到0時則說明當前占用資源的線程數已經達到了所允許的最大數目,不能在允許其他線程的進入,此時的信號量信號將無法發出。線程在處理完共享資源后,應在離開的同時通過ReleaseSemaphore()函數將當前可用資源計數加1。在任何時候當前可用資源計數決不可能大于最大資源計數。

          圖3 使用信號量對象控制資源

          下面結合圖例3來演示信號量對象對資源的控制。在圖3中,以箭頭和白色箭頭表示共享資源所允許的最大資源計數和當前可用資源計數。初始如圖(a)所示,最大資源計數和當前可用資源計數均為4,此后每增加一個對資源進行訪問的線程(用黑色箭頭表示)當前資源計數就會相應減1,圖(b)即表示的在3個線程對共享資源進行訪問時的狀態。當進入線程數達到4個時,將如圖(c)所示,此時已達到最大資源計數,而當前可用資源計數也已減到0,其他線程無法對共享資源進行訪問。在當前占有資源的線程處理完畢而退出后,將會釋放出空間,圖(d)已有兩個線程退出對資源的占有,當前可用計數為2,可以再允許2個線程進入到對資源的處理。可以看出,信號量是通過計數來對線程訪問資源進行控制的,而實際上信號量確實也被稱作Dijkstra計數器。

            使用信號量內核對象進行線程同步主要會用到CreateSemaphore()、OpenSemaphore()、ReleaseSemaphore()、WaitForSingleObject()和WaitForMultipleObjects()等函數。其中,CreateSemaphore()用來創建一個信號量內核對象,其函數原型為:

          HANDLE CreateSemaphore(
           LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, // 安全屬性指針
           LONG lInitialCount, // 初始計數
           LONG lMaximumCount, // 最大計數
           LPCTSTR lpName // 對象名指針
          );


            參數lMaximumCount是一個有符號32位值,定義了允許的最大資源計數,最大取值不能超過4294967295。lpName參數可以為創建的信號量定義一個名字,由于其創建的是一個內核對象,因此在其他進程中可以通過該名字而得到此信號量。OpenSemaphore()函數即可用來根據信號量名打開在其他進程中創建的信號量,函數原型如下:

          HANDLE OpenSemaphore(
           DWORD dwDesiredAccess, // 訪問標志
           BOOL bInheritHandle, // 繼承標志
           LPCTSTR lpName // 信號量名
          );


            在線程離開對共享資源的處理時,必須通過ReleaseSemaphore()來增加當前可用資源計數。否則將會出現當前正在處理共享資源的實際線程數并沒有達到要限制的數值,而其他線程卻因為當前可用資源計數為0而仍無法進入的情況。ReleaseSemaphore()的函數原型為:

          BOOL ReleaseSemaphore(
           HANDLE hSemaphore, // 信號量句柄
           LONG lReleaseCount, // 計數遞增數量
           LPLONG lpPreviousCount // 先前計數
          );


            該函數將lReleaseCount中的值添加給信號量的當前資源計數,一般將lReleaseCount設置為1,如果需要也可以設置其他的值。WaitForSingleObject()和WaitForMultipleObjects()主要用在試圖進入共享資源的線程函數入口處,主要用來判斷信號量的當前可用資源計數是否允許本線程的進入。只有在當前可用資源計數值大于0時,被監視的信號量內核對象才會得到通知。

            信號量的使用特點使其更適用于對Socket(套接字)程序中線程的同步。例如,網絡上的HTTP服務器要對同一時間內訪問同一頁面的用戶數加以限制,這時可以為沒一個用戶對服務器的頁面請求設置一個線程,而頁面則是待保護的共享資源,通過使用信號量對線程的同步作用可以確保在任一時刻無論有多少用戶對某一頁面進行訪問,只有不大于設定的最大用戶數目的線程能夠進行訪問,而其他的訪問企圖則被掛起,只有在有用戶退出對此頁面的訪問后才有可能進入。下面給出的示例代碼即展示了類似的處理過程:

          // 信號量對象句柄
          HANDLE hSemaphore;
          UINT ThreadProc15(LPVOID pParam)
          {
           // 試圖進入信號量關口
           WaitForSingleObject(hSemaphore, INFINITE);
           // 線程任務處理
           AfxMessageBox("線程一正在執行!");
           // 釋放信號量計數
           ReleaseSemaphore(hSemaphore, 1, NULL);
           return 0;
          }
          UINT ThreadProc16(LPVOID pParam)
          {
           // 試圖進入信號量關口
           WaitForSingleObject(hSemaphore, INFINITE);
           // 線程任務處理
           AfxMessageBox("線程二正在執行!");
           // 釋放信號量計數
           ReleaseSemaphore(hSemaphore, 1, NULL);
           return 0;
          }
          UINT ThreadProc17(LPVOID pParam)
          {
           // 試圖進入信號量關口
           WaitForSingleObject(hSemaphore, INFINITE);
           // 線程任務處理
           AfxMessageBox("線程三正在執行!");
           // 釋放信號量計數
           ReleaseSemaphore(hSemaphore, 1, NULL);
           return 0;
          }
          ……
          void CSample08View::OnSemaphore()
          {
           // 創建信號量對象
           hSemaphore = CreateSemaphore(NULL, 2, 2, NULL);
           // 開啟線程
           AfxBeginThread(ThreadProc15, NULL);
           AfxBeginThread(ThreadProc16, NULL);
           AfxBeginThread(ThreadProc17, NULL);
          }


          圖4 開始進入的兩個線程

          圖5 線程二退出后線程三才得以進入

          上述代碼在開啟線程前首先創建了一個初始計數和最大資源計數均為2的信號量對象hSemaphore。即在同一時刻只允許2個線程進入由hSemaphore保護的共享資源。隨后開啟的三個線程均試圖訪問此共享資源,在前兩個線程試圖訪問共享資源時,由于hSemaphore的當前可用資源計數分別為2和1,此時的hSemaphore是可以得到通知的,也就是說位于線程入口處的WaitForSingleObject()將立即返回,而在前兩個線程進入到保護區域后,hSemaphore的當前資源計數減少到0,hSemaphore將不再得到通知,WaitForSingleObject()將線程掛起。直到此前進入到保護區的線程退出后才能得以進入。圖4和圖5為上述代脈的運行結果。從實驗結果可以看出,信號量始終保持了同一時刻不超過2個線程的進入。

            在MFC中,通過CSemaphore類對信號量作了表述。該類只具有一個構造函數,可以構造一個信號量對象,并對初始資源計數、最大資源計數、對象名和安全屬性等進行初始化,其原型如下:

          CSemaphore( LONG lInitialCount = 1, LONG lMaxCount = 1, LPCTSTR pstrName = NULL, LPSECURITY_ATTRIBUTES lpsaAttributes = NULL );


            在構造了CSemaphore類對象后,任何一個訪問受保護共享資源的線程都必須通過CSemaphore從父類CSyncObject類繼承得到的Lock()和UnLock()成員函數來訪問或釋放CSemaphore對象。與前面介紹的幾種通過MFC類保持線程同步的方法類似,通過CSemaphore類也可以將前面的線程同步代碼進行改寫,這兩種使用信號量的線程同步方法無論是在實現原理上還是從實現結果上都是完全一致的。下面給出經MFC改寫后的信號量線程同步代碼:

          // MFC信號量類對象
          CSemaphore g_clsSemaphore(2, 2);
          UINT ThreadProc24(LPVOID pParam)
          {
           // 試圖進入信號量關口
           g_clsSemaphore.Lock();
           // 線程任務處理
           AfxMessageBox("線程一正在執行!");
           // 釋放信號量計數
           g_clsSemaphore.Unlock();
           return 0;
          }
          UINT ThreadProc25(LPVOID pParam)
          {
           // 試圖進入信號量關口
           g_clsSemaphore.Lock();
           // 線程任務處理
           AfxMessageBox("線程二正在執行!");
           // 釋放信號量計數
           g_clsSemaphore.Unlock();
           return 0;
          }
          UINT ThreadProc26(LPVOID pParam)
          {
           // 試圖進入信號量關口
           g_clsSemaphore.Lock();
           // 線程任務處理
           AfxMessageBox("線程三正在執行!");
           // 釋放信號量計數
           g_clsSemaphore.Unlock();
           return 0;
          }
          ……
          void CSample08View::OnSemaphoreMfc()
          {
           // 開啟線程
           AfxBeginThread(ThreadProc24, NULL);
           AfxBeginThread(ThreadProc25, NULL);
           AfxBeginThread(ThreadProc26, NULL);
          }

            互斥內核對象

            互斥(Mutex)是一種用途非常廣泛的內核對象。能夠保證多個線程對同一共享資源的互斥訪問。同臨界區有些類似,只有擁有互斥對象的線程才具有訪問資源的權限,由于互斥對象只有一個,因此就決定了任何情況下此共享資源都不會同時被多個線程所訪問。當前占據資源的線程在任務處理完后應將擁有的互斥對象交出,以便其他線程在獲得后得以訪問資源。與其他幾種內核對象不同,互斥對象在操作系統中擁有特殊代碼,并由操作系統來管理,操作系統甚至還允許其進行一些其他內核對象所不能進行的非常規操作。為便于理解,可參照圖6給出的互斥內核對象的工作模型:

          圖6 使用互斥內核對象對共享資源的保護
          圖(a)中的箭頭為要訪問資源(矩形框)的線程,但只有第二個線程擁有互斥對象(黑點)并得以進入到共享資源,而其他線程則會被排斥在外(如圖(b)所示)。當此線程處理完共享資源并準備離開此區域時將把其所擁有的互斥對象交出(如圖(c)所示),其他任何一個試圖訪問此資源的線程都有機會得到此互斥對象。

            以互斥內核對象來保持線程同步可能用到的函數主要有CreateMutex()、OpenMutex()、ReleaseMutex()、WaitForSingleObject()和WaitForMultipleObjects()等。在使用互斥對象前,首先要通過CreateMutex()或OpenMutex()創建或打開一個互斥對象。CreateMutex()函數原型為:

          HANDLE CreateMutex(
           LPSECURITY_ATTRIBUTES lpMutexAttributes, // 安全屬性指針
           BOOL bInitialOwner, // 初始擁有者
           LPCTSTR lpName // 互斥對象名
          );

            參數bInitialOwner主要用來控制互斥對象的初始狀態。一般多將其設置為FALSE,以表明互斥對象在創建時并沒有為任何線程所占有。如果在創建互斥對象時指定了對象名,那么可以在本進程其他地方或是在其他進程通過OpenMutex()函數得到此互斥對象的句柄。OpenMutex()函數原型為:

          HANDLE OpenMutex(
           DWORD dwDesiredAccess, // 訪問標志
           BOOL bInheritHandle, // 繼承標志
           LPCTSTR lpName // 互斥對象名
          );

            當目前對資源具有訪問權的線程不再需要訪問此資源而要離開時,必須通過ReleaseMutex()函數來釋放其擁有的互斥對象,其函數原型為:

          BOOL ReleaseMutex(HANDLE hMutex);

            其唯一的參數hMutex為待釋放的互斥對象句柄。至于WaitForSingleObject()和WaitForMultipleObjects()等待函數在互斥對象保持線程同步中所起的作用與在其他內核對象中的作用是基本一致的,也是等待互斥內核對象的通知。但是這里需要特別指出的是:在互斥對象通知引起調用等待函數返回時,等待函數的返回值不再是通常的WAIT_OBJECT_0(對于WaitForSingleObject()函數)或是在WAIT_OBJECT_0到WAIT_OBJECT_0+nCount-1之間的一個值(對于WaitForMultipleObjects()函數),而是將返回一個WAIT_ABANDONED_0(對于WaitForSingleObject()函數)或是在WAIT_ABANDONED_0到WAIT_ABANDONED_0+nCount-1之間的一個值(對于WaitForMultipleObjects()函數)。以此來表明線程正在等待的互斥對象由另外一個線程所擁有,而此線程卻在使用完共享資源前就已經終止。除此之外,使用互斥對象的方法在等待線程的可調度性上同使用其他幾種內核對象的方法也有所不同,其他內核對象在沒有得到通知時,受調用等待函數的作用,線程將會掛起,同時失去可調度性,而使用互斥的方法卻可以在等待的同時仍具有可調度性,這也正是互斥對象所能完成的非常規操作之一。

            在編寫程序時,互斥對象多用在對那些為多個線程所訪問的內存塊的保護上,可以確保任何線程在處理此內存塊時都對其擁有可靠的獨占訪問權。下面給出的示例代碼即通過互斥內核對象hMutex對共享內存快g_cArray[]進行線程的獨占訪問保護。下面給出實現代碼清單:

          // 互斥對象
          HANDLE hMutex = NULL;
          char g_cArray[10];
          UINT ThreadProc18(LPVOID pParam)
          {
           // 等待互斥對象通知
           WaitForSingleObject(hMutex, INFINITE);
           // 對共享資源進行寫入操作
           for (int i = 0; i < 10; i++)
           {
            g_cArray[i] = 'a';
            Sleep(1);
           }
           // 釋放互斥對象
           ReleaseMutex(hMutex);
           return 0;
          }
          UINT ThreadProc19(LPVOID pParam)
          {
           // 等待互斥對象通知
           WaitForSingleObject(hMutex, INFINITE);
           // 對共享資源進行寫入操作
           for (int i = 0; i < 10; i++)
           {
            g_cArray[10 - i - 1] = 'b';
            Sleep(1);
           }
           // 釋放互斥對象
           ReleaseMutex(hMutex);
           return 0;
          }
          ……
          void CSample08View::OnMutex()
          {
           // 創建互斥對象
           hMutex = CreateMutex(NULL, FALSE, NULL);
           // 啟動線程
           AfxBeginThread(ThreadProc18, NULL);
           AfxBeginThread(ThreadProc19, NULL);
           // 等待計算完畢
           Sleep(300);
           // 報告計算結果
           CString sResult = CString(g_cArray);
           AfxMessageBox(sResult);
          }

            互斥對象在MFC中通過CMutex類進行表述。使用CMutex類的方法非常簡單,在構造CMutex類對象的同時可以指明待查詢的互斥對象的名字,在構造函數返回后即可訪問此互斥變量。CMutex類也是只含有構造函數這唯一的成員函數,當完成對互斥對象保護資源的訪問后,可通過調用從父類CSyncObject繼承的UnLock()函數完成對互斥對象的釋放。CMutex類構造函數原型為:

          CMutex( BOOL bInitiallyOwn = FALSE, LPCTSTR lpszName = NULL, LPSECURITY_ATTRIBUTES lpsaAttribute = NULL );

            該類的適用范圍和實現原理與API方式創建的互斥內核對象是完全類似的,但要簡潔的多,下面給出就是對前面的示例代碼經CMutex類改寫后的程序實現清單:

          // MFC互斥類對象
          CMutex g_clsMutex(FALSE, NULL);
          UINT ThreadProc27(LPVOID pParam)
          {
           // 等待互斥對象通知
           g_clsMutex.Lock();
           // 對共享資源進行寫入操作
           for (int i = 0; i < 10; i++)
           {
            g_cArray[i] = 'a';
            Sleep(1);
           }
           // 釋放互斥對象
           g_clsMutex.Unlock();
           return 0;
          }
          UINT ThreadProc28(LPVOID pParam)
          {
           // 等待互斥對象通知
           g_clsMutex.Lock();
           // 對共享資源進行寫入操作
           for (int i = 0; i < 10; i++)
           {
            g_cArray[10 - i - 1] = 'b';
            Sleep(1);
           }
           // 釋放互斥對象
           g_clsMutex.Unlock();
           return 0;
          }
          ……
          void CSample08View::OnMutexMfc()
          {
           // 啟動線程
           AfxBeginThread(ThreadProc27, NULL);
           AfxBeginThread(ThreadProc28, NULL);
           // 等待計算完畢
           Sleep(300);
           // 報告計算結果
           CString sResult = CString(g_cArray);
           AfxMessageBox(sResult);
          }

            小結

            線程的使用使程序處理更夠更加靈活,而這種靈活同樣也會帶來各種不確定性的可能。尤其是在多個線程對同一公共變量進行訪問時。雖然未使用線程同步的程序代碼在邏輯上或許沒有什么問題,但為了確保程序的正確、可靠運行,必須在適當的場合采取線程同步措施。

          posted @ 2007-05-28 15:48 change| 編輯 收藏

          如何使一個類只能夠有一個對象 呢,我們知道這樣的類的構造函數是必須為私有的,否則用戶就可以任意多的生產該類的對象了。那么具體應該怎么實現呢,有兩種方式:一種是給類一個該類型的 static 成員對象,每次都調用一個get方法返回該對象,這也就是所謂的餓漢模式;

          public class EagerSingleton {
           String name;
           private static final EagerSingleton m_instance = new EagerSingleton();
           
           private EagerSingleton()
           {
            name ="wqh";
           }
           public static EagerSingleton getInstance()
           {
            return m_instance;
           }
           
           public static void main(String[] args) {
            EagerSingleton es ;
            es = EagerSingleton.getInstance();
            System.out.println(es.name);
           }
          }

          在有一種呢就是在該類型里面先聲明一個對象,如何通過一個同步的static方法每次返回同一個 對象,這也就是 所謂的懶漢模式,如下:

          public class LazySingleton {
           private static LazySingleton m_instance = null;
           private LazySingleton()
           {
           
           }
           synchronized public  static LazySingleton getInstance()
           {
            if(m_instance == null)
            {
             m_instance = new LazySingleton();
            }
            return m_instance;
           }
           
           public static void main(String[] args) {
            LazySingleton ls;
            ls = LazySingleton.getInstance();
            System.out.println(ls.getClass());
           }
          }

            

          posted @ 2007-05-28 15:47 change| 編輯 收藏

          List (interface)  次序是List 的最重要的特點。它確保維護元素特定的順序。

          ArrayList 是由數組實現的 list , 他容許對元素進行快速的隨機訪問,但是插入與移除 的速度很慢。ListIterator 只應該用來從后往前 遍歷 ArrayList ,而不能夠做移除。

          LinkedList 對順序訪問進行了優化,向List 插入與移除的開銷并不大,隨機訪問則相對較慢。

          Set (interface)存入Set 的每個元素都必須是唯一的,不保存重復元素。加入對象必須定義eqauls()方法保證唯一性。不保證維護元素的次序。

          HsahSet 為快速查找而設計的 Set 存入HashSet的對象必須定義 hsahCode()方法

          TreeSet 保持次序的Set,底層微樹結構,使用他可以從Set中提取有序的序列

          LinkedHashSet 具有hashSet 的查詢速度,且內部使用鏈表維護元素的順序(插入的次序)

          Map(interface) 維護鍵值對的關聯性。

          HashMap 基于散列表的實現(取代HashTable),插入和查詢鍵值對的開銷固定,可以通過構造器設置容量和負載因子以調整容器性能。

          LinkedHashMap 類似HashMap ,但是疊帶遍歷時取得 鍵值對 的順序是插入的次序或者是 最近最少使用的次序。使用鏈表維護內部次序。

          TreeMap基于紅黑樹實現,所得到的結果是經過排序的。

          posted @ 2007-05-28 15:46 change| 編輯 收藏

          摘自:《高質量的 C++編程》 

           

          8.2.1重載與覆蓋
              成員函數被重載的特征:
          1)相同的范圍(在同一個類中);
          2)函數名字相同;
          3)參數不同;
          4)virtual關鍵字可有可無。
              覆蓋是指派生類函數覆蓋基類函數,特征是:
          1)不同的范圍(分別位于派生類與基類);
          2)函數名字相同;
          3)參數相同;
          4)基類函數必須有virtual關鍵字。
           
           
          “隱藏”是指派生類的函數屏蔽了與其同名的基類函數,規則如下:
          1)如果派生類的函數與基類的函數同名,但是參數不同。此時,不論有無virtual關鍵字,基類的函數將被隱藏(注意別與重載混淆)。
          (2)如果派生類的函數與基類的函數同名,并且參數也相同,但是基類函數沒有virtual關鍵字。此時,基類的函數被隱藏(注意別與覆蓋混淆)。
           
           
          如下示例程序中:
          1)函數Derived::f(float)覆蓋了Base::f(float)。
          2)函數Derived::g(int)隱藏了Base::g(float),而不是重載。
          3)函數Derived::h(float)隱藏了Base::h(float),而不是覆蓋。
           
          #include <iostream.h>
              class Base
          {
          public:
              virtual void f(float x){ cout << "Base::f(float) " << x << endl; }
          void g(float x){ cout << "Base::g(float) " << x << endl; }
                      void h(float x){ cout << "Base::h(float) " << x << endl; }
          };
              class Derived : public Base
          {
          public:
              virtual void f(float x){ cout << "Derived::f(float) " << x << endl; }
          void g(int x){ cout << "Derived::g(int) " << x << endl; }
                      void h(float x){ cout << "Derived::h(float) " << x << endl; }
          };

            
          void main(void)
          {
          Derived d;
          Base *pb = &d;
          Derived *pd = &d;
          // Good : behavior depends solely on type of the object
          pb->f(3.14f); // Derived::f(float) 3.14
          pd->f(3.14f); // Derived::f(float) 3.14
           
          // Bad : behavior depends on type of the pointer
          pb->g(3.14f); // Base::g(float) 3.14
          pd->g(3.14f); // Derived::g(int) 3        (surprise!)
           
          // Bad : behavior depends on type of the pointer
          //行為(即方法的調用)依賴于指針的類型
          pb->h(3.14f); // Base::h(float) 3.14      (surprise!)
          pd->h(3.14f); // Derived::h(float) 3.14
          }

           

          posted @ 2007-05-28 15:45 change| 編輯 收藏

          引用就是別名。
          引用的一些規則如下:
          1)引用被創建的同時必須被初始化(指針則可以在任何時候被初始化)。
          2)不能有NULL引用,引用必須與合法的存儲單元關聯(指針則可以是NULL)。

          (3)一旦引用被初始化,就不能改變引用的關系(指針則可以隨時改變所指的對象)。

          “引用傳遞”的性質象“指針傳遞”(能夠改變原來的參數值),而書寫方式象“值傳遞”。

          posted @ 2007-05-28 15:45 change| 編輯 收藏

            最近找工作,幾乎所有的公司有要考C/C++ ,沒有辦法,呵呵~~~~只有慢慢的開始 拾起 C++ 來,好久沒有弄過C++ 了,基本語法都忘得差不多了,呵呵~~~今天看了《高質量的 C++編程》,現摘下一些話,已備忘記查找。

            

          C++內存分配方式
          內存分配方式有三種:
          (1)      從靜態存儲區域分配。內存在程序編譯的時候就已經分配好,這塊內存在程序的整個運行期間都存在。例如全局變量,static變量。
          (2)      在棧上創建。在執行函數時,函數內局部變量的存儲單元都可以在棧上創建,函數執行結束時這些存儲單元自動被釋放。棧內存分配運算內置于處理器的指令集中,效率很高,但是分配的內存容量有限。
          (3)      從堆上分配,亦稱動態內存分配。程序在運行的時候用malloc或new申請任意多少的內存,程序員自己負責在何時用free或delete釋放內存。動態內存的生存期由我們決定,使用非常靈活,但問題也最多。
          EG:
                     用函數返回值來傳遞動態內存
           
          char *GetMemory3(int num)
          {
              char *p = (char *)malloc(sizeof(char) * num);
              return p;
          }
          void Test3(void)
          {
              char *str = NULL;
              str = GetMemory3(100); 
              strcpy(str, "hello");
              cout<< str << endl;
              free(str); 
          }

           但是下面這種事有問題的:

              
          char *GetString(void)
          {
              char p[] = "hello world";
              return p;   // 編譯器將提出警告
          }
          void Test4(void)
          {
          char *str = NULL;
          str = GetString(); // str 的內容是垃圾
          cout<< str << endl;
          }

           

          這里強調不要用return語句返回指向“棧內存”的指針,因為該內存在函數結束時自動消亡,如上面的示例。  用調試器逐步跟蹤Test4,發現執行str = GetString語句后str不再是NULL指針,但是str的內容不是“hello world”而是垃圾。

           

          如果把示例7-4-4改寫成示例7-4-5,會怎么樣?
           

          char *GetString2(void)
          {
              char *p = "hello world";
              return p;
          }
          void Test5(void)
          {
              char *str = NULL;
              str = GetString2();
              cout<< str << endl;
          }

          示例7-4-5 return語句返回常量字符串
           

          函數Test5運行雖然不會出錯,但是函數GetString2的設計概念卻是錯誤的。因為GetString2內的“hello world”是常量字符串,位于靜態存儲區,它在程序生命期內恒定不變。無論什么時候調用GetString2,它返回的始終是同一個“只讀”的內存塊

           

          posted @ 2007-05-28 15:44 change| 編輯 收藏

          在jxta里面,所有的資源都是通過廣告來發布的,這里的服務業不例外,在這里的服務  發布里面有兩個很總要的概念,

          • ModuleClassAdvertisement— defines the service class; its main purpose is to formally document the
          existence of a module class. It is uniquely identified by a ModuleClassID.
          • ModuleSpecAdvertisement — defines a service specification; uniquely identified by a ModuleSpecID.
          Its main purpose is to provide references to the documentation needed in order to create conforming
          implementations of that specification. A secondary use is to make running instances usable remotely,
          by publishing any or all of the following:
          • PipeAdvertisement
          • ModuleSpecID of a proxy module
          • ModuleSpecID of an authenticator module
          • ModuleImplAdvertisement — defines an implementation of a given service specification. 

          這里的 ModuleClassAdvertisement  僅僅用來告知服務的存在,對等點若是需要訪問該服務的話,還需要發現與之關聯的 ModuleSpecAdvertisement  廣告信息。

          而這里的 ModuleSpecAdvertisement  則包含了 對等點節點 要訪問該服務所需要的所有相關信息,比如:管道廣告信息,通過它才能夠連接上所需要的服務。

          服務端的代碼示例大抵如下:

          創建發布 ModuleClassAdvertisement  :

          ModuleClassAdvertisement mcadv = (ModuleClassAdvertisement)AdvertisementFactory.newAdvertisement(ModuleClassAdvertisement.getAdvertisementType());
             mcadv.setName("JXTAMOD:JXTA-EX1");
             mcadv.setDescription("Tutorial example to use JXTA module advertisement Framework");
             ModuleClassID mcID = IDFactory.newModuleClassID();
             mcadv.setModuleClassID(mcID);//通過mcID來建立ModuleClassAdvertisement 與ModuleSpecAdvertisement 的聯系

          discovery.publish(mcadv);
           discovery.remotePublish(mcadv);

          創建發布 ModuleSpecAdvertisement :

          ModuleSpecAdvertisement mdadv = (ModuleSpecAdvertisement)AdvertisementFactory.newAdvertisement(ModuleSpecAdvertisement.getAdvertisementType());

          mdadv.setName("JXTASPEC:JXTA-EX1");
             mdadv.setVersion("Version 1.0");
             mdadv.setCreator("sun.com");
             mdadv.setModuleSpecID(IDFactory.newModuleSpecID(mcID));
             mdadv.setSpecURI("http://www.jxta.org/Ex1");

          PipeAdvertisement pipeadv = null;
             try {
              FileInputStream is = new FileInputStream("pipeserver.adv");
              pipeadv = (PipeAdvertisement)AdvertisementFactory.newAdvertisement(MimeMediaType.XMLUTF8, is);
              is.close();
             } catch (Exception e) {
              System.out.println("failed to read/parse pipe advertisement");
             }

          mdadv.setPipeAdvertisement(pipeadv);

          discovery.publish(mdadv);
           discovery.remotePublish(mdadv);
            myPipe = pipes.createInputPipe(pipeadv);

          在客戶端,通過不斷的查找廣告(分本地查找和遠端查找)來 獲取 所需要服務的廣告信息,通過它就可以獲取 管道信息 來創建管道以達到通訊的目的。客戶端代碼示例大抵如下:

          Enumeration en = null;
            while (true) {
             try {
              /* let's look first in our local cache to see if we have it! We try to discover an adverisement which as the (Name, JXTA-EX1) tag value
              en = discovery.getLocalAdvertisements(DiscoveryService.ADV,"Name","JXTASPEC:JXTA-EX1");
              //  Ok we got something in our local cache does not
              //  need to go further!
              if ((en != null) && en.hasMoreElements()) {
               break;
              }
              //  nothing in the local cache?, let's remotely query
              //  for the service advertisement.
              discovery.getRemoteAdvertisements(null,DiscoveryService.ADV,"Name","JXTASPEC:JXTA-EX1",1, null);
              //  The discovery is asynchronous as we do not know
              //  how long is going to take
              try { // sleep as much as we want. Yes we
               //  should implement asynchronous listener pipe...
               Thread.sleep(2000);
              } catch (Exception e) {}
             } catch (IOException e) {
              //  found nothing! move on
             }
             System.out.print(".");
            }
            System.out.println("we found the service advertisement");
            //  Ok get the service advertisement as a Spec Advertisement
            ModuleSpecAdvertisement mdsadv = (ModuleSpecAdvertisement)en.nextElement();
            try {
             //  let's print the advertisement as a plain text document
             StructuredTextDocument doc = (StructuredTextDocument)mdsadv.getDocument(MimeMediaType.TEXT_DEFAULTENCODING);
             StringWriter out = new StringWriter();
             doc.sendToWriter(out);
             System.out.println(out.toString());
             out.close();
             //  we can find the pipe to connect to the service
             //  in the advertisement.
             PipeAdvertisement pipeadv = mdsadv.getPipeAdvertisement();
             //  Ok we have our pipe advertiseemnt to talk to the service
             //  create the output pipe endpoint to connect  to the server
              myPipe = pipes.createOutputPipe(pipeadv, 10000);
             }

             //  send the message to the service pipe
             myPipe.send (msg);

          posted @ 2007-05-28 15:41 change| 編輯 收藏

          JXTA 雙向通訊 可以通過 JxtaServerSocket /JxtaSocket和 JxtaServerPipe/JxtaBiDiPipe 來實現 其實現的過程非常的類是我們做FTP的時候所采用的ServerSocket/Socket機制,也就是服務斷監聽客戶端連接的原理。以JxtaServerPipe為例,在服務端:

          serverPipe = new JxtaServerPipe(eg.netPeerGroup,eg.pipeAdv);

          serverPipe.setPipeTimeout(0);然后就是服務端的循環監聽客戶端的連接

          while (true) {
             try {
              JxtaBiDiPipe bipipe = serverPipe.accept();
              if (bipipe != null ) {
               System.out.println("JxtaBidiPipe accepted,sending 100 messages to the other end");
               //Send a 100 messages
               sendTestMessages(bipipe);
              }
             } catch (Exception e) {
               }
            }

          在客戶端則是通過JxtaBiDiPipe 來進行連接服務斷的操作:pipe = new JxtaBiDiPipe();

          pipe.connect(eg.netPeerGroup,null,eg.pipeAdv,
              180000,
              // register as a message listener
              eg);當有消息來得時候就會觸發 pipeMsgEvent(PipeMsgEvent event)事件

          posted @ 2007-05-28 15:41 change| 編輯 收藏

          最近在JXTA的官方網站上面下載了一份JxtaProgGuide看了看,練習了一下上面的示例程序~~~~大抵上感覺的編程的模式就是:

          //Method to start the JXTA platform.

          NetPeerGroupFactory factory  = new NetPeerGroupFactory();//這是默認的創建的一個組。
                  netPeerGroup = factory.getInterface(); 

          然后就是獲取相應的服務如發現服務(用于發現和發布廣告,那么什么是廣告呢?

          Advertisements 就是:
          All JXTA network resources— such as peers, peer groups, pipes, and services —are represented by an
          advertisement. Advertisements are language-neutral meta-data structures represented as XML documents. The
          JXTAprotocols use advertisements to describe and publish the existence of a peer resources. Peers discover
          resources by searching for their corresponding advertisements, and may cache any discovered advertisements
          locally.),管道服務(用于創建IN/OUT管道來接發消息,這里創建OutPipe管道會觸發outputPipeEvent(OutputPipeEvent event) 事件,而當 Inpipe 管道有消息到來的時候會觸發pipeMsgEvent(PipeMsgEvent event)事件 ,而這里In/Out 管道間的聯系則就是廣告的用處了,它通過PipeID標示出所用的管道來建立他們之間的聯系而不至于混亂。對等點間的通訊就要依賴于它了)

          discovery = netPeerGroup.getDiscoveryService();
           rdv = netPeerGroup.getRendezVousService();

          然后是通過所獲取的服務來注冊監聽器在通過發現事件來獲取一個廣告,或者是直接通過服務來獲取一個廣告,總之目的就是要獲取一個所要找的廣告 。如監聽:

          discovery.addDiscoveryListener(this);此時需要implements DiscoveryListener接口,

          實現里面的 discoveryEvent(DiscoveryEvent ev) 方法,然后通過 DiscoveryEvent  獲取廣告

          DiscoveryResponseMsg res = ev.getResponse();
            // Get the responding peer's advertisement
            PeerAdvertisement peerAdv = res.getPeerAdvertisement();

          或者是直接通過服務來獲取一個廣告

          discovery.getRemoteAdvertisements(null, DiscoveryService.GROUP, null, null, 5);

          在要不就是i通過一個發現服務來發布一個廣告,這里的發布廣告分本地發布和遠程發布

          discoveryService.publish(Adv,PeerGroup.DEFAULT_LIFETIME,PeerGroup.DEFAULT_EXPIRATION);
           discoveryService.remotePublish(Adv,PeerGroup.DEFAULT_EXPIRATION);

           那么一個對等點如何才能夠加入一個Group呢

          StructuredDocument creds = null;

          // Generate the credentials for the Peer Group
             AuthenticationCredential authCred = new AuthenticationCredential( grp, null, creds );
             // Get the MembershipService from the peer group
             MembershipService membership = grp.getMembershipService();
             // Get the Authenticator from the Authentication creds
             Authenticator auth = membership.apply( authCred );
             // Check if everything is okay to join the group
             if (auth.isReadyForJoin()){
              Credential myCred = membership.join(auth);
              System.out.println("Successfully joined group " + grp.getPeerGroupName());
              // display the credential as a plain text document.
              System.out.println("\nCredential: ");
              StructuredTextDocument doc = (StructuredTextDocument)myCred.getDocument(new MimeMediaType("text/plain"));
              StringWriter out = new StringWriter();
              doc.sendToWriter(out);
              System.out.println(out.toString());
              out.close();
             }

           

          posted @ 2007-05-28 15:40 change| 編輯 收藏

          最近也算是閑來無事, 于是乎開始玩玩 J2ME 無線編程,找了一本書翻翻,然后下載了一個 ME插件和諾基亞的模擬器 做了幾個小例子,發覺其實也沒有什么,感覺基本上可以說是windows 窗口編成的一個縮版(新手愚見,高手見效了)。就是所謂的添加一個form(類是面板),在給他添加幾個TextField(類是文本框),添加相應的響應事件,然后就是在不同的現實面板間切換,做一些業務上的事情,至于手機上的存儲嘛,基本上是依賴于 DataStore 這個類的,用它獲取recordID在獲取記錄等等,也就是通常的數據庫操作(增刪改查)都依賴于它。至于手機的聯網通訊則是依賴于Connector這個對象了,通過它 既可以創建一個Socket連接,也可以創建一個HTTP連接,只是連接的URL 字符串不同罷了。如:

          conn =  (HttpConnection)Connector.open(URL.toString());

          想想它的難點的話應該是如何的精簡代碼,高效的利用存儲空間,和網絡通訊的安全吧。因為這畢竟是一個手持設備的局限性問題。這方面就確實沒有什么經驗了,希望有高手的經驗共享。呵呵~~~我只是感覺她的編程模型還是蠻好理解的。沒有什么神秘可言。我翻的那本書比較的老了,也許現在的MIDP2.0 已經有了很大的改觀也說不來噢,個人沒有怎么了解。不過在J2ME的開發包里面有好多的Demo,但是現在是沒有什么時間去研究它了,呵呵~~~以后再說吧,歡迎大家批評指正。

          posted @ 2007-05-28 15:40 change| 編輯 收藏

          以前的同步操作 基本上都是用到 synchronised 關鍵字,類似代碼如下:

          synchronised(obj){

          //dosomething...

          }來做到同步,

          在 JDK5.0  里面有這么一個對象,ReentrantLock,發覺她的加鎖編程的方式非常的適合日常的加鎖習慣,

          EG:

          package com.thread.synchronise;

          import java.util.concurrent.locks.ReentrantLock;

          public class SynchroTest extends Thread{
           private int count = 0;
           private final ReentrantLock lock = new ReentrantLock();
           
           public void run()
           {

          //這里加了幾次鎖,在后面就的要相應的解鎖 幾次
               
          lock.lock();  // block until condition holds
                try {      
                 count++;
                 System.out.println(" count = "+count);
                 try {
              Thread.sleep(3000);
             } catch (InterruptedException e) {
              e.printStackTrace();
             }
                 System.out.println(" count = "+count);
                } finally {
                
          lock.unlock();
                }
           }
           /**
            * @param args
            */
           public static void main(String[] args) {
            // TODO Auto-generated method stub
            SynchroTest st1 = new SynchroTest();
          //  SynchroTest st2 = new SynchroTest();
          //  SynchroTest st3 = new SynchroTest();
            

          //這里不能夠調用   new Thread(st1).run();方法,否則就不是多線程的了
            new Thread(st1).start();
            new Thread(st1).start();
            new Thread(st1).start();
           }

          }

          如果該線程等待某暫時獲取不到的資源,那么我們可以用Condition Object來避免死鎖情況。
          sufficientFunds = lock .newCondition();
          如果條件不滿足:
          sufficientFunds.await();
          這時線程就會釋放鎖并進入blocked狀態,其他線程就有機會執行操作。當其他線程執行完后,就可通知等待的線程繼續執行它的操作了:
          sufficientFunds.signalAll();


          posted @ 2007-05-28 15:39 change| 編輯 收藏

          //解決二次提交問題(提交前)

                    preSubmitValid(servletRequest,servletResponse);
                    
          //解決二次提交問題(提交)
              if(!submitValid(servletRequest,servletResponse))
               try
                        {
                         servletResponse.sendRedirect("public/repeatdeal.jsp");
                         return null;
                        }
                        catch (Exception error)
                        {
                         servletRequest.setAttribute("errorMSG", "重復提交造成頁面跳轉出錯:" + error.getMessage());
                        }   
                       
          /**
                   * 解決二次提交問題(提交前)

                   * @param request
                   * @param response
                   */
                  public void preSubmitValid(HttpServletRequest servletRequest,HttpServletResponse response)
                  {
                   counter = -1;
                      servletRequest.getSession().setAttribute("submissioncount",
                              new Integer(counter));
                      /**
                       * 重要:

                       * 通過調用 saveToken(request)方法,動態生成一個token,并且存放到session中,
                       * 以便在以后可以在動態生成的頁面中加入隱藏字段 <input type="hidden" name="org.apache.struts.taglib.html.TOKEN" value="動態值">
                       * 只要調用了該方法,此后包含<html:form...>標簽的頁面中都會動態生成上面所說的隱藏字段。

                       */
                      this.saveToken(servletRequest);         
                  }
                  /**
                   * 提交驗證,檢驗是否是重復提交,如果重復提交跳轉到統一處理頁面
                   * @param servletRequest
                   * @param servletResponse
                   * @return
                   */
                  public boolean submitValid(HttpServletRequest servletRequest,HttpServletResponse servletResponse)
                  {
                   counter += 1;
                   servletRequest.getSession().setAttribute("submissioncount",new Integer(counter));
                      if (!this.isTokenValid(servletRequest))
                       return false;

                      /**
                       * 在認可了用戶的合法提交后,一定要調用resetToken(request)重置token,這樣session中就沒有相應的token啦

                       * 這樣才能夠保證用戶再次提交相應數據時,能夠檢測出來。

                       */
                      this.resetToken(servletRequest);
                      return true;         
                  }                      

          posted @ 2007-05-28 15:38 change| 編輯 收藏

          以前用得的是hibernate3.0.5的版本~~~~而且關于queryfactory 的配置如下:

          <prop key="hibernate.query.factory_class">org.hibernate.hql.classic.ClassicQueryTranslatorFactory</prop>

          因為數據量超出了10萬條~~~~結果出現了 heap 溢出問題,想想了,也確實該溢出了,呵呵~~~

          這種查詢方式,是將所有的查詢出來的結果以對象的形式進行緩存,如此巨大的數據,不把她給稱爆炸才怪呢:)

          查查hibernate 的文檔,有關于大數據量的處理~~~,流程大抵如下:

          Transaction tx = session.beginTransaction();

                  String hqlDelete = "delete VbufferGis ";
                  int deletedEntities = session.createQuery( hqlDelete ).executeUpdate();
                  tx.commit();
                  session.close();

          測試運行出現如下異常:query must begin with SELECT or FROM ,文檔是清清楚楚是這樣寫的嘛,怎么會出現這樣的問題呢,是不是和我剛開始的時候一樣覺得挺納悶的,呵呵~~~原來是配置的問題,將上面的配置改為:

          <prop key="hibernate.query.factory_class">org.hibernate.hql.ast.ASTQueryTranslatorFactory</prop>

          本以為這下是萬事大吉了,呵呵~~~~問題有出來了,define class not foundexception :antlr.antlrexception

          在網上授了一把,原來hibernate用她來解析 hql ,而我用myEclipse的時候,有沒有導入那個包,自然有問題了,

          于是將那個包導入,測試刪除運行,一切ok!這下是真的萬事大吉了嗎?還沒有,這也難怪我的多磨難了,呵呵

          原來在進行待漢字的參數查詢的時候出現了亂碼現象,感覺挺奇怪的,百思不得其解,幸好有網絡這個好東西,google了一下,^_^ 原來值需要換一個版本就ok了,呵呵~~~于是在取sourceforge上面取下了 hibernate-3.1rc2.zip,這下子就ok了!一切運行正常!!!雖然問題是解決了,原理缺不甚明白,有待學習。。。。

          posted @ 2007-05-28 15:37 change| 編輯 收藏

          在ApplicationContext.xml 里面的配置:


          <!-- begin day -->

          <bean id="Initlogdatatarget" class="com.sotrip.statistic.scheduling.Initlogdata">
           <property name="tlogvisitDAO"><ref local="tlogvisitDAO"/></property>
           </bean>
          <bean id="Jobfortimerdaysservice"
               class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
           <property name="transactionManager"><ref local="transactionManager"/></property>
           <property name="target"><ref local="Initlogdatatarget"/></property>
           <property name="transactionAttributes">
            <props>  
             <prop key="exec*">PROPAGATION_REQUIRED</prop>
            </props>
           </property>
          </bean>

          <bean id="methodInvokingJobDetail"
           class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"> <!--**** 此處的 targetObject 所指定的bean Jobfortimerdaysservice 需是service層的,通過它定義的事務屬性 就可以使得 targetMethod 所定義的方法具有事務屬性。-->
           <property name="targetObject"><ref bean="Jobfortimerdaysservice"/></property>
           <property name="targetMethod"><value>execute</value></property>
          </bean>

          <bean id="cronTrigger"
           class="org.springframework.scheduling.quartz.CronTriggerBean">
           <property name="jobDetail">
            <ref bean="methodInvokingJobDetail"/>
           </property>
           <property name="cronExpression">
            <value>0 0/2 * * * ?</value>
           </property>
          </bean>

          <!-- end day-->

          <bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
           <property name="triggers">
            <list>
          <!--  <ref local="cronTriggertest"/>-->
             <ref local="cronTrigger"/>
              <ref local="cronTriggermonth"/><!-- 此處可以配置多個trigger-->
            </list>
           </property>
          </bean>

          在就是定時時間的定義:

          Cron-Expressions are used to configure instances of CronTrigger. Cron-Expressions are strings that are actually made up of seven sub-expressions, that describe individual details of the schedule. These sub-expression are separated with white-space, and represent:

          1. Seconds
          2. Minutes
          3. Hours
          4. Day-of-Month
          5. Month
          6. Day-of-Week
          7. Year (optional field)

          An example of a complete cron-expression is the string "0 0 12 ? * WED" - which means "every Wednesday at 12:00 pm".

          cronExpression配置說明

          字段   允許值   允許的特殊字符
            0-59   , - * /
            0-59   , - * /
          小時   0-23   , - * /
          日期   1-31   , - * ? / L W C
          月份   1-12 或者 JAN-DEC   , - * /
          星期   1-7 或者 SUN-SAT   , - * ? / L C #
          年(可選)   留空, 1970-2099   , - * /

           


          posted @ 2007-05-28 15:37 change| 編輯 收藏

          在spring里面我們一般是這樣來使用模板模式的:

          JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
          jdbcTemplate.update("UPDATE user SET age = 10 WHERE id = 'erica'");

          或者:

          JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
          jdbcTemplate
          .update(
          "UPDATE user SET age = ? WHERE id = ?",
          new PreparedStatementSetter() {
          public void setValues(PreparedStatementSetter ps)
          throws SQLException {
          ps.setInt(1, 18);
          ps.setString(2, "erica");
          }
          }
          );

          那么具體在spring里面他是怎么運作的呢?

          下面以query查詢為例:

          public class JdbcTemplate extends JdbcAccessor implements JdbcOperations, InitializingBean {

          。。。。。。。。。。。。。。。。。。。。。。。。。

          protected Object query(
             PreparedStatementCreator psc, final PreparedStatementSetter pss, final ResultSetExtractor rse)
             throws DataAccessException {
            if (logger.isDebugEnabled()) {
             String sql = getSql(psc);
             logger.debug("Executing SQL query" + (sql != null ? " [" + sql  + "]" : ""));
            }
            return execute(psc, new PreparedStatementCallback() {

          //此處以 PreparedStatementCallback 為參數調用 execute()方法,在execute()方法里面回調傳入的方法。在回調方法里面即根據傳入的 PreparedStatement 執行 查詢操作,返回結果。而 PreparedStatement  的獲取是在調用回調方法的客戶端實現即在execute()方法里面獲取,并作為參數傳給回調方法。
             public Object doInPreparedStatement(PreparedStatement ps) throws SQLException {
              ResultSet rs = null;
              try {
               if (pss != null) {
                pss.setValues(ps);
               }
               if (getFetchSize() > 0) {
                ps.setFetchSize(getFetchSize());
               }
               rs = ps.executeQuery();
               ResultSet rsToUse = rs;
               if (nativeJdbcExtractor != null) {
                rsToUse = nativeJdbcExtractor.getNativeResultSet(rs);
               }
               return rse.extractData(rsToUse);
              }
              finally {
               JdbcUtils.closeResultSet(rs);
               if (pss instanceof ParameterDisposer) {
                ((ParameterDisposer) pss).cleanupParameters();
               }
              }
             }
            });
           }

          那么在execue()方法里面是怎樣回調的呢?下面看看execue()方法:

          public Object execute(PreparedStatementCreator psc, PreparedStatementCallback action) {
            Connection con = DataSourceUtils.getConnection(getDataSource());
            PreparedStatement ps = null;
            try {
             Connection conToUse = con;
             if (this.nativeJdbcExtractor != null &&
               this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativePreparedStatements()) {
              conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
             }
             ps = psc.createPreparedStatement(conToUse);
             DataSourceUtils.applyTransactionTimeout(ps, getDataSource());
             PreparedStatement psToUse = ps;
             if (this.nativeJdbcExtractor != null) {
              psToUse = this.nativeJdbcExtractor.getNativePreparedStatement(ps);
             }
             Object result = action.doInPreparedStatement(psToUse);
             SQLWarning warning = ps.getWarnings();
             throwExceptionOnWarningIfNotIgnoringWarnings(warning);
             return result;
            }
            catch (SQLException ex) {
             throw getExceptionTranslator().translate(
               "executing PreparedStatementCallback [" + psc + "]", getSql(psc), ex);
            }
            finally {
             if (psc instanceof ParameterDisposer) {
              ((ParameterDisposer) psc).cleanupParameters();
             }
             JdbcUtils.closeStatement(ps);
             DataSourceUtils.closeConnectionIfNecessary(con, getDataSource());
            }
           }

          添加刪除的操作類似,只是他們的實現都在以execute命名的方法里面。

          public void execute(final String sql) throws DataAccessException {
            if (logger.isDebugEnabled()) {
             logger.debug("Executing SQL statement [" + sql + "]");
            }
            class ExecuteStatementCallback implements StatementCallback, SqlProvider {
             public Object doInStatement(Statement stmt) throws SQLException {
              stmt.execute(sql);
              return null;
             }
             public String getSql() {
              return sql;
             }
            }
            execute(new ExecuteStatementCallback());
           }

          public Object execute(final StatementCallback action) {
            Connection con = DataSourceUtils.getConnection(getDataSource());
            Statement stmt = null;
            try {
             Connection conToUse = con;
             if (this.nativeJdbcExtractor != null &&
               this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativeStatements()) {
              conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
             }
             stmt = conToUse.createStatement();
             DataSourceUtils.applyTransactionTimeout(stmt, getDataSource());
             Statement stmtToUse = stmt;
             if (this.nativeJdbcExtractor != null) {
              stmtToUse = this.nativeJdbcExtractor.getNativeStatement(stmt);
             }
             Object result = action.doInStatement(stmtToUse);
             SQLWarning warning = stmt.getWarnings();
             throwExceptionOnWarningIfNotIgnoringWarnings(warning);
             return result;
            }
            catch (SQLException ex) {
             throw getExceptionTranslator().translate("executing StatementCallback", getSql(action), ex);
            }
            finally {

          //這里就是我們自己寫程序的時候需要寫的關于數據庫鏈接的關閉操作
             JdbcUtils.closeStatement(stmt);
             DataSourceUtils.closeConnectionIfNecessary(con, getDataSource());
            }
           }

          posted @ 2007-05-28 15:34 change| 編輯 收藏

          Spring 的聲明式事務是通過TransactionProxyFactoryBean 來實現的,而它是通過持有一個攔截器:TransactionInterceptor 來做到的。

          public class TransactionProxyFactoryBean extends ProxyConfig implements FactoryBean, InitializingBean {

           private final TransactionInterceptor transactionInterceptor = new TransactionInterceptor();

           /**
            * Set the transaction manager. This will perform actual
            * transaction management: This class is just a way of invoking it.
            * @see TransactionInterceptor#setTransactionManager
            */
           public void setTransactionManager(PlatformTransactionManager transactionManager) {
            this.transactionInterceptor.setTransactionManager( transactionManager);
           }

          。。。。。。。。。。//聲明的事務屬性在這里得到                                   

                                                 //見(DefaultTransactionDefinition)定義

          }

          //TransactionInterceptor 在service層的方法調用的時候,會更具配置判斷調用的方法//是否需要事務的處理,若需要則獲取設置事務屬性對象和事務管理器并啟動一個//事務,而其具體的實現是委托給 TransactionAspectSupport 類的//createTransactionIfNecessary 方法實現的,其類結構如下

          public class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor {

           public Object invoke(MethodInvocation invocation) throws Throwable {
            // Work out the target class: may be null.
            // The TransactionAttributeSource should be passed the target class
            // as well as the method, which may be from an interface
            Class targetClass = (invocation.getThis() != null) ? invocation.getThis().getClass() : null;
            
            // Create transaction if necessary
            TransactionInfo txInfo = createTransactionIfNecessary(invocation.getMethod(), targetClass);//此處即是根據配置的聲明性事務屬性決定方法事務級別

            Object retVal = null;
            try {
             // This is an around advice.
             // Invoke the next interceptor in the chain.
             // This will normally result in a target object being invoked.
             retVal = invocation.proceed();
            }
            catch (Throwable ex) {
             // target invocation exception
             doCloseTransactionAfterThrowing(txInfo, ex);
             throw ex;
            }
            finally {
             doFinally(txInfo);
            }
            doCommitTransactionAfterReturning(txInfo);

            return retVal;
           }
           
          }

          //在TransactionAspectSupport 類方法createTransactionIfNecessary()里面根據配置的聲明性事務屬性,決定啟動一個事務,和返回事務級別(信息):

          protected TransactionInfo createTransactionIfNecessary(Method method, Class targetClass) {
            // If the transaction attribute is null, the method is non-transactional
            TransactionAttribute transAtt = this.transactionAttributeSource.getTransactionAttribute(method, targetClass);
            TransactionInfo txInfo = new TransactionInfo(transAtt, method);
            if (transAtt != null) {
             // We need a transaction for this method
             if (logger.isDebugEnabled()) {
              logger.debug("Getting transaction for " + txInfo.joinpointIdentification());
             }

             // The transaction manager will flag an error if an incompatible tx already exists

             txInfo.newTransactionStatus(this.transactionManager.getTransaction(transAtt));

          //此處的參數 this.transactionManager.getTransaction(transAtt) 即是調用各具體平臺的 transactionManager 來獲取她的事務屬性,在獲取事務屬性的同時她會更具具體的事務屬性 來決定是否開始和怎么開始一個事務;見類AbstractPlatformTransactionManager  結構。

           public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable {

          。。。。。。。。。。。。。。。。。。。。。

          public final TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException {

          if (isExistingTransaction(transaction)) {

          //下面即是 更具具體的配置事務屬性 來決定事務
             if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER)
          {
              throw new IllegalTransactionStateException(
                "Transaction propagation 'never' but existing transaction found");
             }
             if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {
              if (debugEnabled) {
               logger.debug("Suspending current transaction");
              }
              Object suspendedResources = suspend(transaction);
              boolean newSynchronization = (this.transactionSynchronization == SYNCHRONIZATION_ALWAYS);
              return newTransactionStatus(
                null, false, newSynchronization, definition.isReadOnly(), debugEnabled, suspendedResources);
             }
             else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {
              if (debugEnabled) {
               logger.debug("Creating new transaction, suspending current one");
              }
              Object suspendedResources = suspend(transaction);

          //此處的doBegin 方法給更具具體的平臺和配置事務屬性來啟動一個事務
              doBegin(transaction, definition);
              boolean newSynchronization = (this.transactionSynchronization != SYNCHRONIZATION_NEVER);
              return newTransactionStatus(
                transaction, true, newSynchronization, definition.isReadOnly(), debugEnabled, suspendedResources);
             }

          。。。。。。。。。。。。。。。。。。。。。

          }

            // We always bind the TransactionInfo to the thread, even if
            // we didn't create a new transaction here.
            // This guarantees that the TransactionInfo stack will be
            // managed correctly even if no transaction was created by
            // this aspect.
            txInfo.bindToThread();
            return txInfo;
           }

          //下面是事務的提交操作,回滾類似

          protected void doCommitTransactionAfterReturning(TransactionInfo txInfo) {
            if (txInfo != null && txInfo.hasTransaction()) {
             if (logger.isDebugEnabled()) {
              logger.debug("Invoking commit for transaction on " + txInfo.joinpointIdentification());
             }

            //這里的transactionManager 就是Spring配置文件里面配置的事務 如:org.springframework.orm.hibernate3.HibernateTransactionManager 。
             this.transactionManager.commit(txInfo.getTransactionStatus());

            }
           }

          //

          protected void doFinally(TransactionInfo txInfo) {
            if (txInfo != null) {
             txInfo.restoreThreadLocalStatus();
            }
           }
          private void restoreThreadLocalStatus() {
             // Use stack to restore old transaction TransactionInfo.
             // Will be null if none was set.
             currentTransactionInfo.set(oldTransactionInfo);
            }

          //下面以 HibernateTransactionManager  例,說說事務的開始和提交/回滾

          //此dobegin()方法即是開始一個事務
           protected void doBegin(Object transaction, TransactionDefinition definition) {
            HibernateTransactionObject txObject = (HibernateTransactionObject) transaction;

            if (txObject.getSessionHolder() == null) {
             Session session = SessionFactoryUtils.getSession(
               getSessionFactory(), getEntityInterceptor(), getJdbcExceptionTranslator(), false);
             if (logger.isDebugEnabled()) {
              logger.debug("Opened new session [" + session + "] for Hibernate transaction");
             }
             txObject.setSessionHolder(new SessionHolder(session), true);
            }

            txObject.getSessionHolder().setSynchronizedWithTransaction(true);
            Session session = txObject.getSessionHolder().getSession();

            try {
             Connection con = session.connection();
             Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
             txObject.setPreviousIsolationLevel(previousIsolationLevel);

             if (definition.isReadOnly() && txObject.isNewSessionHolder()) {
              // just set to NEVER in case of a new Session for this transaction
              session.setFlushMode(FlushMode.NEVER);
             }

             if (!definition.isReadOnly() && !txObject.isNewSessionHolder()) {
              // we need AUTO or COMMIT for a non-read-only transaction
              FlushMode flushMode = session.getFlushMode();
              if (FlushMode.NEVER.equals(flushMode)) {
               session.setFlushMode(FlushMode.AUTO);
               txObject.getSessionHolder().setPreviousFlushMode(flushMode);
              }
             }

             // add the Hibernate transaction to the session holder

          //此處即是真正的調用了hibernate的session開始一個事務session.beginTransaction() 。
             txObject.getSessionHolder().setTransaction(session.beginTransaction());

             // register transaction timeout
             if (definition.getTimeout() != TransactionDefinition.TIMEOUT_DEFAULT) {
              txObject.getSessionHolder().setTimeoutInSeconds(definition.getTimeout());
             }

             // register the Hibernate Session's JDBC Connection for the DataSource, if set
             if (getDataSource() != null) {
              ConnectionHolder conHolder = new ConnectionHolder(con);
              if (definition.getTimeout() != TransactionDefinition.TIMEOUT_DEFAULT) {
               conHolder.setTimeoutInSeconds(definition.getTimeout());
              }
              if (logger.isDebugEnabled()) {
               logger.debug("Exposing Hibernate transaction as JDBC transaction [" +
                 conHolder.getConnection() + "]");
              }
              TransactionSynchronizationManager.bindResource(getDataSource(), conHolder);
              txObject.setConnectionHolder(conHolder);
             }

             // bind the session holder to the thread
             if (txObject.isNewSessionHolder()) {
              TransactionSynchronizationManager.bindResource(getSessionFactory(), txObject.getSessionHolder());
             }
            }

            catch (Exception ex) {
             SessionFactoryUtils.closeSessionIfNecessary(session, getSessionFactory());
             throw new CannotCreateTransactionException("Could not create Hibernate transaction", ex);
            }
           }

          //回滾

          protected void doCommit(DefaultTransactionStatus status) {
            HibernateTransactionObject txObject = (HibernateTransactionObject) status.getTransaction();

          。。。。。。。。。。。。。。。。。。。。。。。。。。
            try {
             txObject.getSessionHolder().getTransaction().commit();
            }
            catch (net.sf.hibernate.TransactionException ex) {
             // assumably from commit call to the underlying JDBC connection
             throw new TransactionSystemException("Could not commit Hibernate transaction", ex);
            }
            catch (JDBCException ex) {
             // assumably failed to flush changes to database
             throw convertJdbcAccessException(ex.getSQLException());
            }
            catch (HibernateException ex) {
             // assumably failed to flush changes to database
             throw convertHibernateAccessException(ex);
            }
           }

            }
            else {
             // The TransactionInfo.hasTransaction() method will return
             // false. We created it only to preserve the integrity of
             // the ThreadLocal stack maintained in this class.
             if (logger.isDebugEnabled())
              logger.debug("Don't need to create transaction for " + methodIdentification(method) +
                ": this method isn't transactional");
            }

          posted @ 2007-05-28 15:33 change| 編輯 收藏

          引用:http://shidu.blogbus.com/logs/2004/12/540365.html

          配置文件大概如下:
          <action-mappings>
          <action path="/account"
          name="accountForm"
          parameter="action"
          scope="request"
          type="com.ai.uaap.admin.web.action.AccountAction">
          <forward name="listOK" path="/AccountMaster.jsp" />
          <forward name="removeOK" path="/account.do?action=list" redirect="true" />
          </action>

          我在執行完remove的方法之后的代碼是return mapping.findForward("removeOK")。這時就會訪問account.do?action=list這個地址,以前我想在account.do?action=list之后繼續添加參數(例如account.do?action=list&abc=123)不知道該怎么實現。

          今天看到一個資料給出了實現:
          String path = mapping.findForward("removeOK").getPath();
          ActionForward forward = new ActionForward(path + "&abc=123", true);
          //這里的true是Redirect
          return forward;

          posted @ 2007-05-28 15:32 change| 編輯 收藏

          CREATE TABLE V_GIS_MONTH
          (
            ID         VARCHAR2(255 BYTE)                 NOT NULL,
            VISITTIME  DATE                               DEFAULT NULL,
            GISNAME    VARCHAR2(255 BYTE),
            GISLEVEL   VARCHAR2(255 BYTE),
            PARENT     VARCHAR2(255 BYTE),
            VNUMBER    NUMBER(19),
            IPFROM     VARCHAR2(255 BYTE),
            IPLEVEL    VARCHAR2(2 BYTE),
            GISPATH    VARCHAR2(1024 BYTE)
          )
          TABLESPACE USERS

          PCTUSED    0
          PCTFREE    10
          INITRANS   1
          MAXTRANS   255
          STORAGE    (
                      INITIAL          64K
                      MINEXTENTS       1
                      MAXEXTENTS       2147483645
                      PCTINCREASE      0
                      BUFFER_POOL      DEFAULT
                     )
          LOGGING
          NOCACHE
          NOPARALLEL;


          ALTER TABLE V_GIS_MONTH ADD (
            PRIMARY KEY (ID)
              USING INDEX
              TABLESPACE USERS
              PCTFREE    10
              INITRANS   2
              MAXTRANS   255
              STORAGE    (
                          INITIAL          64K
                          MINEXTENTS       1
                          MAXEXTENTS       2147483645
                          PCTINCREASE      0
                         ));


          select gisdays.ipfrom,gisdays.gisname,sum(gisdays.vnumber)AS count from V_Gis_Month gisdays where gisdays.GISPATH = '/sotrip_dest/destination_home/china/zhejiang/' and gisdays.iplevel='3' group by gisdays.ipfrom,gisdays.gisname ORDER BY count desc;

          gis:表示目的地

          在數據庫里面這樣的查詢是可以的,但是用這樣的語句在hibernate的DAO層

          這樣做:

          String queryString = "select gisdays.ipfrom,sum(gisdays.vnumber) AS count  from "+strtable+" gisdays where gisdays.iplevel='"+striplevel+"' "+strquery+" and to_char(gisdays.visittime,'YYYY-MM-DD')  between '"+strfrom+"' and '"+strto+"' group by gisdays.ipfrom ORDER BY count desc";           
            

          Iterator iterator = this.getHibernateTemplate().find(queryString).iterator();

          是不行的,會出現異常,報錯。

          改為:

          String queryString = "select gisdays.ipfrom,sum(gisdays.vnumber)  from "+strtable+" gisdays where gisdays.iplevel='"+striplevel+"' "+strquery+" and to_char(gisdays.visittime,'YYYY-MM-DD')  between '"+strfrom+"' and '"+strto+"' group by gisdays.ipfrom ORDER BY sum(gisdays.vnumber) desc";           

           Iterator iterator = this.getHibernateTemplate().find(queryString).iterator();

          while(iterator.hasNext())
            {
             Object[] pair = (Object[])iterator.next();   
             VGisMonth gismonth = new VGisMonth();
             for(int i=0;i<pair.length;i++)
             {
              System.out.println("pair["+i+"]="+pair[i]);
             }

          }//這里iterator 所返回的是對象數組,它把取得的每一列都作為一個對象,然后返回一個對象數組的疊代器,然后我們就可以取出里面的每一列(對象)轉換成相應的數據類型,(這里 strtable 是table所對應的實體的名字)

          就可以了。應該是在里面不支持對  統計添加的列 的別名命名吧。呵呵:)

          posted @ 2007-05-28 15:31 change| 編輯 收藏

                本命年已經悄然過去,在這一年里面確實有太多的心酸淚史。雖然沒有招受太多肉體上的傷害,但是生心的痛苦遠勝于此。

                 過去的日子總感覺自己活的渾渾噩噩的。生活,學習,感情,都是一團糟。然而在這段暈呼的歲月里面卻有一件事情總是不間斷,那就是思索生活的意義。”活著是為了什么“,“。。。”諸如此類的莫名其妙的為什么還有很多很多,我想我都快可以自己在寫一本十萬個為什么了。說句老實話,本人沒有為了人類的從高理想而活的高尚情操,所以這種思索只會讓我變得更加的頹廢,萎縮。我想不清楚這么高深的問題。也許是太富有哲理性了,更本就不是我這種凡夫俗子所能夠想的通的。對于這種問題的思索讓我失去了方向,干什么事情都找不著北。也因此帶來了很多的困擾,也因此而承受了很多原本就不應該承受的負擔。太多的思緒,太多的顧慮,太多的想不通,太多,太多,太多的問題使我的腦袋都已經在塞不下任何其他的東西。

                在這一年里面,我們去了索創的培訓,這確實是一個不錯的機會,然而心不在焉的也沒有把心思花在上面,一連串的心事讓我身在教室心在外。雖是基本不間斷的去上課,可我能夠感覺的到,我像是一具形尸走肉,盲目的穿行在紛亂的人群里面。感覺自己真的使那么的孤單,那么的無助。慢慢的自己也就開始喜歡上了幻想,喜歡活在自己的夢里面。

                 在后來就進了公司去實習,這也許就是一個戰時的避難所,然而它并沒有使我得到多少解脫,我還是依然活在我的夢里面。我希望通過不間歇的勞作使自己盡量的不要理會那些身外之事,然而我畢竟還沒有去少林出家,自然少不了七情六欲,固然沒有煉得真身,依然為塵世所繞。也許活在自己的夢里面是幸福的,夢真的醒來也是幸福的,但是我卻處在了陰陽交界似睡非省的邊緣。糊里糊涂的活著,活在稀里糊涂的世界里面。。。

                 有人說失敗的男人才會想家。看來我真的是一個失敗的男人,因為今年的我就特別的想回家。也許我真的是需要在家里調養一下,忘卻我心中的那份不應有的牽掛。

          posted @ 2007-05-28 15:31 change| 編輯 收藏

          相信大家對于mvc模式并不陌生,它的主要作用就是使 :結果展示,業務操作,以及后臺的數據庫操作,

          起到隔離的效果,這種分層解偶 的思想最大的好處應該就是便于擴展吧。對于現在比較流行的網站建設架構

          structs+hibernate+spring來說,就是用struct的標簽來做結果的展示,用它的action來處理業務邏輯,而用

          hibernate來處理業務邏輯所要操作的后臺數據庫數據。而他們各自的擴展變化和具體操作實現的變化 基本上

          都不合影響到其他層的改動。個人感覺   數據庫的設計定了,那么更具業務需求的hibernate接口成基本上就是

          定下來了,因為數據庫的操作無非不是根據 不同的條件實現  增,刪,改,查!說穿也就是說hibernate所做的

          只是對于你所設計的表 做 增,刪,改,查 操作,而這些操作基本上是固定模式。對于spring的用處主要就是

          用它的課配置事務處理的能力。

          posted @ 2007-05-28 15:29 change| 編輯 收藏

          最近用structs+hibernate+spring做的一個項目,后臺用的是oracle92。

          測試的時候發現了一個數據的插入問題,在數據庫里面定義的一個varchar(4000)的字段,在action層打印顯示的字段大小明明就是<4000字節的  可是一執行用hibernate插入操作的時候,就報錯,而且顯示的字段大小只要一超過2000就報錯說超出范圍,而且漢字還是3字節編碼的,而所有的頁面都用gbk編碼郭了,一直百思不得其解,原來是oracle驅動得問題。oracle92自己帶的驅動只是適合jdk1.3以前的,在oracle的官方網站上下載一個最新驅動,一切ok!暈吧!呵呵,希望大家  不要范和我一樣的毛病。為此二郁悶。

          posted @ 2007-05-28 15:28 change 閱讀(133) | 評論 (0)編輯 收藏

          比如在刪除一條在數據庫操作的時候 我們一般是類似是這樣使用:

          this.getHibernateTemplate().delete("from Information where INFOID='"+infoid.trim()+"'");

          然而具體在spring內部是怎么操作的呢?

          delete()----->excecute()----->執行回調方法HibernateCallback .doInHibernate()。

          下面來讓我們來直接看一下spring的源代碼。

          //hibernate回調接口

          public interface HibernateCallback {

          Object doInHibernate(Session session) throws HibernateException, SQLException;

          }

          //

          package org.springframework.orm.hibernate;

          public class HibernateTemplate extends HibernateAccessor implements HibernateOperations {

          //。。。。。。。。。。。。。。。。

          public int delete(final String queryString) throws DataAccessException {
            Integer deleteCount = (Integer) execute(new HibernateCallback() {//定義回調實現
             public Object doInHibernate(Session session) throws HibernateException {
              checkWriteOperationAllowed(session);
              return new Integer(session.delete(queryString));//此處有hibernate的實現操作
             }
            });
            return deleteCount.intValue();
           }

           public Object execute(HibernateCallback action) throws DataAccessException {
            Session session = (!isAllowCreate() ? SessionFactoryUtils.getSession(getSessionFactory(), false) :
            SessionFactoryUtils.getSession(getSessionFactory(), getEntityInterceptor(), getJdbcExceptionTranslator()));
            boolean existingTransaction = TransactionSynchronizationManager.hasResource(getSessionFactory());
            if (!existingTransaction && getFlushMode() == FLUSH_NEVER) {
             session.setFlushMode(FlushMode.NEVER);
            }
            try {
             Object result = action.doInHibernate(session);//此處調用hibernatecallback回調接口即hibernate的實現
             flushIfNecessary(session, existingTransaction);
             return result;
            }
            catch (HibernateException ex) {
             throw convertHibernateAccessException(ex);
            }
            catch (SQLException ex) {
             throw convertJdbcAccessException(ex);
            }
            catch (RuntimeException ex) {
             // callback code threw application exception
             throw ex;
            }
            finally {
             SessionFactoryUtils.closeSessionIfNecessary(session, getSessionFactory());
            }
           }

          //。。。。。。。。。。。。。。

          //其他操作類似

          }


          posted @ 2007-05-28 15:27 change| 編輯 收藏

          主站蜘蛛池模板: 丹凤县| 手游| 专栏| 陇南市| 宝坻区| 丘北县| 石棉县| 五莲县| 麻栗坡县| 凌海市| 青海省| 纳雍县| 中西区| 淮滨县| 开江县| 南华县| 措勤县| 靖远县| 彰武县| 吉林市| 禄劝| 咸阳市| 阿克陶县| 拜城县| 临泉县| 土默特左旗| 清镇市| 即墨市| 盘锦市| 永州市| 启东市| 拉萨市| 南通市| 台北市| 怀远县| 永川市| 二手房| 额尔古纳市| 永福县| 鹤庆县| 甘肃省|