摘要: 在实际当中,聊天过滤工作 最终可能会消耗掉可观的(有时是惊人的)资源总量--无论是从开发方面还是从原始计算能力方面来说。有些知名SNS类游戏仅在聊天过滤上就耗费了超过50%的计算资源。因此对于这部分的优化工作就显得特别重要。最近做游戏服务器,需要用到聊天过滤,首先想到的是使用HashSet<string> 的方式。基本的思路是把所有需要过滤的关键字保存在HashSet<string>中。对于用户输入的消息进行暴力分割。如:今天你好吗单个字符分割为: 今/天/你/好/吗2个字符分割为: 今天/天你/你好/好吗3个字符分割为: 今天你/天你好/你好吗..........以此阅读全文
posted @ 2011-08-24 22:27 边城浪 阅读(1087) 评论(10) 编辑

前段时间写了篇文章。说的trie算法原文见

http://www.cnblogs.com/yeerh/archive/2011/08/24/2152607.html

回复中有说到一个很快的方法 。原文见xingd的博客

http://www.cnblogs.com/xingd/archive/2008/02/01/1061800.html

经测试,这种方法确实很快,但已不是单纯的hash算法了,其中的思路比较巧妙。

文章中基本上只有代码,只有很少的说明。

经过本人的认真阅读,终于搞懂了优化的关键所在。

现在对这种做了一个小的改动,不敢独独享,特分享给大家。


这种优化基于我们本常所用的大部分词或是字并不是脏字的前提。

有些字符即使在脏字单词中出现,他们出现地位置也不一样。

根据这个特点。我们先建立一个用于快速过滤的数组。

Uint16[] m_fastCheck = new Uint16[char.MaxValue];

fastCheck的索引号用来表示对应的字符。

里面的值 表示这个字符在单词中出现的位置,我们用Uint16来表示,
也就可以保存1-16。。

比如:马这个字符脏字有 "你马", '马的' , '草泥马'

则 fastCheck[39532] = 0x07;  //''转化为数字为39532

因为字符可以隐式转换为整数。也可以写作 fastCheck[''] = 0x07;

至于这个0x07是怎么来的。因为马出现的位置有 12

对这些位置做位运算就可以得出7.

这个我们在添加关键中的时候就可以初始化。

public void AddKey(string word)

{.................

for(int i=0;i<word.Length;i++)

{

    m_fastCheck[word[i]] |= (UInt16)(1<<i);

}

}

有了这个,我们可以快速排除一些不是脏字字符。还可以更快吗?

可以,我们再加一个首尾字符的判断。

Uint16[] m_startCheck = new Uint16[char.MaxValue];

Uint16[] m_endCheck = new Uint16[char.MaxValue];

而这种我们保存的不是字符出现的位置。而是保存词的长度。

public void AddKey(string word)

{.................

for(int i=0;i<word.Length;i++)

{

    m_fastCheck[word[i]] |= (UInt16)(1<<i);

}

Uint16 mask = (UInt16)(1<<word.Length-1);

m_startCheck[word[0]]  |= mask;

m_endCheck[word[word.Length-1]] |=  mask;

}

就这样,在进行查找的时候。我们先判断对应位置的字符出现地位置是否正确,

再判断首尾字符的长度是否要求。如果这些条件都满足,再进行hash查找。

方法如下:

 public string FindOne(string text)

        {

            for (int index = 0; index < text.Length; index++)

            {

                int count = 0;

                int maxIndex = Math.Min(maxWordLength + index, text.Length);

                char begin = text[index];

                for (int j = index; j < maxIndex; j++)

                {

                    char current = text[j];

                    UInt16 mask = (UInt16)(1 << count);

                    //先判断字符出现的位置是否正确

                    if ((m_fastCheck[current] & mask) == 0)

                    {

                        index += count;

                        break;

                    }

                    ++count;

                    //再判断首字符和尾字符的长度是否正确.

                    if ((m_startLength[begin] & mask) > 0 && (m_endLength[current] & mask) > 0)

                    {

                        //进行hash比较

                        .........................

                    }

                }

            }

            return string.Empty;

        }

至此,整个查找方法就完成了。脏字替换跟查找的方法差不多。

为了减少字符串的截取,我重写了一个HashSet, 支持bool  Contains(string text,int offset,int coung)的方式进行调用.

完整的代码下载:

http://files.cnblogs.com/yeerh/Filter2.7z

 

15:15 2011-12-21 更新版,修正了一些小bug
http://files.cnblogs.com/yeerh/Filter3.7z

posted @ 2011-10-20 15:46 边城浪 阅读(2445) 评论(17) 编辑

   在实际当中,聊天过滤工作 最终可能会消耗掉可观的(有时是惊人的)资源总量--无论是从开发方面还是从原始计算能力方面来说。有些知名SNS类游戏仅在聊天过滤上就耗费了超过50%的计算资源。因此对于这部分的优化工作就显得特别重要。

最近做游戏服务器,需要用到聊天过滤,首先想到的是使用HashSet<string> 的方式。

基本的思路是把所有需要过滤的关键字保存在HashSet<string>中。

对于用户输入的消息进行暴力分割。

如:今天你好吗

单个字符分割为: 今/天/你/好/吗

2个字符分割为: 今天/天你/你好/好吗

3个字符分割为: 今天你/天你好/你好吗

..........以此类推

对于大的字符串。我们对分割把分割的长度限制为需过滤关键字的长度。

然后对分割的了字符串进行匹配。

 

View Code
    public class HashFilter
    {
        
int m_maxLen; //关键字最大长度
        HashSet<string> m_keys = new HashSet<string>();
        
        
/// <summary>
        
/// 插入新的Key.
        
/// </summary>
        
/// <param name="name"></param>
        public void AddKey(string key)
        {
            
if ((!string.IsNullOrEmpty(key)) && m_keys.Add(key) && key.Length > m_maxLen)
            {
                m_maxLen 
= key.Length;
            }
        }
        
/// <summary>
        
/// 检查是否包含非法字符
        
/// </summary>
        
/// <param name="text">输入文本</param>
        
/// <returns>找到的第1个非法字符.没有则返回string.Empty</returns>
        public string FindOne(string text)
        {
            
for (int len = 1; len <= text.Length; len++)
            {
                
int maxIndex = text.Length - len;
                
for (int index = 0; index <= maxIndex; index++)
                {
                    
string key = text.Substring(index, len);
                    
if (m_keys.Contains(key))
                    {
                        
return key;
                    }
                }
            }
            
return string.Empty;
        }
    }

这个方式有个缺点.就是使用 String.Substring会创建大量临时对象.

即便是使用了最大长度进行分割字符串的控件.在需要过滤的字符串较长时,还是不见得高效.

 

Trie,又称单词查找树键树,是一种形结构,是一种哈希树的变种。它的优点是:最大限度地减少无谓的字符串比较,查询效率比哈希表高。

我们先来看一个Trie结构的例子

在这个Trie结构中,保存了A、to、tea、ted、ten、i、in、inn这8个字符串

首先看我们如何实现这个结构:

 public class TrieFilter
    {
        
private Char m_key;
        
private Dictionary<Char, TrieFilter> m_values;
        //根节点,不包含字符,即m_key='\0';
        
public TrieFilter()
        {
            m_values 
= new Dictionary<Char, TrieFilter>();
        }
        //用于创建子节点
        TrieFilter(Char key)
        {
            m_key 
= key;
            m_values 
= new Dictionary<Char, TrieFilter>();
        }
        
/// <summary>
        
/// 添加关键字
        
/// </summary>
        
/// <param name="key"></param>
        public void AddKey(string key)
        {
            
if (string.IsNullOrEmpty(key))
            {
                
return;
            }
            TrieFilter node 
= this;
            
foreach (var c in key)
            {
                TrieFilter subnode;
                
if (!node.m_values.TryGetValue(c, out subnode))
                {
                    subnode 
= new TrieFilter(c);
                    node.m_values.Add(c, subnode);
                }
                node 
= subnode;
            }
            //最后的结点,表示单词结束,用‘\0’表示,指向一个空对象
            node.m_values[
'\0'= null;
        }
    }
}

 这样。一个C#的Trie结构就表示完成。。

下面我们来看看如何实现关键字查找

 

 /// <summary>
        
/// 检查是否包含非法字符
        
/// </summary>
        
/// <param name="text">输入文本</param>
        
/// <returns>找到的第1个非法字符.没有则返回string.Empty</returns>
        public string FindOne(string text)
        {
            
for (int i = 0; i < text.Length; i++)
            {
                TrieFilter node;
                
if (m_values.TryGetValue(text[i], out node))
                {
                    
for (int j = i + 1; j < text.Length; j++)
                    {
                        
if (node.m_values.TryGetValue(text[j], out node))
                        {
                            
if (node.m_values.ContainsKey('\0'))
                            {
                                
return text.Substring(i, (j + 1 - i));
                            }
                        }
                        
else
                        {
                            
break;
                        }
                    }
                }
            }
            
return string.Empty;
        }

是不是很简单呢?

把由蛮力匹配转变成基于Trie的匹配方案会极大的节省执行时间,这个比较对较长字符串进行匹配时更加明显。

对于长度在20左右的字符串。Tire的匹配速度比Hash的方式快了近10倍。

下面附上完整的代码下载

里面的BadWord.txt包括了7000多个敏感关键字。 

完整的代码及性能对比测试下载:http://files.cnblogs.com/yeerh/FilterTest.rar

 

希望本文能对你有所帮助。欢迎拍砖。

 

 

posted @ 2011-08-24 22:27 边城浪 阅读(1087) 评论(10) 编辑

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Data;
using System.Xml;
using System.Runtime.Serialization.Json;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.Serialization.Formatters.Soap;

namespace Cblang.Serialization
{
    /// <summary>
    /// 序列化
    /// </summary>
    public static class Serializers
    {
        /// <summary>
        /// 二进制序列化
        /// </summary>
        /// <param name="request">要序列化的对象</param>
        /// <returns>MemoryStream</returns>
        public static System.IO.MemoryStream SerializeBinary(object request)
        {
            BinaryFormatter serializer = new BinaryFormatter();
            System.IO.MemoryStream memStream = new System.IO.MemoryStream();
            serializer.Serialize(memStream, request);
            return memStream;
        }

        /// <summary>
        /// 反序列化二进制对象
        /// </summary>
        /// <param name="memStream"></param>
        /// <returns></returns>
        public static object DeSerializeBinary(System.IO.MemoryStream memStream)
        {
            memStream.Position = 0;
            BinaryFormatter deserializer = new BinaryFormatter();
            object newobj = deserializer.Deserialize(memStream);
            memStream.Close();
            return newobj;
        }

        #region Binary Serializers
        /// <summary>
        /// 对象序列化为Bin
        /// </summary>
        /// <param name="data">要序列化的对象</param>
        public static byte[] BinSerialize(this object data)
        {
            BinaryFormatter formatter = new BinaryFormatter();
            MemoryStream rems = new MemoryStream();
            formatter.Serialize(rems, data);
            return rems.GetBuffer();
        }

        /// <summary>
        /// Bin反序列化为对象
        /// </summary>
        /// <param name="data">数据缓冲区</param>
        /// <returns>对象</returns>
        public static object BinDeserialize(this byte[] data)
        {
            BinaryFormatter formatter = new BinaryFormatter();
            MemoryStream rems = new MemoryStream(data);
            return formatter.Deserialize(rems);
        }

        #endregion

        #region Json序列化
        /// <summary>
        /// Json序列化,用于发送到客户端
        /// </summary>
        public static string JsonSerialize(this object item)
        {
            DataContractJsonSerializer serializer = new DataContractJsonSerializer(item.GetType());
            using (MemoryStream ms = new MemoryStream())
            {
                serializer.WriteObject(ms, item);
                string result = Encoding.UTF8.GetString(ms.ToArray());
                return result;
            }
        }

        /// <summary>
        /// Json反序列化,用于接收客户端Json后生成对应的对象
        /// </summary>
        public static T JsonDeserialize<T>(this string jsonString)
        {
            DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(T));
            using (MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(jsonString)))
            {
                T jsonObject = (T)ser.ReadObject(ms);
                return jsonObject;
            }
        }
        #endregion

        #region XML Serializers
        /// <summary>
        /// XML 序列化
        /// </summary>
        /// <param name="request"></param>
        /// <returns></returns>
        public static System.IO.MemoryStream SerializeSOAP(object request)
        {
            SoapFormatter serializer = new SoapFormatter();
            System.IO.MemoryStream memStream = new System.IO.MemoryStream();
            serializer.Serialize(memStream, request);
            return memStream;
        }

        /// <summary>
        /// XML 反序列化
        /// </summary>
        /// <param name="memStream"></param>
        /// <returns></returns>
        public static object DeSerializeSOAP(System.IO.MemoryStream memStream)
        {
            object sr;
            SoapFormatter deserializer = new SoapFormatter();
            memStream.Position = 0;
            sr = deserializer.Deserialize(memStream);
            memStream.Close();
            return sr;
        }
        #endregion
     
        #region XML文件写DataSet
        /// <summary>
        ///
        /// </summary>
        /// <param name="ds"></param>
        /// <param name="fileName"></param>
        public static void WriteXml(DataSet ds, string fileName)
        {
            if (File.Exists(fileName))
            {
                File.Delete(fileName);
            }
            ds.WriteXml(fileName, XmlWriteMode.WriteSchema);
        }
        /// <summary>
        ///
        /// </summary>
        /// <param name="fileName"></param>
        /// <returns></returns>
        public static DataSet ReadXml(string fileName)
        {
            if (File.Exists(fileName))
            {
                //创建XmlTextReader对象
                XmlTextReader xReader = new XmlTextReader(fileName);
                //创建一个新的数据集
                DataSet ds = new DataSet();
                //从filexml中读出数据集
                ds.ReadXml(xReader, XmlReadMode.Auto);
                return ds;
            }
            return null;
        }      
        #endregion
    }
}

posted @ 2010-07-12 19:45 边城浪 阅读(175) 评论(1) 编辑

//得到Pin
HRESULT GetPin(IBaseFilter *pFilter, PIN_DIRECTION PinDir, const WCHAR * pinName, IPin **ppPin)
{
 IEnumPins  *pEnum;
 IPin       *pPin;
 HRESULT    hr;

 hr = pFilter->EnumPins(&pEnum);
 if (FAILED(hr)) return hr;

 while(pEnum->Next(1, &pPin, 0) == S_OK)
 {
  PIN_INFO pInfo;
  hr = pPin->QueryPinInfo(&pInfo);
  if (SUCCEEDED(hr) && PinDir == pInfo.dir)
  {
   //检查名字是否相同,用于有多个输入或输出的Pin
   if(pinName != NULL && wcsncmp(pInfo.achName,pinName,128) != 0)
   {
    pPin->Release();
    continue;
   }
   pEnum->Release();
   *ppPin = pPin;
   return S_OK;
  }
  pPin->Release();
 }
 pEnum->Release();
 return E_FAIL; 
}

//连接Pin
HRESULT ConnectFilters(IGraphBuilder *pGraph, IBaseFilter *pFirst, const WCHAR * pinOutName, IBaseFilter *pSecond, const WCHAR * pinInName)
{
 IPin *pOut = NULL, *pIn = NULL;
 HRESULT hr = GetPin(pFirst, PINDIR_OUTPUT, pinOutName, &pOut);
 if (FAILED(hr)) return hr;
 hr = GetPin(pSecond, PINDIR_INPUT, pinInName, &pIn);
 if (FAILED(hr))
 {
  pOut->Release();
  return E_FAIL;
 }
 hr = pGraph->Connect(pOut, pIn);
 pIn->Release();
 pOut->Release();
 return hr;
}

posted @ 2010-07-06 23:49 边城浪 阅读(79) 评论(0) 编辑
posted @ 2010-07-06 09:16 边城浪 阅读(98) 评论(0) 编辑

原文引用:

http://www.cnblogs.com/hjf1223/archive/2008/06/19/QQWry_net.html

改进后的下载地址:下载地址

 

性能:

posted @ 2009-05-19 16:44 边城浪 阅读(652) 评论(4) 编辑
摘要: 源代码下载地址:http://files.cnblogs.com/yeerh/HttpDownload.rar 基于事件的Socket源码(带服务器端和客户端示例)http://files.cnblogs.com/yeerh/SocketDemo.rar阅读全文
posted @ 2008-08-26 16:07 边城浪 阅读(955) 评论(2) 编辑