蛙蛙推荐:蛙蛙教你文本聚类

蛙蛙推荐:蛙蛙教你文本聚类

摘要:文本聚类是搜索引擎和语义web的基本技术,这次本蛙和大家一起学习一下简单的文本聚类算法,可能不能直接用于实际应用中,但对于想学搜索技术的初学者还是有一定入门作用的。这里会用到TF/IDF权重,用余弦夹角计算文本相似度,用方差计算两个数据间欧式距离,用k-means进行数据聚类等数学和统计知识。关于这些概念可以去google,或者参考文本后的参考链接。

思路:计算两篇文档的相似度,最简单的做法就是用提取文档的TF/IDF权重,然后用余弦定理计算两个多维向量的距离。能计算两个文本间的距离后,用标准的k-means算法就可以实现文本聚类了。

测试:首先我们准备以下数据
===================
奥运 拳击 入场券 基本 分罄 邹市明 夺冠 对手 浮出 水面
股民 要 清楚 自己 的 目的
印花税 之 股民 四季
杭州 股民 放 鞭炮 庆祝 印花税 下调
残疾 女 青年 入围 奥运 游泳 比赛 创 奥运 历史 两 项 第一
介绍 一 个 ASP.net MVC 系列 教程
在 asp.net 中 实现 观察者 模式 ,或 有 更 好 的 方法 (续)
输 大钱 的 股民 给 我们 启迪
Asp.Net 页面 执行 流程 分析
运动员 行李 将 “后 上 先 下” 奥运 相关 人员 行李 实名制
asp.net 控件 开发 显示 控件 内容
奥运 票务 网上 成功 订票 后 应 及时 到 银行 代售 网点 付款
某 心理 健康 站 开张 后 首 个 咨询 者 是 位 新 股民
ASP.NET 自定义 控件 复杂 属性 声明 持久性 浅析
==================
很明显以上数据可以分为三类:asp.net,奥运和股民,我们就写程序来实现它,各种算法的原理网上都有,我就大概只贴代码,声明一下,部分代码是从网上直接抄的,k-means代码是我从一篇文章的java示例代码转换过来的,我给代码加了不少注释,希望能帮助大家理解。

以下是入口函数

static void Main(string[] args)
{
    
//1、获取文档输入
    string[] docs = getInputDocs("input.txt");
    
if (docs.Length < 1)
    
{
        Console.WriteLine(
"没有文档输入");
        Console.Read();
        
return;
    }


    
//2、初始化TFIDF测量器,用来生产每个文档的TFIDF权重
    TFIDFMeasure tf = new TFIDFMeasure(docs, new Tokeniser());

    
int K = 3//聚成3个聚类

    
//3、生成k-means的输入数据,是一个联合数组,第一维表示文档个数,
    
//第二维表示所有文档分出来的所有词
    double[][] data = new double[docs.Length][];
    
int docCount = docs.Length; //文档个数
    int dimension = tf.NumTerms;//所有词的数目
    for (int i = 0; i < docCount; i++)
    
{
        
for (int j = 0; j < dimension; j++)
        
{
            data[i] 
= tf.GetTermVector2(i); //获取第i个文档的TFIDF权重向量
        }

    }


    
//4、初始化k-means算法,第一个参数表示输入数据,第二个参数表示要聚成几个类
    WawaKMeans kmeans = new WawaKMeans(data, K);
    
//5、开始迭代
    kmeans.Start();

    
//6、获取聚类结果并输出
    WawaCluster[] clusters = kmeans.Clusters;
    
foreach (WawaCluster cluster in clusters)
    
{
        List
<int> members = cluster.CurrentMembership;
        Console.WriteLine(
"-----------------");
        
foreach (int i in members)
        
{
            Console.WriteLine(docs[i]);
        }


    }

    Console.Read();
}



以下是分词器的主要代码

/// <summary>
/// 以空白字符进行简单分词,并忽略大小写,
/// 实际情况中可以用其它中文分词算法
/// </summary>
/// <param name="input"></param>
/// <returns></returns>

public IList<string> Partition(string input)
{
 Regex r
=new Regex("([ //t{}():;. /n])");  
 input
=input.ToLower() ;

 String [] tokens
=r.Split(input);          

 List
<string> filter=new  List<string>() ;

 
for (int i=0; i < tokens.Length ; i++)
 
{
  MatchCollection mc
=r.Matches(tokens[i]);
  
if (mc.Count <= 0 && tokens[i].Trim().Length > 0       
   
&& !StopWordsHandler.IsStopword (tokens[i]) )        
   filter.Add(tokens[i]) ;
        }

 
 
return filter.ToArray();
}



以下是kmeans算法的基本代码

public class WawaKMeans
{
    
/// <summary>
    
/// 数据的数量
    
/// </summary>

    readonly int _coordCount;
    
/// <summary>
    
/// 原始数据
    
/// </summary>

    readonly double[][] _coordinates;
    
/// <summary>
    
/// 聚类的数量
    
/// </summary>

    readonly int _k;
    
/// <summary>
    
/// 聚类
    
/// </summary>

    private readonly WawaCluster[] _clusters;

    
internal WawaCluster[] Clusters
    
{
        
get return _clusters; }
    }
 

    
/// <summary>
    
/// 定义一个变量用于记录和跟踪每个资料点属于哪个群聚类
    
/// _clusterAssignments[j]=i;// 表示第 j 个资料点对象属于第 i 个群聚类
    
/// </summary>

    readonly int[] _clusterAssignments;
    
/// <summary>
    
/// 定义一个变量用于记录和跟踪每个资料点离聚类最近
    
/// </summary>

    private readonly int[] _nearestCluster;
    
/// <summary>
    
/// 定义一个变量,来表示资料点到中心点的距离,
    
/// 其中—_distanceCache[i][j]表示第i个资料点到第j个群聚对象中心点的距离;
    
/// </summary>

    private readonly double[,] _distanceCache;
    
/// <summary>
    
/// 用来初始化的随机种子
    
/// </summary>

    private static readonly Random _rnd = new Random(1);

    
public WawaKMeans(double[][] data, int K)
    
{
        _coordinates 
= data;
        _coordCount 
= data.Length;
        _k 
= K;
        _clusters 
= new WawaCluster[K];
        _clusterAssignments 
= new int[_coordCount];
        _nearestCluster 
= new int[_coordCount];
        _distanceCache 
= new double[_coordCount,data.Length];
        InitRandom();
    }


    
public void Start()
    
{
        
int iter = 0;
        
while (true)
        
{
            Console.WriteLine(
"Iteration " + (iter+++ "");
            
//1、重新计算每个聚类的均值
            for (int i = 0; i < _k; i++)
            
{
                _clusters[i].UpdateMean(_coordinates);
            }


            
//2、计算每个数据和每个聚类中心的距离
            for (int i = 0; i < _coordCount; i++)
            
{
                
for (int j = 0; j < _k; j++)
                
{
                    
double dist = getDistance(_coordinates[i], _clusters[j].Mean);
                    _distanceCache[i,j] 
= dist;
                }

            }


            
//3、计算每个数据离哪个聚类最近
            for (int i = 0; i < _coordCount; i++)
            
{
                _nearestCluster[i] 
= nearestCluster(i);
            }


            
//4、比较每个数据最近的聚类是否就是它所属的聚类
            
//如果全相等表示所有的点已经是最佳距离了,直接返回;
            int k = 0;
            
for (int i = 0; i < _coordCount; i++)
            
{
                
if (_nearestCluster[i] == _clusterAssignments[i])
                    k
++;

            }

            
if (k == _coordCount)
                
break;

            
//5、否则需要重新调整资料点和群聚类的关系,调整完毕后再重新开始循环;
            
//需要修改每个聚类的成员和表示某个数据属于哪个聚类的变量
            for (int j = 0; j < _k; j++)
            
{
                _clusters[j].CurrentMembership.Clear();
            }

            
for (int i = 0; i < _coordCount; i++)
            
{
                _clusters[_nearestCluster[i]].CurrentMembership.Add(i);
                _clusterAssignments[i] 
= _nearestCluster[i];
            }

            
        }


    }


    
/// <summary>
    
/// 计算某个数据离哪个聚类最近
    
/// </summary>
    
/// <param name="ndx"></param>
    
/// <returns></returns>

    int nearestCluster(int ndx)
    
{
        
int nearest = -1;
        
double min = Double.MaxValue;
        
for (int c = 0; c < _k; c++)
        
{
            
double d = _distanceCache[ndx,c];
            
if (d < min)
            
{
                min 
= d;
                nearest 
= c;
            }

      
        }

        
if(nearest==-1)
        
{
            ;
        }

        
return nearest;
    }

    
/// <summary>
    
/// 计算某数据离某聚类中心的距离
    
/// </summary>
    
/// <param name="coord"></param>
    
/// <param name="center"></param>
    
/// <returns></returns>

    static double getDistance(double[] coord, double[] center)
    
{
        
//int len = coord.Length;
        
//double sumSquared = 0.0;
        
//for (int i = 0; i < len; i++)
        
//{
        
//    double v = coord[i] - center[i];
        
//    sumSquared += v * v; //平方差
        
//}
        
//return Math.Sqrt(sumSquared);

        
//也可以用余弦夹角来计算某数据离某聚类中心的距离
        return 1- TermVector.ComputeCosineSimilarity(coord, center);

    }
 
    
/// <summary>
    
/// 随机初始化k个聚类
    
/// </summary>

    private void InitRandom()
    
{
        
for (int i = 0; i < _k; i++)
        
{
            
int temp = _rnd.Next(_coordCount);
            _clusterAssignments[temp] 
= i; //记录第temp个资料属于第i个聚类
            _clusters[i] = new WawaCluster(temp,_coordinates[temp]);
        }

    }

}



以下是聚类实体类的定义

internal class WawaCluster
{
    
public WawaCluster(int dataindex,double[] data)
    
{
        CurrentMembership.Add(dataindex);
        Mean 
= data;
    }


    
/// <summary>
    
/// 该聚类的数据成员索引
    
/// </summary>

    internal List<int> CurrentMembership = new List<int>();
    
/// <summary>
    
/// 该聚类的中心
    
/// </summary>

    internal double[] Mean;
    
/// <summary>
    
/// 该方法计算聚类对象的均值 
    
/// </summary>
    
/// <param name="coordinates"></param>

    public void UpdateMean(double[][] coordinates)
    
{
        
// 根据 mCurrentMembership 取得原始资料点对象 coord ,该对象是 coordinates 的一个子集;
        
//然后取出该子集的均值;取均值的算法很简单,可以把 coordinates 想象成一个 m*n 的距阵 ,
        
//每个均值就是每个纵向列的取和平均值 , //该值保存在 mCenter 中

        
for (int i = 0; i < CurrentMembership.Count; i++)
        
{
            
double[] coord = coordinates[CurrentMembership[i]];
            
for (int j = 0; j < coord.Length; j++)
            
{
                Mean[j] 
+= coord[j]; // 得到每个纵向列的和;
            }

            
for (int k = 0; k < Mean.Length; k++)
            
{
                Mean[k] 
/= coord.Length; // 对每个纵向列取平均值
            }

        }

    }

}



计算TF/IDF和利用余弦定理计算相似度的代码见完整版的代码下载,那两部分都是外国人写的,里面有它的联系方式,不懂的可以问他,反正我差不多懂了。

下面看看咱们的测试结果:
Iteration 0...
Iteration 1...
Iteration 2...
-----------------
奥运 拳击 入场券 基本 分罄 邹市明 夺冠 对手 浮出 水面
杭州 股民 放 鞭炮 庆祝 印花税 下调
残疾 女 青年 入围 奥运 游泳 比赛 创 奥运 历史 两 项 第一
运动员 行李 将 “后 上 先 下” 奥运 相关 人员 行李 实名制
奥运 票务 网上 成功 订票 后 应 及时 到 银行 代售 网点 付款
-----------------
股民 要 清楚 自己 的 目的
印花税 之 股民 四季
输 大钱 的 股民 给 我们 启迪
某 心理 健康 站 开张 后 首 个 咨询 者 是 位 新 股民
-----------------
介绍 一 个 ASP.net MVC 系列 教程
在 asp.net 中 实现 观察者 模式 ,或 有 更 好 的 方法 (续)
Asp.Net 页面 执行 流程 分析
asp.net 控件 开发 显示 控件 内容
ASP.NET 自定义 控件 复杂 属性 声明 持久性 浅析
聚类聚的非常准确,而且只迭代了3次,模型就收敛了,当然了这是最理想的效果,其实聚类的结果受好多种因素制约,提取特征的算法,随机初始化函数,kmeans算法的实现等,都有优化的地方,不信你把输入的数据的顺序改改,聚类结果就不一样了,或者把随机数的种子变一下,结果也不一样,k-means算法加入一些变异系数的调整,结果也不一样,提取特征的地方不用TF/IDF权重算法用别的,结果肯定也不一样。
完整代码里还有另一组测试数据,结果也很不错,我的意思是我的算法不是针对一组测试数据,而是针对好多数据都有不错的结果。

总结:数学和英语真是写程序之根本呀,弄这个东西遇到了好多英语单词不会,查还查不出来,也理解不了,最后google一看,是个数学专用词,再搜索这个数学专用词的中文解释,发现还是理解不了那数学原理。所以还是得多学习数学和英语。

参考链接:
K-MEANS算法
http://beauty9235.javaeye.com/blog/161675
什么是变异系数
http://zhidao.baidu.com/question/15013015.html
TF/IDF实现
http://www.codeproject.com/KB/cs/tfidf.aspx

源码下载:WawaTextCluster.zip

蛙蛙推荐:蛙蛙牌儿IIS备份器

05-18

蛙蛙推荐:蛙蛙牌儿IIS备份器rn把下面所有代码保存成一个.hta文件,然后双击就可以了rnrnrn New Document rn rn rnrnrn备份名称 rnrnrnrnrnrn rn rn 问:这个小软件有什么用?rn 答: 这个小软件用来手工备份和还原IIS配置,如果IIS元数据库严重受损,IIS 将无法启动,这个小软件可以做的小帮手,帮助你在IIS出错的时候选择合适的备份来恢复IIS配置.rn 问:重装机器后如何恢复IIS配置?rn 答: 做虚拟主机的时候如果重装了系统,一般需要手工一个一个的添加网站,网站很多的话,非常费时费力,而象IIS备份精灵,IISExport都需要注册才能无限制使用.其实在Internet 信息服务管理器控制台(也称为 IIS 插件)中所设置的属性和值默认情况都储存在 C:\winnt\system32\inetsrv\metabase.bin (如果是windows 2003+iis6是C:\WINDOWS\system32\inetsrv\MetaBase.xml)文件中。可以简便地使用 xcopy、scopy 或任何其他复制程序来复制这个 文件。但这时最好先停止 Internet 服务(在cmd命令模式下里键入 net stop "IIS Admin Service"来停止Internet服务,net start "IIS Admin Service"来启动IIS服务,或者在服务管理器在界面模式下操作,),以保证元数据库是最新的并且不在使用状态中。 重装系统的时候先停止internet服务,然后覆盖那个目录,IIS配置就恢复了.rn rn rn rn 问:请提供一些关于IIS管理和编程的文章rn rn 答: rn 1.Internet Information Services SDK rn http://msdn.microsoft.com/library/default.asp?url=/library/en-us/iissdk/html/b1073d67-0cfd-42cb-b62b-97e670a4eafb.asp rn 2.Backing Up and Restoring the IIS Metabase rn http://www.windowsitpro.com/Web/Article/ArticleID/9159/Web_9159.htmlrn 3.有关 Internet 信息服务的 5 个热点问题及其解答 rn http://www.microsoft.com/china/technet/community/columns/insider/iisi0602.mspxrn 4.IIS 内幕 rn http://www.microsoft.com/china/technet/community/columns/insider/default.mspxrn 5.Internet Information Services rn http://msdn.microsoft.com/library/default.asp?url=/library/en-us/iissdk/html/cd7a8a8d-dc9d-45be-b5f8-b7d548053b3f.asprn rn rn rnrnrnrn

蛙蛙推荐:蛙蛙Delphi学习经验(第一版)

01-21

蛙蛙推荐:蛙蛙Delphi学习经验(第一版)rn  一、使用ModelMaker设计类,并生成设计文档。rn  ModelMaker是Delphi7自带的一个UML建模软件,支持和Delphi IDE的双向工程,没用过Rose For Delphi的插件。我直接用的MM6,感觉MM就挺好用的。rn  你可以在Classes视图里添加类,并添加类的方法、字段、属性、字段,这些操作都很简单,在Classes视图的上边类部分右键点击添加类,然后选中这个类,点击下面的各个添加成员按钮,按向导就可以给类添加成员了。rn  要想生成代码,请点击Units视图,在Units视图的空白处右键点击添加新的单元,在弹出的Edit Unit对话框里,在Relative Unit file name后面点击Browse按钮选择单元保存的路径,其它参数你看着选择,然后点击OK。然后在Units视图里把刚刚添加的类从Classes not assignet to units拖到新建的单元下面。选中刚刚添加的单元,确保最上面按钮条的开着的小锁按钮处于按下状态(代码生成解锁,如果不选中,则代码处于保护状态,无法生成代码。)点击Units视图上的闪电按钮生成代码。点击菜单Delphi-locate in delphi,就可以在delphi里打开刚刚生成的代码了,你可以在实现部分写你要写的代码,因为mm只给你生成了一个类的架子,我们在代码的接口部分再添加一些字段和方法,以及注释,然后点击菜单ModelMaker-jump to modelmaker,又切换到mm界面了。在Units视图里点击刷新按钮(和浏览器的刷新按钮类似的那个按钮),你会发现刚刚在delphi里添加的字段和方法已经同步到了mm里,然后你再在MM里修改类,再生成代码,你会发现其它部分都生成的很好,就是接口部分你给成员写的注释给没了,这就不爽了,好歹也是你手工写的注释代码,为啥MM给你弄没了捏,一会儿再给大家介绍解决办法。rn  模型有了,我们还要生成模型的文档,以便和别人交流。在左边点击到单元视图,右边点击到Documentation视图。在左边选中单元,在右边的文档视图的下拉菜单里选择Unit,然后在下面大的文本编辑区域里写关于单元的注释文本。在左边点击类,右边下拉列表里选择Class,添加类的注释,在左边选择类的成员,右边下拉列表里选择Class Member,添加类成员的注释。OK,把右边的下拉列表选到unit,点击下拉列表左边的问号按钮,在弹出的对话框里,选择文档的保存路径,和是否生成私有成员的注释,然后点击OK就生成文档了。打开刚刚生成文档的目录,你会发现一个rtf文件和一个hpj文件,打开rtf文件一看,原来不支持中文,这可咋办,这可咋好,这可咋整,别着急,从网上下载个Microsoft Help WorkShop装上,双击生成的.hpj项目,打开后点击存盘并编译按钮,就生成一个hlp文件了,这不就是你要的文档吗?多好呀,和MSDN似的。.net的ndoc也不过如此嘛。rn  生成文档有个缺点,我给大家说说哦,你在文档视图里给类添加的描述(One Line)是不是想生成到代码里作为注释以提高代码的可读性呀,我也想呀,可是找了半天帮助也不直到怎么生成。还有,你是不是想在代码里写一定的注释,让mm同步的时候直接同步到文档视图里显示呀,这样你就可以在delphi里写注释,用mm来生成专业文档了,这个应该也能搞成,但我还是没搞成,你写的注释要符合MM的格式,大括号的那种注释。在MM的Project option对话框的Source Doc Generation标签里有生成注释和同步注释的一些设置,然后在帮助里有关于类注释、成员注释的一些语法,你可以看看,没准儿能鼓捣成呢,我主要是没时间琢磨了。其实这两个不算是MM的缺点,只能怪我没研究出来了。还有一个缺点,就是MM在生成文档的时候只能生成一个单元的,我晕,这个问题困扰了我好久,最终在它的网站上下载了一个插件装到MM上才搞定,这插件免费的哦。http://www.modelmakertools.com/modelmaker/plugins.html。下载那个HelpExpert插件,解压后把Expert目录下的OCHelpExpertMM6.dll拷贝到mm6安装目录的Expert目录下,把bin目录下的rtf文件拷贝到mm6安装目录的bin目录下。点击MM的tools菜单的Expert Manager子菜单,在打开的对话框里看看有没有多了一个OCHelpExpertMM6.dll,打上勾,发现tools里多了一个子菜单Geneater help form all units,点击就可以生成所有单元的文档了,cool吧。rn  MM的基本功能咱就挖掘到这儿吧,另一个常用的功能就是它的UML图视图,在这里你可以把你设计好的类,拖上去,以表示它们之间的关系给你的开发团队成员看。你刚拖上去的类也只显示一个类名,没有成员,你在这个类上点击右键,选择dragram properties,在打开的对话框里,把Class symbol member filter组合框的Project member type filter复选框的勾去掉,然后把custom member type filter下面的勾打上就可以显示成员了,这下你就可以画UML图来表达你的设计了,我想最常用的也是类图和组件图吧,其它的图我没用过。MM的功能还很强大,可以直接根据设计模式生成代码的架构,还有用宏自定义生成代码等功能,等你觉得以上我介绍的功能不够用的时候你可以学习学习,网上mm的教程不多,但也有几个比较好的。比如《ModelMake初探.rar》,比如《MM(ModelMaker)两小时上手指南》,还有大富翁整理的关于MM应用设计模式的一个教程。rn  总的来说mm对delphi的开发还是很有帮助的,和delphi结合紧密,比visio和vs结合还好,建议delphi的新手学学。老手一般不用这个,直接手工写就可以了,但如果想节省时间的话也可以用用。rn  二、使用OutputDebugString进行跟踪输出rn  无论哪一种语言和编辑工具,使用好它的调试功能都是非常必要的。在Delphi里可以使用OutputDebugString API函数输出调试信息,然后在调试运行的时候可以打开view-debug windows-event log菜单,查看你跟踪输出的信息。如果软件编译成二进制执行代码后,可以去微软下载个你debugview(下载地址自己搜索一下吧,微软绝对免费下载),用它去查看调试信息。在用Delphi调试的时候用debugview看不到信息的,因为被delphi的Event log给截获了。DebugView里可以使用命令行来启动,并把获取的信息输出到一个文本文件,还可以用命令行自动运行,每天生成一个日志,具体命令行参数可以看帮助。另外DebugView非常适合监控分布式程序,它有远程监控功能,你可以在你的机器上监控到运行在远程机器上的软件的调试信息,前提是你在那个服务器上用命令行启动一个debugview服务器,这个我没有试过哦,人家帮助里说的,现在DebugView刚出新版本,支持64位系统。有了它,你就可以简化你的日志方案了,你可以轻轻松松了解你的软件的运行状态,根本不用自己鼓捣一套。另外你也可以自己开发一个截获OutputDebugString输出消息的小软件,网上有资料,这样你就可以自定义显示你的调试信息和跟踪信息了。rn  三、使用断言,给断言处理事件挂接一个处理错误的回调函数。rn  我们.net程序员都知道,像c#,java这样的语言编译后不是二进制代码,而是可以解释执行的一种东西,这样在我们调试的时候,如果出错了,报的错可以显示错误所在的堆栈,方法,行号(当然,你release编译或者把pdb调试文件去了就没行号了),而Delphi就不行了。如果你在异常处理里想把异常出现的堆栈,方法和行号输出出来简直是不可能的。但有个你变通的方法,就是使用断言,断言在调试状态下可以指出错误行号(其实我编译生成exe后好像也可以输出源文件的行号,我不知道为什么)。我们要先为断言失败事件挂接一个默认执行方法,如下。rnprocedure AssertErrorHandler(const Message, Filename: string; LineNumber: Integer; ErrorAddr: Pointer);rnvarrn S: String;rnbeginrn S := Format('%s (%s, line %d, address $%x)',rn [Message, Filename, LineNumber, Pred(Integer(ErrorAddr))]);rn OutputDebugString(PChar(S));rnend;rnrninitializationrn AssertErrorProc := @AssertErrorHandler rn然后你就可以使用断言了, Assert(pam1 <> nil,'参数为空');比如你的方法要传入一个pam1的参数,如果传进来的参数未定义,这个断言就会失败,从而触发断言失败事件,然后你就可以用debugview看到断言失败的行号了。rn  使用这个方法的不足就是,你只能获取可预期异常的信息,对未知异常无法使用了。rn

蛙蛙推荐:置疑纯ORM方案

07-23

蛙蛙推荐:置疑纯ORM方案 rn纯orm方案的缺点:rn处理复杂对象查询时有难度。数据模型中的所有表格及关系很复杂,包括关联、引用(也就是主从表)和继承三种关系,甚至包括嵌套的复杂关系,在做or mapping的时候非常复杂。rn对应于表中的连接查询,如果直接写sql语句违背了map 规则,不写,灵活度不够。虽然在一些ORM方案中也可以把数据库表之间的关系也映射到对象层里,但是这样在数据库和数据访问层之间增加了一个映射层,再说了映射用的元数据都是XML格式的,访问这个XML文件的时候还得做额外操作,性能肯定要降低了(当然可以在应用程序启动的时候把元数据都缓存起来)。而且那个什么Opath语言,我看不出来和t-sql相比,他有什么优势,Xpath是查询XML用的,它用来查询Object,但是它的Opath查询,最后还是得转换成SQL来查询数据库呀,而且很可能一条Opath语句查下去可能得生成好几条SQL语句,而且它生成的SQL语句有你自己写的语句可靠吗,有你自己写的sql语句性能好吗?我看呀,以后这人们就都不用学sql了,直接学这个Opath就行了,到时候只有高手才会写SQL语句,就象初学者只会调用.NET封装好的类一样,而不知道这些类是怎么封装了COM和API的。我不是说这不好哦,这样做入门是简单了,可是人们被迷惑了似的,多年以后肯定只有微软自己的人才知道这些类的实现原理,而人们想接触一下底层的东西就很难了,我担心有一天我们的精力不够了,从懂事就开始学编程,一直学到40岁了才算入门,还啥都用不上,因为知识太多了,要想实现一个简单程序就得学10几年的知识,这不是悲哀吗,呵呵,撤远了哦。rnrn下面说说我的想法哦:rn我感觉将关系数据映射到业务实体,还是手工或者半手工做的比较好,因为你完全靠工具来生成OSD,RSD,MSD这些文件肯定不能完全覆盖你在领域分析后得出的问题域,工具哪儿有人智能呀,人有时候还分析不出来呢?rnrn而数据库层面的的业务逻辑实现(细粒度的)还是用存储过程来做比较好,而靠Opath这类的语言生成的sql语句肯定不能达到最优化(你能相信DataAdapter自己生成的数据更新sql语句吗?还是使用自定义命令呀?),而且在集合处理方面还存在问题(你说在内存里建立1000个对象的实例和建立一个有1000行的DataTable哪个开销大?),orm是建立在ADO.NET之上的,以后人们是不是连ADO.NET也不用学了呀,直接把对象组成的ArrayList绑定到DataGrid上呀?rnrn现在这人是越来越懒了,我感觉写一个程序最重要的工作还是业务建模和写业务层的代码,这个层的代码估计除了MDA没什么工具可以生成的吧(个人感觉MDA也不是很合适的软件开发方法)?当然了,向orm这些东西属于次要复杂性,本应该是由工具和语言来完成,而不应该耗费程序员的精力(谁也不会否定面向对象的软件开发比结构化编程有优势,谁也不会否定递归算法很巧妙,尽管它耗费资源),但是我感觉还没成熟到那种全自动的程度吧,在全自动生成和全手工编写之间我们得找一个平衡点儿。rnrn我们的目标是在数据库从sqlserver换成oracle,一个表里增加了两个字段的时候,客户要求再给某个对象增加一个方法的时候我们只要改动数据访问层和业务实体层就可以了,而尽量不改动业务层和表示层的代码,也就是维护方便。我们想利用ORM来达到这种效果,我们认为数据实体层和数据访问层只改动元数据就可以完成维护工作了,可是这些东西用了大量的反射(性能降低了),而业务层代码和UI不用改了吗,这怎么可能呢?你数据表里增加个字段,你的业务层的方法签名不需要改吗(就算你用业务实体(业务实体根据xml确定)做传入参数,但是方法里面处理的时候你不得变呀)?还有你的界面显示的时候不还得变呀,你不做客户端的数据验证代码了吗?这问题多着呢,哪儿能那样一劳永逸呀?现在还除了好多代码生成的工具,甚至界面也能靠XML生成了,人都考虑不过来的东西,让工具去考虑,真是的。rnrn我想我们一定是厌倦了在c#里写那么多重复繁琐的sql处理代码了,我们是想把处理sql或者存储过程的这些代码自动化,我们不想为每个存储过程的调用手工创建一大堆参数,然后给参数赋值,然后再调用DAAB执行,还得考虑参数缓存什么的,我们肯定讨厌这些重复工作了,我们想存储过程修改(无论是存储过程本身改变还是从sql的存储过程改变成oracle的存储过程)后我们的数据访问层能自动适应,自动创建参数,自动赋值。如果想这样,就得给存储过程建立元数据,而不是给表建立元数据,表对应的是Object(实际物体,订单啦,产品啦),而存储过程对应的是Action(例如增加或者取消一个订单)或者Service(例如根据某个条件来生成一个DataView)的数据层实现,当然有人说了“随着在存储过程中实现的业务逻辑的增多,存储过程可以简化维护带来的优势会逐渐减弱”,可是那也比纯用Opath这类更高层次的语言来实现数据访问有优势吧,再说了存储过程只封装细粒度的业务逻辑,返回简单处理后的数据,然后由数据层经过复杂处理才提供给业务层的。我估计光Opath也不能完成粗粒度的业务逻辑吧,也得把结构提供给业务层吧?rnrn我设想了一个架构(抄的,不过感觉很灵活):rnMonitorServices:负责监控和跟踪。rnMonitorServicesLogging:负责日志记录和错误处理。rnConfigurationServices:负责配置服务,从.config里读取配置数据。rnCMPServices:提供CMP处理rn这几个项目提供底层服务,微软发布了7中企业开发模块,里面几乎都包含了上面我提到的这些东西(除了最后一个),我感觉我提出的这几个是必要的,比较通用,加密解密和缓存是可选的,有的项目里用不到吧,看项目需要再做添加。rnrn别的好说,微软的企业开发模块里都包括了,在设计模式和实际操作上都给了一些指导,微软好像有视频下载,下载下来看看,理解一下,再参考MSDN的类库开发人员指南,写出自己的类库就行了。rn着中看一下CMPServices类库,在这里执行操作存储过程,首先肯定得有个类负责读取存储过程的元数据,元数据里包括了存储过程的名字,参数的信息,以及对应的业务实体类(业务实体类可以用代码生成器生成再改改),这些元数据放在.config里,如果这些东西改了,调用存储过程的代码自动就改了。而不象petshop里那样为oracle和sqlserver写两个dal层,然后用通过接口和工厂类来确定使用哪个类库,这还算好的,普通的方案的BLL层里调用DAL层的方法的时候用的是RunSqlserverSP(strSP)或者RunOracleSP(strSP)这样的方法,DAL层和BLL层是紧密耦合,这些都属于托管组件持久性(Component Managed Persistence),而我想用的是托管容器持久性(Container Managed Persistence)。当然这个类库里还要设计好几个类来提供业务实体的持久性,简单介绍一下。rnCMPConfigurationHandler:读取.config里的XML数据生成容器映射和命令映射类,并缓存起来。rnCommandMapping:命令映射类,存储过程的名称,参数等,对应一个命令。 rnCommandParameter :存储过程的参数,包括参数的类型,长度,方向等rnContainerMapping :容器映射类。rnContainerMappingSet :一组容器映射类。rnPersistableObject :持久类,和业务实体类差不多。rnPersistableObjectSet 一组业务实体类,如果一个select语句返回1000个记录,与其生成1000个PersistableObject的实例,不如保存在Dataset里。rnStdPersistenceContainer:这是个容器基类,定义实现容器映射的成员,每个容器可以执行CRUD四个命令。rnSqlPersistenceContainer :sqlserver的容器实现类,这里利用反射和XML元数据来建立存储过程元数据和持久类之间的关系。rnrn写业务逻辑层的时候可以实力一个容器,然后调用容器的CRUD命令来执行操作,实际上这就是去执行存储过程了,而存储过程里你再去写更细的逻辑,给CRUD命令传递参数的时候可以把这个容器对应的持久类的实例传进去,然后容器自己就会处理持久类实例的属性和存储过程参数的对应关系。这样就相当于把存储过程包装起来了,也在持久对象和数据库间建立了一个层,但这个层比纯ORM的方案灵活吧,你也不用opath这类晦涩的语言执行关系数据库查询,存储过程你爱怎么写怎么写,只要能灵活应用这种架构,就能适应无穷无尽的业务逻辑。如果数据库变了,至少在数据访问层可以做很小修改就能完成维护工作,改元数据呗,至于由于数据库变化导致的业务层和其它中间层,显示层的改动可以通过其它技巧减少一些,肯定不可能一劳永逸的,我们差完全自动生成代码还远呢,你的基础架构越强大,灵活性就越差我感觉,因为动些越多,耦合性越强,到时候架构功能太多,我想去一项服务也不行,本身就难以维护了,是吧,就象.NET框架,部署的时候还得把它部署进去,20多M的东西,我写一愕几十K的程序得部署一个20多M的框架,我能不生气吗?rnrn别人置疑.NET社区,偶就来置疑一下纯ORM吧,呱呱。rn

蛙蛙推荐:保护你的程序

05-08

蛙蛙推荐:保护你的程序rn我们知道windowsXP现在使用了在线激活的功能来实现保护正版的功能,它是通过你的硬件的一些标识生成一个原始数据(通常是一个字符串),然后把这个原始字符串通过inernet发送到ms网站的一个程序里,而这个程序接受到这个原始字符串后经过一些不为人知的算法处理后再发送给你的机器,我想或多或少是在你的机器上做了一些手脚,我们假设在你的硬盘生成一段密文。然后你的机器启动的时候就读取你的硬件标识并还生成那个原始数据,在本地还是经过那个不为人知的算法后得到一段密文,然后用刚刚生成的密文和在线激活的时候生成到你硬盘上的那段密文比较,如果相同,那你就能进入桌面,如果不同,那你就进不了桌面,而如果你机器更改了硬件配置,生成密文的原始数据就变了,所以密文就不同了,你就需要重新算号了。rn当然了,我是把问题描述简单化了,winxp是把激活文件放在一个特殊文件里了,要不我们破解winxp和window 2003的时候也不替换那几个文件了,另外我还有意忽略了一些在线激活的细节。rn我们可以把这个激活技术推广到我们自己的软件中去,毕竟我们程序员最不愿意看到的就是我们费了很久的时间,投入了很大的精力开发出的软件在被人们疯狂的拷贝使用,并且没有给你一点好处,尽管这是事实,我们还是要做一些措施的。rn微软的用以确定硬件杂凑编码的10项硬件设备特征分别是:显示适配器、SCSI适配器、IDE适配器、网络适配器MAC地址、RAM容量范围(例如0至64兆或64至128兆)、处理器类型、处理器序列号、硬盘驱动器设备、硬盘驱动器卷序列号、CD-ROM/CD-RW/DVD-ROM。我们就不要这么复杂了,我们只要其中3个就行了。rnrn一.获取硬件杂凑编码rn把下面的代码复制到剪贴版里,并保存成一个.vbs文件,这个脚本文件用来获取你的机器的CPUID,硬盘的PNP DeviceID,和网卡MAC号,得到的这3个字符串都能代表你的机器特征,而3个字符串组合起来几乎是就是表示你的机器的一个全球唯一标识。按理说,无论你重装系统,还是格式化硬盘,得到的这个组合字符串是唯一不变的,我们可以用它作为我们生成软件安装序列号的原始数据,当然这个组合字符串是很长的,你可以把这个字符串进行摘要算法处理后作为原始数据,比说把把这个字符串进行MD5加密。rnOn Error Resume NextrnrnstrComputer = "."rnstrOut = ""rnSet objWMIService = GetObject("winmgmts:" _rn & "impersonationLevel=impersonate!\\" & strComputer & "\root\cimv2")rnrn'获取网卡的MAC地址,因为一个机器可能有多个网络适配器,所以我们只获取AdapterType值为"Ethernet 802.3"的适配器MAC号,也就是AdapterTypeID=0的网络适配器,网卡号其实不是很保险,有的小网卡厂商生产的网卡可能会有重复的.rnSet colItems = objWMIService.ExecQuery("Select * from Win32_NetworkAdapter")rnFor Each objItem in colItems rn If objItem.AdapterTypeID = 0 Then strOut = strOut & "MAC Address: " & objItem.MACAddress &vbcrlfrnNextrn'获取CPU的序列号,这个号可不是全球唯一哦,有可能一批CPU使用一个序列号rnSet colItems = objWMIService.ExecQuery("Select * from Win32_Processor")rnFor Each objItem in colItemsrn strOut = strOut & "Processor ID: " & objItem.ProcessorId&vbcrlfrnNextrn'获取硬盘的PNP DeviceID,这个号和传说中的磁盘序列号差不多,应该是也是唯一的,大家都把这个值给偶发上来偶看看.rnSet colDiskDrives = objWMIService.ExecQuery _ rn ("Select * from Win32_DiskDrive")rnFor each objDiskDrive in colDiskDrivesrn strOut = strOut & "DiskDrives PNP DeviceID: " & vbTab & objDiskDrive.PNPDeviceID&vbcrlfrnNextrn'建立一个IE对象,并利用它的功能巧妙的把输出字符串复制到剪贴版里.rnSet objIE = CreateObject("InternetExplorer.Application")rnobjIE.Navigate("about:blank")rnobjIE.document.parentwindow.clipboardData.SetData "text", strOutrnobjIE.QuitrnWscript.Echo strOutrn以上是通过WSH脚本来获取硬件字符串的,我们让用户复制这个字符串,并通过你提供的在线算号的web程序里算号,当然你可以在算号前要求对方做一些事,比如说点击一下广告啦,照顾你一些money啦什么的。rn

蛙蛙请教:CreateParameter的type选择问题

07-22

蛙蛙请教:CreateParameter的type选择问题rn在创建参数的时候要指定参数的类型,可是我发现adovbs.inc里定义的数据类型和sqlserver里定义的数据类型并不是一一对应的,请问一下下面rnrn的类型各对应adovbs.inc里的哪个编号呀rnbitrncharrndatetimernintrnbigintrnsmallintrncharrnvarcharrnncharrnnvarcharrnmoneyrntextrnntextrnbinaryrnvarbinaryrn有的类型我没有列出来,我只列了几个常用的,大家谁要是知道的话,请在我上面列出的类型的后面写上它的序号,谢谢大家了.rn下面是adovbs.inc里定义参数类型的部分,你可以参照一下下面的东西我现在只用过varchar,我用的代码是202,有人说用200,晕,还是大家给偶rnrn个正确答案吧,是不是有varchar既可以用200(adVarChar)也可以用202(adVarWChar)呀,这里有没有什么说法呀?rnrnConst adEmpty = 0rnConst adTinyInt = 16rnConst adSmallInt = 2rnConst adInteger = 3rnConst adBigInt = 20rnConst adUnsignedTinyInt = 17rnConst adUnsignedSmallInt = 18rnConst adUnsignedInt = 19rnConst adUnsignedBigInt = 21rnConst adSingle = 4rnConst adDouble = 5rnConst adCurrency = 6rnConst adDecimal = 14rnConst adNumeric = 131rnConst adBoolean = 11rnConst adError = 10rnConst adUserDefined = 132rnConst adVariant = 12rnConst adIDispatch = 9rnConst adIUnknown = 13rnConst adGUID = 72rnConst adDate = 7rnConst adDBDate = 133rnConst adDBTime = 134rnConst adDBTimeStamp = 135rnConst adBSTR = 8rnConst adChar = 129rnConst adVarChar = 200rnConst adLongVarChar = 201rnConst adWChar = 130rnConst adVarWChar = 202rnConst adLongVarWChar = 203rnConst adBinary = 128rnConst adVarBinary = 204rnConst adLongVarBinary = 205rnConst adChapter = 136rnConst adFileTime = 64rnConst adPropVariant = 138rnConst adVarNumeric = 139rnConst adArray = &H2000

蛙蛙推荐:asp提高首页性能的一个技巧

03-25

蛙蛙推荐:asp提高首页性能的一个技巧rnrn简单介绍:一般一个网站的首页访问量是最大的,如果您的网站的首页打开的非常缓慢,您的客户将会陆续离开你的网站.通常我们把需要经过复杂运算或者查询数据库得出的数据缓存起来或者生成静态网页来提高web应用的性能,这次我们直接把首页的输出缓存成一个字符串,然后定时更新,即照顾了性能,又不影响首页的时效性.这里用到了一些VBS自定义类,Application对象,XmlHttp对象,adodb.stream对象的一些东西,相关知识大家可以查资料了解.rnrn<%rnDim wawa,StarTime,EndTimernStarTime=Timer()rnSet wawa=new Cls_Cachernwawa.Reloadtime=0.5rnwawa.CacheName="wawa"rnwawa.Name="XmlInfoIndex"rnIf wawa.ObjIsEmpty() Then CacheXmlInfoIndex()rnResponse.Write wawa.valuernEndTime=Timer()rnResponse.Write " 执行时间:" & FormatNumber((Endtime-StarTime)*1000,5) & "毫秒。"rnrnSub CacheXmlInfoIndex()rn Dim BodyText, xmlrn Set xml = Server.CreateObject("Microsoft.XMLHTTP")rn '把下面的地址替换成你的首页的文件地址,一定要用http://开头的绝对路径,不能写相对路径rn xml.Open "GET", "http://onlytiancai/bak/vote/InfoIndex.asp", Falsern xml.Send rn BodyText=xml.ResponseBodyrn BodyText=BytesToBstr(BodyText,"gb2312")rn wawa.Value=BodyTextrnSet xml = NothingrnEnd SubrnFunction BytesToBstr(body,Cset)rn dim objstreamrn set objstream = Server.CreateObject("adodb.stream")rn objstream.Type = 1rn objstream.Mode =3rn objstream.Openrn objstream.Write bodyrn objstream.Position = 0rn objstream.Type = 2rn objstream.Charset = Csetrn BytesToBstr = objstream.ReadText rn objstream.Closern set objstream = nothingrnEnd Functionrn%>rn<%rn'下面这个类可以保存在单独的文件里,然后包含到此页rnClass Cls_Cachern Rem ==================使用说明=================================================================================rn Rem = 本类模块是动网先锋原创,作者:迷城浪子。如采用本类模块,请不要去掉这个说明。这段注释不会影响执行的速度。=rn Rem = 作用:缓存和缓存管理类 =rn Rem = 公有变量:Reloadtime 过期时间(单位为分钟)缺省值为14400, =rn Rem = MaxCount 缓存对象的最大值,超过则自动删除使用次数少的对象。缺省值为300 =rn Rem = CacheName 缓存组的总名称,缺省值为"Dvbbs",如果一个站点中有超过一个缓存组,则需要外部改变这个值。 =rn Rem = 属性:Name 定义缓存对象名称,只写属性。 =rn Rem = 属性:value 读取和写入缓存数据。 = rn Rem = 函数:ObjIsEmpty()判断当前缓存是否过期。 =rn Rem = 方法:DelCahe(MyCaheName)手工删除一个缓存对象,参数是缓存对象的名称。 =rn Rem ===========================================================================================================rn Public Reloadtime,MaxCount,CacheNamern Private LocalCacheName,CacheData,DelCountrn Private Sub Class_Initialize()rn Reloadtime=14400rn CacheName="Dvbbs"rn End Subrn Private Sub SetCache(SetName,NewValue)rn Application.Lockrn Application(SetName) = NewValuern Application.unLockrn End Sub rn Private Sub makeEmpty(SetName)rn Application.Lockrn Application(SetName) = Emptyrn Application.unLockrn End Sub rn Public Property Let Name(ByVal vNewValue)rn LocalCacheName=LCase(vNewValue)rn End Propertyrn Public Property Let Value(ByVal vNewValue)rn If LocalCacheName<>"" Then rn CacheData=Application(CacheName&"_"&LocalCacheName)rn If IsArray(CacheData) Thenrn CacheData(0)=vNewValuern CacheData(1)=Now()rn Elsern ReDim CacheData(2)rn CacheData(0)=vNewValuern CacheData(1)=Now()rn End Ifrn SetCache CacheName&"_"&LocalCacheName,CacheDatarn Elsern Err.Raise vbObjectError + 1, "DvbbsCacheServer", " please change the CacheName."rn End If rn End Propertyrn Public Property Get Value()rn If LocalCacheName<>"" Then rn CacheData=Application(CacheName&"_"&LocalCacheName) rn If IsArray(CacheData) Thenrn Value=CacheData(0)rn Elsern Err.Raise vbObjectError + 1, "DvbbsCacheServer", " The CacheData Is Empty."rn End Ifrn Elsern Err.Raise vbObjectError + 1, "DvbbsCacheServer", " please change the CacheName."rn End Ifrn End Propertyrn Public Function ObjIsEmpty()rn ObjIsEmpty=Truern CacheData=Application(CacheName&"_"&LocalCacheName)rn If Not IsArray(CacheData) Then Exit Functionrn If Not IsDate(CacheData(1)) Then Exit Functionrn If DateDiff("s",CDate(CacheData(1)),Now()) < 60*Reloadtime Thenrn ObjIsEmpty=Falsern End Ifrn End Functionrn Public Sub DelCahe(MyCaheName)rn makeEmpty(CacheName&"_"&MyCaheName)rn End SubrnEnd Classrn%>

没有更多推荐了,返回首页

私密
私密原因:
请选择设置私密原因
  • 广告
  • 抄袭
  • 版权
  • 政治
  • 色情
  • 无意义
  • 其他
其他原因:
120
出错啦
系统繁忙,请稍后再试