当前位置: 首页 > 编程日记 > 正文

字符串多模式精确匹配(脏字/敏感词汇/关键字过滤算法)——TTMP算法 之实战F模式...

前面那么多篇文章都太抽象,这次来一个稍微实际一点的。F模式是我实际上选用的模式,对该模式我做了不少实际的测试,因此代码也算是比较稳定的。不过由于实际上为了得到该算法的效率,算法本身做了一些优化,对于初学者,理解起来可能会有点困难,因此不适合直接贴原始代码。为了便于大家理解,这里出示的代码会比较好读,但是并不能取得我宣称的效果。大家可以在此基础上进行一定的优化,以便达到你所想要的性能。我目前版本的性能大约是:
T2080 1.73GHz(双核) 笔记本 1.5GB 内存 Vista Ultra 特性全开(集成显卡)
15M字符/秒

不过即便经过精简,这个算法的代码也还是比一般的算法要复杂:除了算法本身所在的类,它还需要5个额外的数据结构,以及一个特殊枚举的支持。这几个额外的数据结构包括:
FastDict
FastList
FastQueue
FastWorkItem
FastScanResult
一个额外的枚举是
CharacterType

其中前面四个数据类,以及那个枚举对于本算法是至关重要的。下面首先讲解一下这几个数据结构和枚举:
    // hashcode对应字符串列表的字典
    public class FastDict : Dictionary<uint, FastList>
    
{
        
// 该hashcode对应的字符串最大长度是多少
        public int MaxCharacterLength;
    }


    
// 相同hashcode都有哪些字符串
    public class FastList : List<string>
    
{
    }


    
// 一个起始字符记录(也就是可能需要处理的数据)
    public class FastWorkItem
    
{
        
public FastWorkItem(int index, int maxEndIndex, uint lowPartDelta)
        
{
            Index 
= index;
            MaxEndIndex 
= maxEndIndex;
            LowPartDelta 
= lowPartDelta;
        }

        
// 起始字符在待检文本中的位置
        public int Index;
        
// 该起始字符所对应的关键字条目的最远结束位置
        public int MaxEndIndex;
        
// 该记录所对应的(与前一条记录的)hashcode差值
        public uint LowPartDelta;
    }


    
// 待处理数据队列
    public class FastQueue:Queue<FastWorkItem>
    
{
    }


    
// 扫描结果
    public class FastScanResult
    
{
        
public FastScanResult(string word, int index)
        
{
            Word 
= word;
            Index 
= index;
        }

        
// 被扫描到的字符串
        public readonly string Word;
        
// 
        public readonly int Index;
    }


    
// 字符类型枚举
    public enum CharacterType : uint
    
{
        
// 该字符不是起始符,也不是中间的字符。(有可能是结束符,也可能不在脏字表当中)
        NoHit = 0,
        
// 起始字符掩码,代表以该起始符起始的脏字条目的最大长度。
         
// 如果为0,表示不是起始符。
        StarterMask = 0x7fffffff,
        
// 表示该字符是中间字符。
        MiddleChar = 0x80000000,
    }


其实,FastDict、FastList以及FastQueue本质上和.NET框架提供的没有什么差别——根本就没有什么代码嘛!没错,我主要是为了书写简便,把这些个泛型给封闭了。虽然是没有什么代码,这几个结构却是算法要处理的核心数据。当我们初始化这个关键字过滤引擎的时候,就需要把所有的关键字导入到FastDict/FastList数据结构,以及一个CharacterType数组当中。而当我们进行过滤的时候,就需要把一些待处理项转化为FastWorkItem,并放入一个FastQueue队列中。那么我们是如何初始化这个引擎的呢?

        public MultiWordScanner(string[] scanKeys)
        
{
                scanKeys 
= RemoveDuplicated(scanKeys);

                
// Setup fast scan environment
                
// Create a terminator indexer
                FastDict[] result = new FastDict[char.MaxValue];
                
// Create a starter/ middle character indexer.
                CharMap = new CharacterType[char.MaxValue];
                
foreach (string key in scanKeys)
                
{
                    
// create a dictionary for the specific terminator of this key
                    FastDict dict = CreateDict(result, key);
                    
// add the key into a list in this dictionary
                    CreateList(dict, key);
                    
// markup the middle character
                    MarkMiddleChar(key);
                    
// set the starter info
                    SetCharMap(key);
                }

                _fastIndex 
= result;
        }


        
private FastDict[] _fastIndex;

这段代码看着应该还算是简单,只是其中又是Dictionary又是List的,比较难搞明白其中的关系,之前的一些回复里面也有这样的问题。其实啊,之前的“理论如此”篇章已经解说过一遍了,可能太抽象,不太好理解。这里呢,我就从Hashtable的理论知识开始说起,以便对Hashtable还不是很了解的读者能够搞明白其中的关系。
当我们需要把一堆数据组织起来,比如说认为是一个集合,这个时候大家可以有很多的选择。比如说数组、链表、队列、栈、排序数等等,当然也包括了哈希表。我们把这些数据组织起来,必然面临如何往里面添加一个元素,如何遍历每一个元素,如何找到某一个元素,如何删除一个元素等等问题。这些问题都涉及了两个效率:空间效率和时间效率。比如说数组无疑是遍历速度最快的,存储空间最为节省的形式(不考虑压缩的情况下)。但是通常来讲,我们面临的问题并不是遍历所有数据的速度,而是——例如在本任务当中,查找某一个元素的速度如何。那么哈希表是如何提高这方面的效率的呢?简单的讲,就是首先计算数据的特征值,然后根据特征值来确定该数据应该存在于一个内部数组的什么位置(下标)。由于通常两个不同的数据,计算出来的特征值是不一样的,因此根据特征值来找到的位置通常也就是我们想要的数据所在的位置。例如我们有A和BC两个数据,特征值的计算函数就是该数据的ASCII拼起来,那么有:

数据A,特征值:0x41000000
数据BC,特征值:0x42430000
而内部数组大小为11个元素,则
数据A可能所在的位置是:0x41000000 % 11 = 6
数据B可能所在的位置是:0x42430000 % 11 = 9

很显然,A和BC两个数据是可以放在位置6和位置9,而不会互相干扰的。但是,有的时候,不同的数据是有可能产生相同的特征值的。很容易想象嘛,如果说用一个优先大小的特征值,就能够一一代表无穷尽的各种字符串组合,那么RAR就没有市场了,直接用特征值来“压缩”数据好了。同时,即便是不同的特征值,经过一个模运算计算该特征值在内部数组的位置时,仍然可能会产生相同的位置结果。例如数据AB,特征值是0x41420000,算出来的位置也是6。那么这个时候就叫做出现了冲突,有冲突就要解决,解决的办法有很多种。比如说再往下找一个空余的位置,或者干脆在该位置用一个列表来保存所有具有相同的位置计算结果的数据。说道这里,大家都应该明白了,Hashtable在添加(查找)一个数据的时候,其步骤为:
1、计算数据特征值(也就是哈希值)
2、根据特征值计算下标
3、看看该位置是否为空余状态
4、对于添加,如果空余,就可以直接添加了,否则需要解决冲突;
    对于查找,如果空余就返回没有结果,否则还要对比一下是否就是要找的数据:如果不是,则看一下是否有“冲突”的数据在后面,直到查找到没有冲突数据为止。

其实上面说的是Hashtable的一种特殊形式,叫做HashSet(.NET 3.5里面有提供)。比较正式的Hashtable,实际上添加和查询的时候,是通过一个key来操作的,上述的那些计算也不是对数据,而是对这个key进行的。比如找到key为A的数据的过程,就变成通过计算key A的特征值,再根据特征值找到可能的位置,然后通过比对所有的冲突直到找到"A"这个key,然后再把这个位置上的具体数据返回出来。
从上面这一大队的描述,我们基本上应该可以看出来,一个包含了n个数据的哈希表的查找时间速度接近于O(c)。也就是说,基本上是和数据的数量多少没有太大关系的。而c的大小基本上是受下列因素影响的:哈希表本身的代码是否有效率;哈希值计算公式是否能有效的把数据特征值分散到每一个可能的数值上,可以想象,不好的公式会导致大量数据有相同的哈希值,也就是会产生大量的冲突;解决冲突的机制是否有效率,有的解决方法可能会导致接下来添加的数据也会产生冲突,或者找了半天还是没有找到空余的位置。

说了这半天好像还是没有解决你的困惑,别着急,马上就要说明白了:我们可以知道,由于存在冲突的可能性,在做查询的时候不能直接比对完特征值就确定该数据一定存在,因此必不可少的一步就是做“数据比对”。也就是要看看,根据数据AB的特征值0x41420000计算出来的位置上面,是否真的存着"AB"这个字符串。所以,如果我们直接使用HashTable或者HashSet,都会遇到一个问题:我们要直接提供一个数据"AB",交由HashTable/HashSet内部的算法来计算哈希值,以及进行冲突检查。因此,在我们的关键字过滤算法中,一旦触发“检索”,其代码就类似这样书写:

for(int length = 1; length <= maxLength; length++)
{
  
string word = text.SubString(currentIndex, length);
  
if (_hashTable.ContainsKey(word))
  
{
    
return new FastResult(word, currentIndex);
  }

}

可以看到,这样的算法有这么几个问题(为了便于描述,我们假设在最糟糕的情况下):
1、我们要分别计算从currentIndex开始,长度为1到maxLength的每一个字符串的哈希值;
2、我们要分别对从currentIndex开始,长度为1到maxLength的每一个情况,取出一个字符串对象,也就是不得不产生maxLength个字符串对象,也就不得不复制1+2+...+maxLength个字符。

实际上,这些基本上都是不必要的,或者说是可以被优化掉的。要不要优化,我们就要看一下这样的优化是否值得:对于我那个280K字符的正常文本,检出关键字数量为1000个左右,但是触发检索的次数大概在10K到20K次左右。也就是说,如果采用上述算法,我们很可能至少得要为每一次检出关键字,多负担10次的额外运算。为了减少这样可观数量的不必要运算,我简单的对哈希表进行了一个拆解——把计算哈希值,以及进行字符串对比(冲突检查)的工作给提取出来,交由我的代码来进行运算。于是,就有了FastDict和FastList这两个基础数据结构了。

FastDict就是根据一个哈希值,给出该哈希值所对应的一个FastList。而FastList里面就保存着同一个哈希值所对应的所有字符串。换而言之,FastDict就是根据数据的特征值,找到的可能的数据所在位置,FastList则是用来解决冲突的。通过这样的改造,我们就有机会对偏移量从1到maxLength的字符 逐一扫描一遍,就可以分别得到长度为1到maxLength的maxLength个字符串的哈希值。同时,我们也不需要为每一次比较产生一个新的字符串了。下面是其中一部分的代码:

        public FastScanResult FastScan(string text, int start)
        
{
            FastQueue queue 
= new FastQueue();
            FastDict[] fastIndex 
= FastIndex;
            
// 上一次遇到起始符之前,计算出来的哈希值低16位部分。
            uint lastLowPart = 0;
            
// 当前处理项,该项永远不在queue中。
            
// 之所以这么处理,是为了避免大量的创建对象,和加入、弹出队列的操作
            FastWorkItem currentItem = new FastWorkItem(-1-10);

            
int count = text.Length;
            
for (int i = start; i < count; i++)
            
{
                
uint index = FastScanStringHelper.FastLower(text[i]);
                CharacterType ctype 
= CharMap[index];
                
// 如果这个是起始符,那么下面这条语句则可以得出,
                
// 以该字符开头的关键字,除去起始符的最大长度是多少。
                int maxLength = (int) (ctype & CharacterType.StarterMask);
                
int endIndex;
                
// 如果maxLength大于零,表示这是一个开始位置。
                if (maxLength > 0)
                
{
                    
// 设置结束访问的位置,这和后面的To do 1,以及后面的代码都有关系
                    endIndex = i + maxLength - 1;
                    
// To do 1: 往currentItem或者queue中添加一条待解决起始符FastWorkItem记录
                }

                
else
                
{
                    endIndex 
= 0;
                }


                
// 如果当前正在扫描的字符所在位置,没有超过当前项目的MaxEndIndex
                
// 则我们需要对当前字符进行检查。
                if (i <= currentItem.MaxEndIndex)
                
{
                    
// 是否为一个结束符?
                    FastDict dict = fastIndex[index];
                    
if (dict != null)
                    
{
                        
// 如果是,则从queue中的记录进行检索工作。
                        FastScanResult result = ScanWorkItems(text, i, dict, currentItem.LowPartDelta, queue);
                        
// 找到了,自然是返回了。
                        if (result != null)
                        
{
                            
return result;
                        }

                    }


                    
// 如果当前不是一个中间字符,同时也不是起始符,或者队列中有待处理的项目。
                    
// 那么我们就需要清除待处理队列,避免不必要的重复计算。
                    if ((maxLength == 0 || queue.Count != 0&& (ctype & CharacterType.MiddleChar) == CharacterType.NoHit)
                    
{
                        
                        lastLowPart 
= 0;
                        queue.Clear();
                        currentItem.LowPartDelta 
= 0;
                        currentItem.MaxEndIndex 
= endIndex;
                        
if (endIndex == 0)
                        
{
                            
// 如果不是遇到起始符,则完全不需要尝试后面的计算。
                            continue;
                        }

                    }


                    
// 如果当前待处理项的结束位置已经超出当前位置,则需要尝试清理现有的工作项
                    if (queue.Count > 0 && currentItem.MaxEndIndex <= i)
                    
{
                        
// 下面的代码用于恢复最后待处理项哈希值的低16位
                        uint lastLowPartDelta = 0;
                        
uint oldLowPart = currentItem.LowPartDelta;
                        
do
                        
{
                            currentItem 
= queue.Dequeue();
                            lastLowPartDelta 
^= currentItem.LowPartDelta;
                        }
 while (queue.Count > 0 && currentItem.MaxEndIndex <= i);
                        currentItem.LowPartDelta 
= oldLowPart ^ lastLowPartDelta;
                        lastLowPart 
^= lastLowPartDelta;
                        
// 只有当前位置仍然在最远可能结束符之前,我们就需要计算哈希值
                        if (i < currentItem.MaxEndIndex)
                        
{
                            currentItem.LowPartDelta 
^= index;
                        }

                    }

                    
else
                    
{
                        
// 如果待处理项的结束位置没有超出当前位置,也需要计算哈希值
                        currentItem.LowPartDelta ^= index;
                    }

                }

            }


            
// 如果循环到结束位置,仍然没有找到关键字,表示找不到了,此时返回空。
            return null;
        }

可以看到,上面这段代码确实是比普通的算法要复杂。提高效率自然是要付出代价的,有的时候我们可以通过多消耗点内存来达到,而更加根本、更加有效的还是提高算法的“复杂度”。比如快速排序,其算法复杂度就比普通的冒泡、插入排序要高得多。即便是多消耗再多的内存,冒泡排序在处理速度上仍然是无法比得过快速排序的。当然了,你可以说我的任务只需要每秒钟扫描3百万个字符,这个时候普通的算法就足以应付了。又或者例如我之前的情况,从晚上6点开始到早上6点之前,必须要把所有的内容都检索一遍,于是没功夫想一个更好的算法,有什么代码只要能工作,写起来又简单就好。在这样的任务条件下,也许TTMP-F算法是不适合的。而如果你现在已经有点时间,同时效率又是你最头痛的事情时,你还是不得不仔细看看上面的代码,况且我觉得还不算太复杂。

大部分的代码,我想大家还是轻易就能看明白的。关键有两个地方不太好理解:
1、为什么待处理项currentItem不放在队列中?"To do 1"是怎么实施的?
2、lastLowPart、LowPartDelta这些变量是什么意思?为什么要使用这些变量?

下面是部分的答案:
1、产生一个对象也许没有什么成本,但是如果产生上万次,累计起来的效率也是比较客观的。这还不是关键的,关键是整个核心就是和当前待处理的项目有关,如果我们把当前待处理项加入到队列当中,则我们每次要对一些当前条件做判断的时候,是否也需要从队列中取出来?即使我们用Peek,其对于效率的影响也是非常之高的,甚至该部分调用的次数,比产生currentItem的次数还要多。那么To do 1是怎么实施的呢?就算作我就留给大家的一个作业了。
2、如果大家看过我以前的文章,一定知道我这个人写东西有两个特点:一个是罗嗦,另一个就是爱吊人胃口。没错,放在下一集播出。其实不是我不想一口气写完,是我发现由于废话比较多的缘故,写得太长了——断续写了好几天了,也没有一个结束。想到这个问题,还是觉得先发布一下吧,免得我一不小心IE崩溃,这片心血就奔赴长江了……

那么,下一期的节目,还是会继续实战F模式。(还有好多代码没有贴出来哦……敬请期待)
 

相关文章:

深入java_深入Java Final

JAVA关键字final用于修饰数据、方法或类&#xff0c;通常意味着“无法改变的”&#xff0c;既数据不能改变&#xff0c;方法不能覆盖&#xff0c;类不能继承。一般采用final有两种原因&#xff1a;设计和效率。而随着JAVA版本的更新&#xff0c;一些效率上的问题可以交由编译器…

盛会再临,2018中国大数据技术大会(BDTC)首曝日程及议题

满目皆干货&#xff0c;俯仰尽拾珠。作为年度技术趋势与行业应用的风向标&#xff0c;连续成功举办十一年的中国大数据技术大会&#xff08;BDTC&#xff09;携主题“大数据新应用”再度强势来袭&#xff0c;稳踏技术时代浪潮&#xff0c;势将引爆今冬技术圈。 数据&#xff0c…

Linux下修改MAC地址总结

偶尔会用到这个知识点&#xff0c;久了不用又会记不住&#xff0c;所以记之&#xff0c;方便以后查询。 Linux下修改MAC地址 方法一&#xff1a; 1.关闭网卡设备 ifconfig eth0 down 2.修改MAC地址 ifconfig eth0 hw ether MAC地址 3.重启网卡 ifconfig eth0 up 或者将以上内容…

hadoop 2 java hdfs_Hadoop2.6.0学习笔记(二)HDFS访问

鲁春利的工作笔记&#xff0c;谁说程序员不能有文艺范&#xff1f;通过hadoop shell与java api访问hdfs工作笔记之Hadoop2.6集群搭建已经将集群环境搭建好了&#xff0c;下面来进行一些HDFS的操作1、HDFS的shell访问HDFS设计主要用来对海量数据进行处理&#xff0c;即HDFS上存储…

知乎如何洞察你的真实喜好?首页信息流技术揭秘

11月8-9日&#xff0c;由中国 IT 社区 CSDN 与硅谷 AI 社区 AICamp 联合出品的 2018 AI 开发者大会&#xff08;AI NEXTCon&#xff09; 在北京举行&#xff0c;就人工智能的最新技术及深度实践&#xff0c;进行了全方位的解读及论证。本文是机器学习技术专题中知乎首页业务总监…

[Web开发] 微软的RSS协议扩展 - FeedSync 介绍 (4)

上一篇文章介绍了在2台电脑上同时修改数据的feedsync 同步过程&#xff0c; 今天再讨论一下当在2台电脑上同时删除同一个数据的情况。 假设最初feed 里面数据是这样的<item><sx:sync id"ep2.100" updates"1" deleted"false" noconflict…

weblogic 修改控制台密码

关掉weblogic所有进程切换到域下面$cd /home/weblogic/Oracle/Middleware/user_projects/domains/jydomain/security$java -classpath /home/weblogic/Oracle/Middleware/wlserver_10.3/server/lib/weblogic.jar weblogic.security.utils.AdminAccount weblogic weblogic123 …

WPF框架的内存泄漏BUG

用户在使用GIX4某模块的过程中&#xff0c;内存只见加不见减。我们怀疑出现了内存泄漏&#xff0c;所以我花了相当一段时间来进行此问题的排查。 我使用Red Gate公司的产品ANTS Memory Profiler 5进行应用程序的内存进行监视。并在过程中修改程序中出现的一些问题。但是最后留下…

java map深拷贝_java 实现Map的深复制

在java中有一个比较有趣的特性&#xff0c;在对对象进行赋值&#xff0c;或者clone时候一般都是我们所说的浅复制&#xff0c;Object A B;也就是说我们获取的并非在堆中重新分配的一块内存&#xff0c;而是一个指向原有数据内存的一个引用。这样的后果就是我们修改了A中的属性…

出门问问工程副总裁黄美玉入选IEEE Fellow,曾担任微软Cortana首席NLP科学家

虽然 IEEE&#xff08;国际电子电气工程协会&#xff09;2019 年的 Fellow 评选结果还未正式出炉&#xff0c;但记者刚刚获悉&#xff0c;IEEE Fellow 又新增一名华人科学家入选——出门问问工程副总裁、Mobvoi AI Lab 的负责人黄美玉博士。黄美玉博士是由于其在语音/语言技术领…

Windows2003服务器不支持FLV视频的解决方法

Windows2003服务器不支持FLV视频的解决方法2007年10月19日 星期五 10:43 A.M.原因&#xff1a;WIN2003加强了IIS6的MIME验证&#xff0c;一切未注册扩展文件格式统统显示404错误。手动在IIS中HTTP头->MIME添加MIME影射关系&#xff0c;MIME类型: video/x-flv 扩展名:.flv&am…

mpi并行 java_【并行计算】用MPI进行分布式内存编程(一)

通过上一篇关于并行计算准备部分的介绍&#xff0c;我们知道MPI(Message-Passing-Interface 消息传递接口)实现并行是进程级别的&#xff0c;通过通信在进程之间进行消息传递。MPI并不是一种新的开发语言&#xff0c;它是一个定义了可以被C、C和Fortran程序调用的函数库。这些函…

JQuery——选择器分类

JQuery选择器1 什么是JQuery选择器快速高效的找到指定节点&#xff0c;支持css语法设置页面2 JQuery选择器分类2.1 基本选择器CSS选择器层级选择器表单域选择器2.2 过滤选择器简单过滤选择器内容过滤选择器属性过滤选择器子元素过滤选择器表单域属性过滤选择器可见性过…

3月6日工作日志-88250

今天&#xff1a; 1. 与zy、vanessa一起使用mingle做了开发计划 2. 使用了XStream重写了XML格式的Dynamic Dictionary Basic Engine TODO&#xff1a; 1. 提高Dynamic Dict引擎的效率 2. 分片转换一部43W词汇的英&#xff0d;中词库(按字母、大小写分片) 转载于:https:/…

专注文本处理,达观数据完成B轮融资,累计融资超2亿元

11月22日&#xff0c;达观数据宣布成功完成1.6亿元B轮融资&#xff0c;由宽带旗下基金晨山资本领投&#xff0c;元禾重元、联想之星、钟鼎资本及老股东等跟投。达观数据总部位于上海张江高科技园区&#xff0c;目前已在北京、成都、深圳、西安等地开设分支机构。2015年获真格基…

Asp.Net Core写个共享磁盘文件Web查看器

查看器功能说明与演示 本查看器主要是为了方便大家查看服务器上的日志&#xff0c;这里没有考虑其他安全性问题&#xff0c;比如特定人员登录才能查看&#xff0c;这个需要您们自己去增加&#xff1b;如果你服务器有对外开放了ip&#xff0c;那么运行这个软件的时候建议考虑配置…

ImageNet时代将终结?何恺明新作:Rethinking ImageNet Pre-training

译者 | 刘畅 林椿眄整理 | Jane出品 | AI科技大本营Google 最新的研究成果 BERT 的热度还没褪去&#xff0c;大家都还在讨论是否 ImageNet 带来的预训练模型之风真的要进入 NLP 领域了。如今&#xff0c;Facebook AI Research 的何恺明、Ross Girshick 及 Piotr Dollar 三位大佬…

java 序列化 缓存_java_缓冲流、转换流、序列化流

一、缓冲流缓冲流的基本原理&#xff0c;是在创建流对象时&#xff0c;会创建一个内置的默认大小的缓冲区数组&#xff0c;通过缓冲区读写&#xff0c;减少系统IO次数&#xff0c;从而提高读写的效率。字节缓冲流构造方法创建字节缓冲输入流&#xff1a;BufferedInputStream bi…

QQ2007去广告教程(本地vip)

只要是vip就可以去掉广告了 关键函数QQHelperDll.dll的IsQQServiceEnable 在入口点修改: mov eax,1 retn 好了这样就成了本地的vip了 因为那个dll的版本太多了不能通用&#xff0c;所以就不提供下载了&#xff08;我的版本7.1.644.1777&#xff09; 同时qq每次升级都有可能替换…

java instanceof 报错_java instanceof方法

基本用法null instanceof Object 为false&#xff1b; null instanceof 任意类 为false&#xff1b;任意实例 instanceof 对应的类或者父类 都为true&#xff1b;基本数据类型 instanceof Object 编译时会报错(如 int a&#xff1b;a instanceof Object 编译不通过)&#xff…

grep的常用命令语法

grep的常用命令语法 1. 双引号引用和单引号引用 在g r e p命令中输入字符串参数时&#xff0c;最好将其用双引号括起来。例如&#xff1a;“m y s t r i n g”。这样做有两个原因&#xff0c;一是以防被误解为 s h e l l命令&#xff0c;二是可以用来查找多个单词组成的字符串&…

千呼万唤始出来!OpenCV 4.0正式发布!

作者 | 周强&#xff08;本文为作者独立观点&#xff0c;转载请联系作者&#xff09;来源 | 我爱计算机视觉OpenCV 4.0 正式版来啦&#xff01;重回英特尔的 OpenCV 终于迎来一次大版本更新&#xff0c;增加了诸多新特性&#xff0c;快来一起看看吧&#xff5e;因为 OpenCV 最开…

ORA-01031: insufficient privileges的解决方法

原文出自:http://www.chinaunix.net/jh/19/132866.html############################################# # # NAME: troubleshoot connect internal.txt # # DESCRIPTION: # connect internal # connect / as sysdba 要口令问题&#xff1a;# refer (METALINK,ORACLEDOC), # me…

java 线程通讯_java多线程(五)线程通讯

1.1. 为什么要线程通信多个线程并发执行时&#xff0c;在默认情况下CPU是随机切换线程的&#xff0c;有时我们希望CPU按我们的规律执行线程&#xff0c;此时就需要线程之间协调通信。1.2. 线程通讯方式线程间通信常用方式如下&#xff1a;l 休眠唤醒方式&#xff1a;Object的w…

合并排序(C语言实现)

递归算法是把一个问题分解成和自身相似的子问题&#xff0c;然后再调用自身把相应的子问题解决掉。这些算法用到了分治思想。其基本模式如下&#xff1a; 分解&#xff1a;把一个问题分解成与原问题相似的子问题 解决&#xff1a;递归的解各个子问题 合并&#xff1a;合并子问题…

工程实践也能拿KDD最佳论文?解读Embeddings at Airbnb

作者 | Mihajlo Grbovic&#xff0c;Airbnb 资深机器学习科学家译者 | Lang Yang&#xff0c;Airbnb 工程经理【导读】本文最早于 2018 年 5 月 13 日发表&#xff0c;主要介绍了机器学习的嵌入技术在 Airbnb 爱彼迎房源搜索排序和实时个性化推荐中的实践。Airbnb 爱彼迎的两位…

计算点、线、面等元素之间的交点、交线、封闭区域面积和闭合集(续1)

继续上一节的内容&#xff0c;本节主要讲解三维空间中射线、线段与平面及三维物体的交点及距离的计算&#xff0c;它们在碰撞检测和可见性剔除等应用中是必不可少的。首先给出3D空间下点乘和叉乘的定义与定理的推导&#xff0c;再谈如何应用到程序编码的工作中。 设三维空间中任…

android 抓取native层奔溃

使用android的breakpad工具 使用这个工具需要下载Breakpad的源码&#xff0c;然后进行编译&#xff0c;编译之后会生成两个工具 我们使用这两个工具来解析奔溃的位置。这里我们可以下载已经编译好的工具 下载地址是&#xff1a;链接&#xff1a;http://pan.baidu.com/s/1jIiU5c…

渗透各行各业,这家RPA外企宣布全面进军中国市场

11月15日&#xff0c;全球机器人流程自动化&#xff08;RPA&#xff09;领域平台UiPath首次在中国举办UiPath Together年度大会&#xff0c;来自自动化、人工智能和机器学习领域的行业专家&#xff0c;以及来自中国和世界的领先公司的客户与合作伙伴共同参与了此次活动。在此次…

java gettext_JAVA中getText()怎么从一个JTextArea中读出内容?

想先创建一个JTextArea&#xff0c;然后在里面输入内容(几个字母)&#xff0c;然后用getText读出里面的内容&#xff0c;可是好像只能是先在JTextArea里面写&#xff0c;然后getText才能读出&#xff0c;而不能先运行&#xff0c;在图形界面的JTex...想先创建一个JTextArea&…