• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            不會飛的鳥

            2010年12月10日 ... 不鳥他們?。?! 我要用自己開發的分布式文件系統、分布式調度系統、分布式檢索系統, 做自己的搜索引擎?。?!大魚有大志!??! ---楊書童

            2015年2月26日 #

            [轉]Win7下使用VS2010編譯的Win32控制臺應用程序在XP下運行報錯:找不到msvcp100d.dll

            //targetver.h

            #pragma once

            // 包括 SDKDDKVer.h 將定義可用的最高版本的 Windows 平臺。

            // 如果要為以前的 Windows 平臺生成應用程序,請包括 WinSDKVer.h,并將
            // WIN32_WINNT 宏設置為要支持的平臺,然后再包括 SDKDDKVer.h。

            #include <WinSDKVer.h>

            #define _WIN32_WINNT _WIN32_WINNT_WINXP

            #include <SDKDDKVer.h>

            1、如上:在targetver.h中添加代碼

            2、如下:修改運行庫



            posted @ 2015-02-26 18:53 不會飛的鳥 閱讀(733) | 評論 (0)編輯 收藏

            2014年12月18日 #

            [轉]Linux下g++編譯C++連接oracle(OCCI)出現的問題及解決方式

            由于項目原因,開始學習C++,剛接觸半個多月,今天參考網上例子,寫了個簡單的C++連接ORACLE的DEMO,可是使用g++編譯時不順利,不是報這個錯就是那個,最后參考網上的解決方式和個人理解,終于調試好了,現把編譯中出現的問題和解決方法總結出來。 

              源代碼 
             
            C++代碼
            1. #include <iostream>  
            2. #include <string>  
            3. #include "occi.h"  
            4. using namespace oracle::occi;  
            5. using namespace std;  
            6.   
            7. int main()  
            8. {  
            9.         string usr="sys";  
            10.         string pwd="orcl";  
            11.         string SID="ORCL";  
            12.         string date;  
            13.   
            14.         Environment *env=Environment::createEnvironment(Environment::OBJECT);  
            15.         Connection *conn= env->createConnection(usr,pwd,SID);//all strings  
            16.         if(conn)  
            17.                 cout<<"success createConnection!"<<endl;  
            18.         else  
            19.                 cout<<"failure createConnection!"<<endl;  
            20.   
            21.         Statement *stmt = conn->createStatement();  
            22.         string sSQL = "select to_char(sysdate,'yyyy-mm-dd hh24:mi:ss') from dual";  
            23.         stmt->setSQL(sSQL);  
            24.   
            25.   
            26.         ResultSet *rs = stmt->executeQuery();  
            27.         if(rs->next())  
            28.         {  
            29.                 date = rs->getString(1);  
            30.         }  
            31.   
            32.         cout<<"now time :"<<date<<endl;  
            33.   
            34.         env->terminateConnection(conn);  
            35.         Environment::terminateEnvironment(env);  
            36.   
            37.         return 0;  
            38. }  
            39.     
            1. #include <iostream>  
            2. #include <string>  
            3. #include "occi.h"  
            4. using namespace oracle::occi;  
            5. using namespace std;  
            6.   
            7. int main()  
            8. {  
            9.         string usr="sys";  
            10.         string pwd="orcl";  
            11.         string SID="ORCL";  
            12.         string date;  
            13.   
            14.         Environment *env=Environment::createEnvironment(Environment::OBJECT);  
            15.         Connection *conn= env->createConnection(usr,pwd,SID);//all strings  
            16.         if(conn)  
            17.                 cout<<"success createConnection!"<<endl;  
            18.         else  
            19.                 cout<<"failure createConnection!"<<endl;  
            20.   
            21.         Statement *stmt = conn->createStatement();  
            22.         string sSQL = "select to_char(sysdate,'yyyy-mm-dd hh24:mi:ss') from dual";  
            23.         stmt->setSQL(sSQL);  
            24.   
            25.   
            26.         ResultSet *rs = stmt->executeQuery();  
            27.         if(rs->next())  
            28.         {  
            29.                 date = rs->getString(1);  
            30.         }  
            31.   
            32.         cout<<"now time :"<<date<<endl;  
            33.   
            34.         env->terminateConnection(conn);  
            35.         Environment::terminateEnvironment(env);  
            36.   
            37.         return 0;  
            38. }  
            39.     


              本人linux上安裝oracle路徑:/opt/app/oracle/product/10.2.0/db_1 

              編譯命令:g++ -o conn -L/opt/app/oracle/product/10.2.0/db_1/lib -L/opt/oracle/product/10.2.0/db_1/rdbms/lib conn_db.cpp -g 

            問題一:編譯時報如下錯誤: 
                
            Shell代碼
            1.       [oracle@localhost demo]$ g++ g++ -o conn -L/opt/app/oracle/product/10.2.0/db_1/lib -L/opt/oracle/product/10.2.0/db_1/rdbms/lib -lclntsh -locci /usr/lib/libstdc++.so.5 conn_db.cpp -g  
            2. g++: g++: No such file or directory  
            3. conn_db.cpp:3:18: error: occi.h: No such file or directory  
            4. conn_db.cpp:4: error: 'oracle' has not been declared  
            5. conn_db.cpp:4: error: 'occi' is not a namespace-name  
            6. conn_db.cpp:4: error: expected namespace-name before ';' token  
            7. conn_db.cpp: In function 'int main()':  
            8. conn_db.cpp:14: error: 'Environment' was not declared in this scope  
            9. conn_db.cpp:14: error: 'env' was not declared in this scope  
            10. conn_db.cpp:14: error: 'Environment' is not a class or namespace  
            11. conn_db.cpp:14: error: 'Environment' is not a class or namespace  
            12. conn_db.cpp:15: error: 'Connection' was not declared in this scope  
            13. conn_db.cpp:15: error: 'conn' was not declared in this scope  
            14. conn_db.cpp:21: error: 'Statement' was not declared in this scope  
            15. conn_db.cpp:21: error: 'stmt' was not declared in this scope  
            16. conn_db.cpp:26: error: 'ResultSet' was not declared in this scope  
            17. conn_db.cpp:26: error: 'rs' was not declared in this scope  
            18. conn_db.cpp:35: error: 'Environment' is not a class or namespace  
            19.        

               
                解決:編譯時沒有引入OCCI頭文件,如果沒有,先下載對應的 ORACLE client安裝,比如我的是oracle10g,下載了oracle-instantclient-basic- 10.2.0.4-1.i386.zip,解壓到一個目錄下(/home/oracle/oracle/include),然后在編譯文件的時候引進這個解壓目錄 

               編譯命令增加OCCI目錄:g++ -o conn -I/home/oracle/oracle/include -L/opt/app/oracle/product/10.2.0/db_1/lib -L/opt/oracle/product/10.2.0/db_1/rdbms/lib conn_db.cpp -g 


            問題2:找不到對應函數 
             
            Shell代碼
            1.    [oracle@localhost demo]$ g++ -o conn -I/home/oracle/oracle/include -L/opt/app/oracle/product/10.2.0/db_1/lib -L/opt/oracle/product/10.2.0/db_1/rdbms/lib conn_db.cpp -Wall -O -g  
            2. /tmp/cclFs9xq.o: In function `main':  
            3. /home/oracle/oracle/demo/conn_db.cpp:14: undefined reference to `oracle::occi::Environment::createEnvironment(oracle::occi::Environment::Mode, void*, void* (*)(void*, unsigned int), void* (*)(void*, void*, unsigned int), void (*)(void*, void*))'  
            4. /home/oracle/oracle/demo/conn_db.cpp:35: undefined reference to `oracle::occi::Environment::terminateEnvironment(oracle::occi::Environment*)'  
            5. collect2: ld returned 1 exit status   
            6.    

            解決:增加libocci.so和libclntsh.so指定編譯 
              
              修改后的編譯命令: g++ -o conn -I/home/oracle/oracle/include -L/opt/app/oracle/product/10.2.0/db_1/lib -L/opt/oracle/product/10.2.0/db_1/rdbms/lib conn_db.cpp -lclntsh -locci -Wall -O -g 

              另外可能在引入-lclntsh -locci編譯時可能會報找不到以下錯誤: 
                
            Shell代碼
            1.      [oracle@localhost demo]$ g++ -o conn -I/home/oracle/oracle/include -L/opt/app/oracle/product/10.2.0/db_1/lib -L/opt/oracle/product/10.2.0/db_1/rdbms/lib conn_db.cpp -lclntsh -locci /usr/lib/libstdc++.so.5 -Wall -O -g  
            2. /usr/bin/ld: cannot find -lclntsh  
            3. collect2: ld returned 1 exit status  
            4. [oracle@localhost demo]$   
            5.        

                 解決:這是因為沒有找到libclntsh.so和libocci.so鏈接庫,你在可以把oracle client安裝目錄下把這兩個文件拷貝到$ORACLE_HOME/lib目錄下,或加到/usr/lib目錄下就可以了 

            問題三:occi在linux編譯運行時報libstdc++.so.6沖突的問題 
               
            Java代碼
            1. [oracle@localhost demo]$ g++ -o conn -I/home/oracle/oracle/include -L/opt/app/oracle/product/10.2.0/db_1/lib -L/opt/oracle/product/10.2.0/db_1/rdbms/lib conn_db.cpp -lclntsh -locci -Wall -O -g  
            2. /usr/bin/ld: warning: libstdc++.so.5, needed by /opt/app/oracle/product/10.2.0/db_1/lib/libocci.so, may conflict with libstdc++.so.6  
            1. [oracle@localhost demo]$ g++ -o conn -I/home/oracle/oracle/include -L/opt/app/oracle/product/10.2.0/db_1/lib -L/opt/oracle/product/10.2.0/db_1/rdbms/lib conn_db.cpp -lclntsh -locci -Wall -O -g  
            2. /usr/bin/ld: warning: libstdc++.so.5, needed by /opt/app/oracle/product/10.2.0/db_1/lib/libocci.so, may conflict with libstdc++.so.6  

              解決:OCCI庫在linux編譯的時候,由于linux版本太高,會提示以上情況,實際上,在大多數linux系統上,還保留有libstdc++5的庫,自己手工在編譯的時候加上去就好了 

              修改后的編譯命令:g++ -o conn -I/home/oracle/oracle/include -L/opt/app/oracle/product/10.2.0/db_1/lib -L/opt/oracle/product/10.2.0/db_1/rdbms/lib -lclntsh -locci /usr/lib/libstdc++.so.5 conn_db.cpp -g 

              編譯通過后執行結果輸出: 
              
            Shell代碼
            1. [oracle@localhost demo]$ g++ -o conn -I/home/oracle/oracle/include -L/opt/app/oracle/product/10.2.0/db_1/lib -L/opt/oracle/product/10.2.0/db_1/rdbms/lib conn_db.cpp -lclntsh -locci /usr/lib/libstdc++.so.5 -Wall -O -g  
            2. [oracle@localhost demo]$ ./conn  
            3. success createConnection!  
            4. now time :2010-11-14 22:49:24  
            5. [oracle@localhost demo]$  


            posted @ 2014-12-18 13:04 不會飛的鳥 閱讀(853) | 評論 (0)編輯 收藏

            2014年11月10日 #

            各種字符串Hash函數比較

            常用的字符串Hash函數還有ELFHash,APHash等等,都是十分簡單有效的方法。這些函數使用位運算使得每一個字符都對最后的函數值產生影響。另外還有以MD5和SHA1為代表的雜湊函數,這些函數幾乎不可能找到碰撞。

            常用字符串哈希函數有BKDRHash,APHash,DJBHash,JSHash,RSHash,SDBMHash,PJWHash,ELFHash等等。對于以上幾種哈希函數,我對其進行了一個小小的評測。

            Hash函數數據1數據2數據3數據4數據1得分數據2得分數據3得分數據4得分平均分
            BKDRHash20477448196.5510090.9582.0592.64
            APHash23475449396.5588.4610051.2886.28
            DJBHash22497547496.5592.31010083.43
            JSHash14476150610084.6296.8317.9581.94
            RSHash10486150510010051.5820.5175.96
            SDBMHash32484950493.192.3157.0123.0872.41
            PJWHash302648785130043.89021.95
            ELFHash302648785130043.89021.95

            其中數據1為100000個字母和數字組成的隨機串哈希沖突個數。數據2為100000個有意義的英文句子哈希沖突個數。數據3為數據1的哈希值與1000003(大素數)求模后存儲到線性表中沖突的個數。數據4為數據1的哈希值與10000019(更大素數)求模后存儲到線性表中沖突的個數。

            經過比較,得出以上平均得分。平均數為平方平均數。可以發現,BKDRHash無論是在實際效果還是編碼實現中,效果都是最突出的。APHash也是較為優秀的算法。DJBHash,JSHash,RSHash與SDBMHash各有千秋。PJWHash與ELFHash效果最差,但得分相似,其算法本質是相似的。

            在信息修競賽中,要本著易于編碼調試的原則,個人認為BKDRHash是最適合記憶和使用的。

            BYVoid原創,歡迎建議、交流、批評和指正。

            附:各種哈希函數的C語言程序代碼

            unsigned int SDBMHash(char *str)
            {
                unsigned 
            int hash = 0;

                
            while (*str)
                {
                    
            // equivalent to: hash = 65599*hash + (*str++);
                    hash = (*str+++ (hash << 6+ (hash << 16- hash;
                }

                
            return (hash & 0x7FFFFFFF);
            }

            // RS Hash Function
            unsigned int RSHash(char *str)
            {
                unsigned 
            int b = 378551;
                unsigned 
            int a = 63689;
                unsigned 
            int hash = 0;

                
            while (*str)
                {
                    hash 
            = hash * a + (*str++);
                    a 
            *= b;
                }

                
            return (hash & 0x7FFFFFFF);
            }

            // JS Hash Function
            unsigned int JSHash(char *str)
            {
                unsigned 
            int hash = 1315423911;

                
            while (*str)
                {
                    hash 
            ^= ((hash << 5+ (*str+++ (hash >> 2));
                }

                
            return (hash & 0x7FFFFFFF);
            }

            // P. J. Weinberger Hash Function
            unsigned int PJWHash(char *str)
            {
                unsigned 
            int BitsInUnignedInt = (unsigned int)(sizeof(unsigned int* 8);
                unsigned 
            int ThreeQuarters    = (unsigned int)((BitsInUnignedInt  * 3/ 4);
                unsigned 
            int OneEighth        = (unsigned int)(BitsInUnignedInt / 8);
                unsigned 
            int HighBits         = (unsigned int)(0xFFFFFFFF<< (BitsInUnignedInt - OneEighth);
                unsigned 
            int hash             = 0;
                unsigned 
            int test             = 0;

                
            while (*str)
                {
                    hash 
            = (hash << OneEighth) + (*str++);
                    
            if ((test = hash & HighBits) != 0)
                    {
                        hash 
            = ((hash ^ (test >> ThreeQuarters)) & (~HighBits));
                    }
                }

                
            return (hash & 0x7FFFFFFF);
            }

            // ELF Hash Function
            unsigned int ELFHash(char *str)
            {
                unsigned 
            int hash = 0;
                unsigned 
            int x    = 0;

                
            while (*str)
                {
                    hash 
            = (hash << 4+ (*str++);
                    
            if ((x = hash & 0xF0000000L!= 0)
                    {
                        hash 
            ^= (x >> 24);
                        hash 
            &= ~x;
                    }
                }

                
            return (hash & 0x7FFFFFFF);
            }

            // BKDR Hash Function
            unsigned int BKDRHash(char *str)
            {
                unsigned 
            int seed = 131// 31 131 1313 13131 131313 etc..
                unsigned int hash = 0;

                
            while (*str)
                {
                    hash 
            = hash * seed + (*str++);
                }

                
            return (hash & 0x7FFFFFFF);
            }

            // DJB Hash Function
            unsigned int DJBHash(char *str)
            {
                unsigned 
            int hash = 5381;

                
            while (*str)
                {
                    hash 
            += (hash << 5+ (*str++);
                }

                
            return (hash & 0x7FFFFFFF);
            }

            // AP Hash Function
            unsigned int APHash(char *str)
            {
                unsigned 
            int hash = 0;
                
            int i;

                
            for (i=0*str; i++)
                {
                    
            if ((i & 1== 0)
                    {
                        hash 
            ^= ((hash << 7^ (*str++^ (hash >> 3));
                    }
                    
            else
                    {
                        hash 
            ^= (~((hash << 11^ (*str++^ (hash >> 5)));
                    }
                }

                
            return (hash & 0x7FFFFFFF);
            }

            posted @ 2014-11-10 19:53 不會飛的鳥 閱讀(830) | 評論 (0)編輯 收藏

            2014年5月26日 #

            [轉]C++的Json解析庫:jsoncpp和boost

            JSON(JavaScript Object Notation)跟xml一樣也是一種數據交換格式,了解json請參考其官網http://json.org/,本文不再對json做介紹,將重點介紹c++的json解析庫的使用方法。json官網上列出了各種語言對應的json解析庫,作者僅介紹自己使用過的兩種C++的json解析庫:jsoncpp(v0.5.0)和Boost(v1.34.0)。

            一. 使用jsoncpp解析json

            Jsoncpp是個跨平臺的開源庫,首先從http://jsoncpp.sourceforge.net/上下載jsoncpp庫源碼,我下載的是v0.5.0,壓縮包大約107K,解壓,在jsoncpp-src-0.5.0/makefiles/vs71目錄里找到jsoncpp.sln,用VS2003及以上版本編譯,默認生成靜態鏈接庫。 在工程中引用,只需要include/json及.lib文件即可。

            使用JsonCpp前先來熟悉幾個主要的類:

            Json::Value 可以表示里所有的類型,比如int,string,object,array等,具體應用將會在后邊示例中介紹。

            Json::Reader 將json文件流或字符串解析到Json::Value, 主要函數有Parse。

            Json::Writer 與Json::Reader相反,將Json::Value轉化成字符串流,注意它的兩個子類:Json::FastWriter和Json::StyleWriter,分別輸出不帶格式的json和帶格式的json。

            1. 從字符串解析json

            int ParseJsonFromString()
            {
              const char* str = "{\"uploadid\": \"UP000000\",\"code\": 100,\"msg\": \"\",\"files\": \"\"}";

              Json::Reader reader;
              Json::Value root;
              if (reader.parse(str, root))  // reader將Json字符串解析到root,root將包含Json里所有子元素
              {
                std::string upload_id = root["uploadid"].asString();  // 訪問節點,upload_id = "UP000000"
                int code = root["code"].asInt();    // 訪問節點,code = 100
              }
              return 0;
            }

            2. 從文件解析json

            {
                "uploadid": "UP000000",
                "code": "0",
                "msg": "",
                "files":
                [
                    {
                        "code": "0",
                        "msg": "",
                        "filename": "1D_16-35_1.jpg",
                        "filesize": "196690",
                        "width": "1024",
                        "height": "682",
                        "images":
                        [
                            {
                                "url": "fmn061/20111118",
                                "type": "large",
                                "width": "720",
                                "height": "479"
                            },
                            {
                                "url": "fmn061/20111118",
                                "type": "main",
                                "width": "200",
                                "height": "133"
                            }
                        ]
                    }
                ]
            }
            解析代碼:
            int ParseJsonFromFile(const char* filename)
            {
              // 解析json用Json::Reader
              Json::Reader reader;
              // Json::Value是一種很重要的類型,可以代表任意類型。如int, string, object, array
              Json::Value root;       

              std::ifstream is;
              is.open (filename, std::ios::binary );  
              if (reader.parse(is, root))
              {
                std::string code;
                if (!root["files"].isNull())  // 訪問節點,Access an object value by name, create a null member if it does not exist.
                  code = root["uploadid"].asString();
                
                // 訪問節點,Return the member named key if it exist, defaultValue otherwise.
                code = root.get("uploadid", "null").asString();

                // 得到"files"的數組個數
                int file_size = root["files"].size();

                // 遍歷數組
                for(int i = 0; i < file_size; ++i)
                {
                  Json::Value val_image = root["files"][i]["images"];
                  int image_size = val_image.size();
                  for(int j = 0; j < image_size; ++j)
                  {
                    std::string type = val_image[j]["type"].asString();
                    std::string url = val_image[j]["url"].asString();
                  }
                }
              }
              is.close();
              return 0;
            }

            3. 在json結構中插入json

                Json::Value arrayObj;   // 構建對象
                Json::Value new_item, new_item1;
                new_item["date"] = "2011-12-28";
                new_item1["time"] = "22:30:36";
                arrayObj.append(new_item);  // 插入數組成員
                arrayObj.append(new_item1); // 插入數組成員
                int file_size = root["files"].size();
                for(int i = 0; i < file_size; ++i)
                  root["files"][i]["exifs"] = arrayObj;   // 插入原json中

            4. 輸出json

            // 轉換為字符串(帶格式)
            std::string out = root.toStyledString();
            // 輸出無格式json字符串
            Json::FastWriter writer;
            std::string out2 = writer.write(root);

            二. 使用Boost property_tree解析json

            property_tree可以解析xml,json,ini,info等格式的數據,用property_tree解析這幾種格式使用方法很相似。

            解析json很簡單,命名空間為boost::property_tree,reson_json函數將文件流、字符串解析到ptree,write_json將ptree輸出為字符串或文件流。其余的都是對ptree的操作。

            解析json需要加頭文件:

            #include <boost/property_tree/ptree.hpp>
            #include <boost/property_tree/json_parser.hpp>

            1. 解析json

            解析一段下面的數據:

            {
              "code": 0,
              "images":
              [
                {
                  "url": "fmn057/20111221/1130/head_kJoO_05d9000251de125c.jpg"
                },
                {
                  "url": "fmn057/20111221/1130/original_kJoO_05d9000251de125c.jpg"
                }
              ]
            }
            int ParseJson()
            {
              std::string str = "{\"code\":0,\"images\":[{\"url\":\"fmn057/20111221/1130/head_kJoO_05d9000251de125c.jpg\"},{\"url\":\"fmn057/20111221/1130/original_kJoO_05d9000251de125c.jpg\"}]}";
              using namespace boost::property_tree;

              std::stringstream ss(str);
              ptree pt;
              try{    
                read_json(ss, pt);
              }
              catch(ptree_error & e) {
                return 1; 
              }

              try{
                int code = pt.get<int>("code");   // 得到"code"的value
                ptree image_array = pt.get_child("images");  // get_child得到數組對象
                
                
            // 遍歷數組
                BOOST_FOREACH(boost::property_tree::ptree::value_type &v, image_array)
                {
                  std::stringstream s;
                  write_json(s, v.second);
                  std::string image_item = s.str();
                }
              }
              catch (ptree_error & e)
              {
                return 2;
              }
              return 0;
            }

            2. 構造json

            int InsertJson()
            {
              std::string str = "{\"code\":0,\"images\":[{\"url\":\"fmn057/20111221/1130/head_kJoO_05d9000251de125c.jpg\"},{\"url\":\"fmn057/20111221/1130/original_kJoO_05d9000251de125c.jpg\"}]}";
              using namespace boost::property_tree;

              std::stringstream ss(str);
              ptree pt;
              try{    
                read_json(ss, pt);
              }
              catch(ptree_error & e) {
                return 1; 
              }

              // 修改/增加一個key-value,key不存在則增加
              pt.put("upid", "00001");

              // 插入一個數組
              ptree exif_array;
              ptree array1, array2, array3;
              array1.put("Make", "NIKON");
              array2.put("DateTime", "2011:05:31 06:47:09");
              array3.put("Software", "Ver.1.01");
              exif_array.push_back(std::make_pair("", array1));
              exif_array.push_back(std::make_pair("", array2));
              exif_array.push_back(std::make_pair("", array3));

            //   exif_array.push_back(std::make_pair("Make", "NIKON"));
            //   exif_array.push_back(std::make_pair("DateTime", "2011:05:31 06:47:09"));
            //   exif_array.push_back(std::make_pair("Software", "Ver.1.01"));

              pt.put_child("exifs", exif_array);
              std::stringstream s2;
              write_json(s2, pt);
              std::string outstr = s2.str();

              return 0;
            }

            三. 兩種解析庫的使用經驗

            1. 用boost::property_tree解析字符串遇到"\/"時解析失敗,而jsoncpp可以解析成功,要知道'/'前面加一個'\'是JSON標準格式。

            2. boost::property_tree的read_json和write_json在多線程中使用會引起崩潰。

            針對1,可以在使用boost::property_tree解析前寫個函數去掉"\/"中的'\',針對2,在多線程中同步一下可以解決。

            我的使用心得:使用boost::property_tree不僅可以解析json,還可以解析xml,info等格式的數據。對于解析json,使用boost::property_tree解析還可以忍受,但解析xml,由于遇到問題太多只能換其它庫了。

            posted @ 2014-05-26 00:36 不會飛的鳥 閱讀(925) | 評論 (0)編輯 收藏

            2014年3月6日 #

            [轉]字符串匹配的Boyer-Moore算法

            上一篇文章,我介紹了KMP算法

            但是,它并不是效率最高的算法,實際采用并不多。各種文本編輯器的"查找"功能(Ctrl+F),大多采用Boyer-Moore算法

            Boyer-Moore算法不僅效率高,而且構思巧妙,容易理解。1977年,德克薩斯大學的Robert S. Boyer教授和J Strother Moore教授發明了這種算法。

            下面,我根據Moore教授自己的例子來解釋這種算法。

            1.

            假定字符串為"HERE IS A SIMPLE EXAMPLE",搜索詞為"EXAMPLE"。

            2.

            首先,"字符串"與"搜索詞"頭部對齊,從尾部開始比較。

            這是一個很聰明的想法,因為如果尾部字符不匹配,那么只要一次比較,就可以知道前7個字符(整體上)肯定不是要找的結果。

            我們看到,"S"與"E"不匹配。這時,"S"就被稱為"壞字符"(bad character),即不匹配的字符。我們還發現,"S"不包含在搜索詞"EXAMPLE"之中,這意味著可以把搜索詞直接移到"S"的后一位。

            3.

            依然從尾部開始比較,發現"P"與"E"不匹配,所以"P"是"壞字符"。但是,"P"包含在搜索詞"EXAMPLE"之中。所以,將搜索詞后移兩位,兩個"P"對齊。

            4.

            我們由此總結出"壞字符規則"

              后移位數 = 壞字符的位置 - 搜索詞中的上一次出現位置

            如果"壞字符"不包含在搜索詞之中,則上一次出現位置為 -1。

            以"P"為例,它作為"壞字符",出現在搜索詞的第6位(從0開始編號),在搜索詞中的上一次出現位置為4,所以后移 6 - 4 = 2位。再以前面第二步的"S"為例,它出現在第6位,上一次出現位置是 -1(即未出現),則整個搜索詞后移 6 - (-1) = 7位。

            5.

            依然從尾部開始比較,"E"與"E"匹配。

            6.

            比較前面一位,"LE"與"LE"匹配。

            7.

            比較前面一位,"PLE"與"PLE"匹配。

            8.

            比較前面一位,"MPLE"與"MPLE"匹配。我們把這種情況稱為"好后綴"(good suffix),即所有尾部匹配的字符串。注意,"MPLE"、"PLE"、"LE"、"E"都是好后綴。

            9.

            比較前一位,發現"I"與"A"不匹配。所以,"I"是"壞字符"。

            10.

            根據"壞字符規則",此時搜索詞應該后移 2 - (-1)= 3 位。問題是,此時有沒有更好的移法?

            11.

            我們知道,此時存在"好后綴"。所以,可以采用"好后綴規則"

              后移位數 = 好后綴的位置 - 搜索詞中的上一次出現位置

            舉例來說,如果字符串"ABCDAB"的后一個"AB"是"好后綴"。那么它的位置是5(從0開始計算,取最后的"B"的值),在"搜索詞中的上一次出現位置"是1(第一個"B"的位置),所以后移 5 - 1 = 4位,前一個"AB"移到后一個"AB"的位置。

            再舉一個例子,如果字符串"ABCDEF"的"EF"是好后綴,則"EF"的位置是5 ,上一次出現的位置是 -1(即未出現),所以后移 5 - (-1) = 6位,即整個字符串移到"F"的后一位。

            這個規則有三個注意點:

             ?。?)"好后綴"的位置以最后一個字符為準。假定"ABCDEF"的"EF"是好后綴,則它的位置以"F"為準,即5(從0開始計算)。

              (2)如果"好后綴"在搜索詞中只出現一次,則它的上一次出現位置為 -1。比如,"EF"在"ABCDEF"之中只出現一次,則它的上一次出現位置為-1(即未出現)。

             ?。?)如果"好后綴"有多個,則除了最長的那個"好后綴",其他"好后綴"的上一次出現位置必須在頭部。比如,假定"BABCDAB"的"好后綴"是"DAB"、"AB"、"B",請問這時"好后綴"的上一次出現位置是什么?回答是,此時采用的好后綴是"B",它的上一次出現位置是頭部,即第0位。這個規則也可以這樣表達:如果最長的那個"好后綴"只出現一次,則可以把搜索詞改寫成如下形式進行位置計算"(DA)BABCDAB",即虛擬加入最前面的"DA"。

            回到上文的這個例子。此時,所有的"好后綴"(MPLE、PLE、LE、E)之中,只有"E"在"EXAMPLE"還出現在頭部,所以后移 6 - 0 = 6位。

            12.

            可以看到,"壞字符規則"只能移3位,"好后綴規則"可以移6位。所以,Boyer-Moore算法的基本思想是,每次后移這兩個規則之中的較大值。

            更巧妙的是,這兩個規則的移動位數,只與搜索詞有關,與原字符串無關。因此,可以預先計算生成《壞字符規則表》和《好后綴規則表》。使用時,只要查表比較一下就可以了。

            13.

            繼續從尾部開始比較,"P"與"E"不匹配,因此"P"是"壞字符"。根據"壞字符規則",后移 6 - 4 = 2位。

            14.

            從尾部開始逐位比較,發現全部匹配,于是搜索結束。如果還要繼續查找(即找出全部匹配),則根據"好后綴規則",后移 6 - 0 = 6位,即頭部的"E"移到尾部的"E"的位置。

            posted @ 2014-03-06 21:47 不會飛的鳥 閱讀(364) | 評論 (0)編輯 收藏

            [轉]字符串匹配的KMP算法

            字符串匹配是計算機的基本任務之一。

            舉例來說,有一個字符串"BBC ABCDAB ABCDABCDABDE",我想知道,里面是否包含另一個字符串"ABCDABD"?

            許多算法可以完成這個任務,Knuth-Morris-Pratt算法(簡稱KMP)是最常用的之一。它以三個發明者命名,起頭的那個K就是著名科學家Donald Knuth。

            這種算法不太容易理解,網上有很多解釋,但讀起來都很費勁。直到讀到Jake Boxer的文章,我才真正理解這種算法。下面,我用自己的語言,試圖寫一篇比較好懂的KMP算法解釋。

            1.

            首先,字符串"BBC ABCDAB ABCDABCDABDE"的第一個字符與搜索詞"ABCDABD"的第一個字符,進行比較。因為B與A不匹配,所以搜索詞后移一位。

            2.

            因為B與A不匹配,搜索詞再往后移。

            3.

            就這樣,直到字符串有一個字符,與搜索詞的第一個字符相同為止。

            4.

            接著比較字符串和搜索詞的下一個字符,還是相同。

            5.

            直到字符串有一個字符,與搜索詞對應的字符不相同為止。

            6.

            這時,最自然的反應是,將搜索詞整個后移一位,再從頭逐個比較。這樣做雖然可行,但是效率很差,因為你要把"搜索位置"移到已經比較過的位置,重比一遍。

            7.

            一個基本事實是,當空格與D不匹配時,你其實知道前面六個字符是"ABCDAB"。KMP算法的想法是,設法利用這個已知信息,不要把"搜索位置"移回已經比較過的位置,繼續把它向后移,這樣就提高了效率。

            8.

            怎么做到這一點呢?可以針對搜索詞,算出一張《部分匹配表》(Partial Match Table)。這張表是如何產生的,后面再介紹,這里只要會用就可以了。

            9.

            已知空格與D不匹配時,前面六個字符"ABCDAB"是匹配的。查表可知,最后一個匹配字符B對應的"部分匹配值"為2,因此按照下面的公式算出向后移動的位數:

              移動位數 = 已匹配的字符數 - 對應的部分匹配值

            因為 6 - 2 等于4,所以將搜索詞向后移動4位。

            10.

            因為空格與C不匹配,搜索詞還要繼續往后移。這時,已匹配的字符數為2("AB"),對應的"部分匹配值"為0。所以,移動位數 = 2 - 0,結果為 2,于是將搜索詞向后移2位。

            11.

            因為空格與A不匹配,繼續后移一位。

            12.

            逐位比較,直到發現C與D不匹配。于是,移動位數 = 6 - 2,繼續將搜索詞向后移動4位。

            13.

            逐位比較,直到搜索詞的最后一位,發現完全匹配,于是搜索完成。如果還要繼續搜索(即找出全部匹配),移動位數 = 7 - 0,再將搜索詞向后移動7位,這里就不再重復了。

            14.

            下面介紹《部分匹配表》是如何產生的。

            首先,要了解兩個概念:"前綴"和"后綴"。 "前綴"指除了最后一個字符以外,一個字符串的全部頭部組合;"后綴"指除了第一個字符以外,一個字符串的全部尾部組合。

            15.

            "部分匹配值"就是"前綴"和"后綴"的最長的共有元素的長度。以"ABCDABD"為例,

             ?。?A"的前綴和后綴都為空集,共有元素的長度為0;

             ?。?AB"的前綴為[A],后綴為[B],共有元素的長度為0;

              - "ABC"的前綴為[A, AB],后綴為[BC, C],共有元素的長度0;

             ?。?ABCD"的前綴為[A, AB, ABC],后綴為[BCD, CD, D],共有元素的長度為0;

             ?。?ABCDA"的前綴為[A, AB, ABC, ABCD],后綴為[BCDA, CDA, DA, A],共有元素為"A",長度為1;

             ?。?ABCDAB"的前綴為[A, AB, ABC, ABCD, ABCDA],后綴為[BCDAB, CDAB, DAB, AB, B],共有元素為"AB",長度為2;

             ?。?ABCDABD"的前綴為[A, AB, ABC, ABCD, ABCDA, ABCDAB],后綴為[BCDABD, CDABD, DABD, ABD, BD, D],共有元素的長度為0。

            16.

            "部分匹配"的實質是,有時候,字符串頭部和尾部會有重復。比如,"ABCDAB"之中有兩個"AB",那么它的"部分匹配值"就是2("AB"的長度)。搜索詞移動的時候,第一個"AB"向后移動4位(字符串長度-部分匹配值),就可以來到第二個"AB"的位置。

            posted @ 2014-03-06 21:46 不會飛的鳥 閱讀(269) | 評論 (0)編輯 收藏

            [轉]進程與線程的一個簡單解釋

            進程(process)和線程(thread)是操作系統的基本概念,但是它們比較抽象,不容易掌握。

            最近,我讀到一篇材料,發現有一個很好的類比,可以把它們解釋地清晰易懂。

            1.

            計算機的核心是CPU,它承擔了所有的計算任務。它就像一座工廠,時刻在運行。

            2.

            假定工廠的電力有限,一次只能供給一個車間使用。也就是說,一個車間開工的時候,其他車間都必須停工。背后的含義就是,單個CPU一次只能運行一個任務。

            3.

            進程就好比工廠的車間,它代表CPU所能處理的單個任務。任一時刻,CPU總是運行一個進程,其他進程處于非運行狀態。

            4.

            一個車間里,可以有很多工人。他們協同完成一個任務。

            5.

            線程就好比車間里的工人。一個進程可以包括多個線程。

            6.

            車間的空間是工人們共享的,比如許多房間是每個工人都可以進出的。這象征一個進程的內存空間是共享的,每個線程都可以使用這些共享內存。

            7.

            可是,每間房間的大小不同,有些房間最多只能容納一個人,比如廁所。里面有人的時候,其他人就不能進去了。這代表一個線程使用某些共享內存時,其他線程必須等它結束,才能使用這一塊內存。

            8.

            一個防止他人進入的簡單方法,就是門口加一把鎖。先到的人鎖上門,后到的人看到上鎖,就在門口排隊,等鎖打開再進去。這就叫"互斥鎖"(Mutual exclusion,縮寫 Mutex),防止多個線程同時讀寫某一塊內存區域。

            9.

            還有些房間,可以同時容納n個人,比如廚房。也就是說,如果人數大于n,多出來的人只能在外面等著。這好比某些內存區域,只能供給固定數目的線程使用。

            10.

            這時的解決方法,就是在門口掛n把鑰匙。進去的人就取一把鑰匙,出來時再把鑰匙掛回原處。后到的人發現鑰匙架空了,就知道必須在門口排隊等著了。這種做法叫做"信號量"(Semaphore),用來保證多個線程不會互相沖突。

            不難看出,mutex是semaphore的一種特殊情況(n=1時)。也就是說,完全可以用后者替代前者。但是,因為mutex較為簡單,且效率高,所以在必須保證資源獨占的情況下,還是采用這種設計。

            11.

            操作系統的設計,因此可以歸結為三點:

            (1)以多進程形式,允許多個任務同時運行;

            (2)以多線程形式,允許單個任務分成不同的部分運行;

            (3)提供協調機制,一方面防止進程之間和線程之間產生沖突,另一方面允許進程之間和線程之間共享資源。

            posted @ 2014-03-06 21:44 不會飛的鳥 閱讀(236) | 評論 (0)編輯 收藏

            [轉]相似圖片搜索的原理(二)

            昨天,我在isnowfy的網站看到,還有其他兩種方法也很簡單,這里做一些筆記。

            一、顏色分布法

            每張圖片都可以生成顏色分布的直方圖(color histogram)。如果兩張圖片的直方圖很接近,就可以認為它們很相似。

            任何一種顏色都是由紅綠藍三原色(RGB)構成的,所以上圖共有4張直方圖(三原色直方圖 + 最后合成的直方圖)。

            如果每種原色都可以取256個值,那么整個顏色空間共有1600萬種顏色(256的三次方)。針對這1600萬種顏色比較直方圖,計算量實在太大了,因此需要采用簡化方法??梢詫?~255分成四個區:0~63為第0區,64~127為第1區,128~191為第2區,192~255為第3區。這意味著紅綠藍分別有4個區,總共可以構成64種組合(4的3次方)。

            任何一種顏色必然屬于這64種組合中的一種,這樣就可以統計每一種組合包含的像素數量。

            上圖是某張圖片的顏色分布表,將表中最后一欄提取出來,組成一個64維向量(7414, 230, 0, 0, 8, ..., 109, 0, 0, 3415, 53929)。這個向量就是這張圖片的特征值或者叫"指紋"。

            于是,尋找相似圖片就變成了找出與其最相似的向量。這可以用皮爾遜相關系數或者余弦相似度算出。

            二、內容特征法

            除了顏色構成,還可以從比較圖片內容的相似性入手。

            首先,將原圖轉成一張較小的灰度圖片,假定為50x50像素。然后,確定一個閾值,將灰度圖片轉成黑白圖片。

              

            如果兩張圖片很相似,它們的黑白輪廓應該是相近的。于是,問題就變成了,第一步如何確定一個合理的閾值,正確呈現照片中的輪廓?

            顯然,前景色與背景色反差越大,輪廓就越明顯。這意味著,如果我們找到一個值,可以使得前景色和背景色各自的"類內差異最小"(minimizing the intra-class variance),或者"類間差異最大"(maximizing the inter-class variance),那么這個值就是理想的閾值。

            1979年,日本學者大津展之證明了,"類內差異最小"與"類間差異最大"是同一件事,即對應同一個閾值。他提出一種簡單的算法,可以求出這個閾值,這被稱為"大津法"(Otsu's method)。下面就是他的計算方法。

            假定一張圖片共有n個像素,其中灰度值小于閾值的像素為 n1 個,大于等于閾值的像素為 n2 個( n1 + n2 = n )。w1 和 w2 表示這兩種像素各自的比重。

              w1 = n1 / n

              w2 = n2 / n

            再假定,所有灰度值小于閾值的像素的平均值和方差分別為 μ1 和 σ1,所有灰度值大于等于閾值的像素的平均值和方差分別為 μ2 和 σ2。于是,可以得到

              類內差異 = w1(σ1的平方) + w2(σ2的平方)

              類間差異 = w1w2(μ1-μ2)^2

            可以證明,這兩個式子是等價的:得到"類內差異"的最小值,等同于得到"類間差異"的最大值。不過,從計算難度看,后者的計算要容易一些。

            下一步用"窮舉法",將閾值從灰度的最低值到最高值,依次取一遍,分別代入上面的算式。使得"類內差異最小"或"類間差異最大"的那個值,就是最終的閾值。具體的實例和Java算法,請看這里。

            有了50x50像素的黑白縮略圖,就等于有了一個50x50的0-1矩陣。矩陣的每個值對應原圖的一個像素,0表示黑色,1表示白色。這個矩陣就是一張圖片的特征矩陣。

            兩個特征矩陣的不同之處越少,就代表兩張圖片越相似。這可以用"異或運算"實現(即兩個值之中只有一個為1,則運算結果為1,否則運算結果為0)。對不同圖片的特征矩陣進行"異或運算",結果中的1越少,就是越相似的圖片。

            posted @ 2014-03-06 21:42 不會飛的鳥 閱讀(346) | 評論 (0)編輯 收藏

            [轉]相似圖片搜索的原理(一)

            上個月,Google把"相似圖片搜索"正式放上了首頁。

            你可以用一張圖片,搜索互聯網上所有與它相似的圖片。點擊搜索框中照相機的圖標。

            一個對話框會出現。

            你輸入網片的網址,或者直接上傳圖片,Google就會找出與其相似的圖片。下面這張圖片是美國女演員Alyson Hannigan。

            上傳后,Google返回如下結果:

            類似的"相似圖片搜索引擎"還有不少,TinEye甚至可以找出照片的拍攝背景。

            ==========================================================

            這種技術的原理是什么?計算機怎么知道兩張圖片相似呢?

            根據Neal Krawetz博士的解釋,原理非常簡單易懂。我們可以用一個快速算法,就達到基本的效果。

            這里的關鍵技術叫做"感知哈希算法"(Perceptual hash algorithm),它的作用是對每張圖片生成一個"指紋"(fingerprint)字符串,然后比較不同圖片的指紋。結果越接近,就說明圖片越相似。

            下面是一個最簡單的實現:

            第一步,縮小尺寸。

            將圖片縮小到8x8的尺寸,總共64個像素。這一步的作用是去除圖片的細節,只保留結構、明暗等基本信息,摒棄不同尺寸、比例帶來的圖片差異。

             

            第二步,簡化色彩。

            將縮小后的圖片,轉為64級灰度。也就是說,所有像素點總共只有64種顏色。

            第三步,計算平均值。

            計算所有64個像素的灰度平均值。

            第四步,比較像素的灰度。

            將每個像素的灰度,與平均值進行比較。大于或等于平均值,記為1;小于平均值,記為0。

            第五步,計算哈希值。

            將上一步的比較結果,組合在一起,就構成了一個64位的整數,這就是這張圖片的指紋。組合的次序并不重要,只要保證所有圖片都采用同樣次序就行了。

             =  = 8f373714acfcf4d0

            得到指紋以后,就可以對比不同的圖片,看看64位中有多少位是不一樣的。在理論上,這等同于計算"漢明距離"(Hamming distance)。如果不相同的數據位不超過5,就說明兩張圖片很相似;如果大于10,就說明這是兩張不同的圖片。

            具體的代碼實現,可以參見Wote用python語言寫的imgHash.py。代碼很短,只有53行。使用的時候,第一個參數是基準圖片,第二個參數是用來比較的其他圖片所在的目錄,返回結果是兩張圖片之間不相同的數據位數量(漢明距離)。

            這種算法的優點是簡單快速,不受圖片大小縮放的影響,缺點是圖片的內容不能變更。如果在圖片上加幾個文字,它就認不出來了。所以,它的最佳用途是根據縮略圖,找出原圖。

            實際應用中,往往采用更強大的pHash算法和SIFT算法,它們能夠識別圖片的變形。只要變形程度不超過25%,它們就能匹配原圖。這些算法雖然更復雜,但是原理與上面的簡便算法是一樣的,就是先將圖片轉化成Hash字符串,然后再進行比較。

            posted @ 2014-03-06 21:42 不會飛的鳥 閱讀(318) | 評論 (0)編輯 收藏

            [轉]TF-IDF與余弦相似性的應用(三):自動摘要

            有時候,很簡單的數學方法,就可以完成很復雜的任務。

            這個系列的前兩部分就是很好的例子。僅僅依靠統計詞頻,就能找出關鍵詞相似文章。雖然它們算不上效果最好的方法,但肯定是最簡便易行的方法。

            今天,依然繼續這個主題。討論如何通過詞頻,對文章進行自動摘要(Automatic summarization)。

            如果能從3000字的文章,提煉出150字的摘要,就可以為讀者節省大量閱讀時間。由人完成的摘要叫"人工摘要",由機器完成的就叫"自動摘要"。許多網站都需要它,比如論文網站、新聞網站、搜索引擎等等。2007年,美國學者的論文《A Survey on Automatic Text Summarization》(Dipanjan Das, Andre F.T. Martins, 2007)總結了目前的自動摘要算法。其中,很重要的一種就是詞頻統計。

            這種方法最早出自1958年的IBM公司科學家H.P. Luhn的論文《The Automatic Creation of Literature Abstracts》。

            Luhn博士認為,文章的信息都包含在句子中,有些句子包含的信息多,有些句子包含的信息少。"自動摘要"就是要找出那些包含信息最多的句子。

            句子的信息量用"關鍵詞"來衡量。如果包含的關鍵詞越多,就說明這個句子越重要。Luhn提出用"簇"(cluster)表示關鍵詞的聚集。所謂"簇"就是包含多個關鍵詞的句子片段。

            上圖就是Luhn原始論文的插圖,被框起來的部分就是一個"簇"。只要關鍵詞之間的距離小于"門檻值",它們就被認為處于同一個簇之中。Luhn建議的門檻值是4或5。也就是說,如果兩個關鍵詞之間有5個以上的其他詞,就可以把這兩個關鍵詞分在兩個簇。

            下一步,對于每個簇,都計算它的重要性分值。

            以前圖為例,其中的簇一共有7個詞,其中4個是關鍵詞。因此,它的重要性分值等于 ( 4 x 4 ) / 7 = 2.3。

            然后,找出包含分值最高的簇的句子(比如5句),把它們合在一起,就構成了這篇文章的自動摘要。具體實現可以參見《Mining the Social Web: Analyzing Data from Facebook, Twitter, LinkedIn, and Other Social Media Sites》(O'Reilly, 2011)一書的第8章,python代碼見github。

            Luhn的這種算法后來被簡化,不再區分"簇",只考慮句子包含的關鍵詞。下面就是一個例子(采用偽碼表示),只考慮關鍵詞首先出現的句子。

              Summarizer(originalText, maxSummarySize):

                // 計算原始文本的詞頻,生成一個數組,比如[(10,'the'), (3,'language'), (8,'code')...]
                wordFrequences = getWordCounts(originalText)

                // 過濾掉停用詞,數組變成[(3, 'language'), (8, 'code')...]
                contentWordFrequences = filtStopWords(wordFrequences)

                // 按照詞頻進行排序,數組變成['code', 'language'...]
                contentWordsSortbyFreq = sortByFreqThenDropFreq(contentWordFrequences)

                // 將文章分成句子
                sentences = getSentences(originalText)

                // 選擇關鍵詞首先出現的句子
                setSummarySentences = {}
                foreach word in contentWordsSortbyFreq:
                  firstMatchingSentence = search(sentences, word)
                  setSummarySentences.add(firstMatchingSentence)
                  if setSummarySentences.size() = maxSummarySize:
                    break

                // 將選中的句子按照出現順序,組成摘要
                summary = ""
                foreach sentence in sentences:
                  if sentence in setSummarySentences:
                    summary = summary + " " + sentence

                return summary

            類似的算法已經被寫成了工具,比如基于Java的Classifier4J庫的SimpleSummariser模塊、基于C語言的OTS庫、以及基于classifier4J的C#實現python實現

            posted @ 2014-03-06 21:37 不會飛的鳥 閱讀(278) | 評論 (0)編輯 收藏

            僅列出標題  下一頁
            丁香五月综合久久激情| 久久亚洲av无码精品浪潮| 亚洲精品无码专区久久同性男| 精品熟女少妇a∨免费久久| 亚洲欧美成人久久综合中文网| 久久久久久亚洲精品不卡| 91久久香蕉国产熟女线看| 热re99久久精品国产99热| 久久精品国产亚洲一区二区| 国产精品久久久久影视不卡| 久久精品国产亚洲av影院| 国产精品免费看久久久| 成人国内精品久久久久一区| 国产精品无码久久综合| 精品久久久久久| 国产免费福利体检区久久| 久久精品无码一区二区三区日韩 | 久久国产乱子伦精品免费强| 国产精品久久久久久一区二区三区 | 日韩人妻无码一区二区三区久久99 | 久久精品国产99久久丝袜| 久久国产精品免费一区| 伊人久久亚洲综合影院| 人人狠狠综合久久88成人| 丰满少妇人妻久久久久久| 四虎国产精品免费久久久| 久久久久久极精品久久久| 热久久最新网站获取| 国产美女久久精品香蕉69| 久久艹国产| 日韩精品久久无码中文字幕| 久久久久久免费一区二区三区| 久久精品不卡| 久久天天躁狠狠躁夜夜躁2O2O| 欧美精品一区二区精品久久 | 亚洲中文字幕久久精品无码APP| 久久精品国产亚洲AV电影 | 思思久久99热免费精品6| 无码人妻少妇久久中文字幕蜜桃| 久久中文娱乐网| 精品一二三区久久aaa片|