IT技术博客大学习 共学习 共进步

算法

共 606 篇文章

IT 2015-01-25 21:39:22 / 累计浏览 7,652

XML和JSON

这篇文章从社区中一个常见的争论出发,客观对比了XML和JSON这两种数据交换格式。作者没有简单地断言谁更优越,而是通过具体代码示例,细致地分析了两者在体积、表达直观性和功能特性上的关键差异。 文章指出,JSON在数据类型区分(如数字与字符串)、数组表示(用[]更直观)以及处理特殊字符(如换行)时更为简洁自然,这些特性使其在JavaScript生态中拥有原生支持的优势。然而,XML也并非没有强项:它支持声明和编码信息,可通过命名空间灵活组合不同定义,并且其DTD和XML Schema提供的验证能力远强于JSON Schema,能实现更严格的自描述和自校验。 在对象转换方面,JSON的解析和序列化在许多语言中已成为基础操作,而XML则通常需要依赖额外的类库和注解配置。文章还介绍了在线转换工具作为实用补充。总的来说,作者的分析平衡而深入,帮助读者理解两种格式各自的适用场景——JSON更轻便直观,适合Web API和前端交互;XML则在需要复杂验证、命名空间管理和文档自解释性的企业级或配置场景中更显稳固。

IT 2015-01-25 21:38:28 / 累计浏览 3,434

STL笔记之hashtable

这篇讲的是SGI STL中经典hashtable容器的源码实现。作者从自己动手实现一个简单hashtable的经历切入,发现与STL源码的思路惊人相似,由此展开对底层机制的剖析。 文章深入讲解了SGI版本hashtable的核心设计:它采用开链法解决冲突,节点通过链表串联在bucket中。关键实现的巧妙之处体现在两处:一是迭代器逻辑,当前bucket遍历完毕后,能自动跳转到下一个非空bucket,保证了前向遍历的连贯性;二是容器容量的管理,预置了一张精心计算的素数表来动态选择桶的数量,这能有效降低哈希冲突概率,提升查找效率。 通过拆解节点定义、迭代器操作符和容器初始化等关键源码,文章清晰地展现了hashtable从数据结构到算法选择的完整实现脉络,有助于读者真正理解这个高效容器背后的工程权衡与精巧构思。

IT 2015-01-25 21:37:04 / 累计浏览 4,057

STL笔记之二叉查找树

这篇讲的是STL关联容器(如map和set)背后的核心数据结构——二叉查找树及其变体。作者从SGI STL的实现出发,系统梳理了三种关键树结构的区别与联系。 文章首先剖析了基础的二叉查找树(BST),说明了它通过键值左小右大的规则实现对数时间操作,但也指出了其退化为链表导致效率骤降至O(N)的风险。为了解决平衡问题,文章对比了AVL树和红黑树:AVL树是严格的平衡树,任何节点的左右子树高度差不超过1,通过旋转保持平衡,保证了稳定的O(log n)性能;而红黑树通过着色规则和局部调整,在不过分追求严格平衡的前提下,同样实现了高效的查找与动态操作。 最后,文章将理论落地,解释了为何红黑树凭借其在插入和删除场景下更少的旋转次数,成为SGI STL中map、set等容器的底层选择,而不只是理想中“更平衡”的AVL树。这种从原理到工程实现的脉络,对于理解标准库的设计哲学很有帮助。

IT 2015-01-25 21:36:02 / 累计浏览 3,747

Redis编程小技巧拾遗

这篇讲的是作者在阅读Redis源码时,特意“拾遗”的几个精妙的C语言编程技巧。作者从Redis简洁的1.0版本入手,并未重复大众熟知的源码剖析,而是聚焦于那些能让代码更健壮、更高效的小细节。 最典型的是“空数组”技巧:在`sdshdr`和`zskiplistNode`结构体的末尾定义一个空数组成员(如`char buf[]`和`level[]`)。这允许在动态内存分配时,根据实际需要的数据长度(如字符串长度、跳表层数)一次性申请合适大小的内存,实现了结构体内可变长数据的紧凑存储。 另一个常见但重要的技巧是使用 `do { } while(0)` 来包裹宏定义中的多条语句。这不仅能确保宏在if等控制流中像单条语句一样安全执行,文章还展示了将其用于简化流程控制的用法,使代码逻辑更清晰。 此外,文章还介绍了Redis中定制化的断言宏`redisAssert`和分级日志系统`redisLog`,前者在条件失败时能输出详尽的上下文信息,后者则允许根据日志级别进行过滤。这些实现虽小,却体现了生产级项目对可调试性和可观测性的重视。 这些从顶级项目中提炼出的技巧,对任何C/C++开发者都有直接的借鉴意义。

IT 2015-01-24 23:16:57 / 累计浏览 3,212

围住神经猫 1步玩法-”作弊”

这篇讲的是如何通过“破解”微信分享机制,来实现游戏“围住神经猫”分享链接的“作弊”。作者从被朋友圈刷屏后自己试玩出发,发现分享链接的击败百分比与步骤数直接相关,于是产生了一个“纯娱乐”的技术念头:能否自己伪造一个分享页面,让好友看到夸张的“1步通关”战绩? 核心实现思路很直接:分享的只是一个网页URL,因此可以自己生成一个静态HTML页面,自定义页面的`title`(例如“我用了1步围住神经猫”),并引用原游戏的图片作为缩略图,以便微信抓取。关键的巧妙之处在于,为了不让好友点进这个“假”页面,利用浏览器`onload`事件直接通过`location.href`跳转到原游戏网址。 文章进一步深入,指出微信提供了更规范的分享JS-SDK。通过调用`WeixinJSBridge.invoke`接口,可以一站式设置缩略图、标题、描述以及跳转链接,使得这个“小恶作剧”的实现更加优雅和可控。作者附上了相关的JavaScript代码片段,展示了如何配置`shareTitle`和`link`等参数,为想了解微信网页分享机制的朋友提供了一个生动有趣的实现案例。

IT 2015-01-23 23:56:07 / 累计浏览 5,817

位运算小结(按位与、按位或、按位异或、取反、左移、右移)

这篇讲的是位运算的基础知识系统梳理。作者从计算机底层表示数字的“补码”概念讲起,为后续运算奠定了基础,然后逐一拆解了按位与、或、异或、取反、左移、右移这六种核心操作。 文章没有停留在理论定义,而是用同一个数字(10与-10)作为贯穿始终的例子,详细演示了每种运算的二进制计算过程与最终结果。这种对比式的呈现方式,让不同运算符之间的逻辑差异(比如“与”是“都真才真”,“或”是“有真就真”)变得一目了然。 文中还特别提到了异或运算的一个巧妙应用:利用其“任何数与0异或结果不变”的特性,可以实现两个变量的交换而不借助临时变量。这个经典的算法小技巧,为实用的运算知识增添了工程色彩。 对于需要回顾计算机底层操作、或在实际编程(如权限管理、标志位处理、算法优化)中直接使用位运算的开发者,这篇文章提供了一份清晰、直观的参考手册。

IT 2015-01-23 23:54:56 / 累计浏览 6,294

如何编写一个JSON解析器

这篇讲的是如何从头实现一个JSON解析器。作者从JSON解析器的基本定义出发——即将JSON字符串转换为语言内存中的数据结构,对比了它与XML的差异,并梳理了JSON基本类型与Java数据结构(如Map、List)的映射关系。 文章的核心在于拆解实现思路。它强调解析器应采用流式单次扫描,本质是一个状态机。实现的关键步骤被清晰地勾勒出来:首先,需要封装一个`CharReader`来支持`peek()`操作,以便在不移动指针的情况下预判字符;其次,将原始字符流进一步抽象为Token流(如`BEGIN_OBJECT`、`STRING`等),从而大幅简化状态判断逻辑。最后,文章点出了处理嵌套结构的技巧:利用栈来管理Object和Array的构建,遇到开始标记时创建对应的Map或List并压栈,以此高效地完成递归解析。 整个实现过程体现了从具体到抽象、逐步化繁为简的设计思想,将看似复杂的解析任务分解为字符读取、Token识别和栈操作几个清晰的模块。

IT 2015-01-23 23:50:52 / 累计浏览 3,009

如何正确地处理时间

这篇讲的是程序开发中一个极易被忽视却至关重要的陷阱:如何正确地存储和处理时间。作者从大多数开发者习以为常的“获取本地时间直接存库”这一操作切入,一针见血地指出了问题的核心——本地时间丢失了时区信息,一旦环境变更,时间数据就会错乱。 文章深入浅出地解释了时区的本质,并引出了“绝对时间”(即时间戳)的概念。作者强调,正确的做法是遵循“存储与显示分离”的原则:在数据库中只存储一个与时区无关的整数或浮点数时间戳。这样,无论服务器时区如何设置,存储的时间值都是全局一致的。显示时,再根据用户的时区将时间戳格式化为字符串。 这种方案带来的好处是根本性的:时间比较和筛选变成了简单的数值运算,彻底摆脱了时区转换的泥潭。文章通过Java示例清晰地展示了一个时间戳如何被正确格式化为不同时区下的本地时间,直观地证明了其有效性。对于任何需要处理国际化时间数据的开发者来说,这篇文章揭示的原理和提供的方案,都是避免未来无尽调试的必备知识。

IT 2015-01-20 23:19:00 / 累计浏览 15,996

红黑树并没有我们想象的那么难(下)

这篇讲的是红黑树如何“落地”到我们常用的STL map中。作者从SGI STL源码出发,直接剖析红黑树底层类`_Rb_tree`的实现细节。 文章亮点在于对核心机制的拆解。首先解释了`_M_header`这个辅助头节点的巧妙设计,它同时维护根节点、最小与最大节点,让管理变得规整。重点展开的是两种插入策略:`insert_equal()`允许重复值,逻辑直白;而`insert_unique()`的去重判断则颇为精巧,它利用二叉搜索树性质,在寻找插入位置时通过一次向右再持续向左的走位,结合对前驱节点的比较,就能“hack”地判断出键值是否已存在。 最后,文章也回应了“为何用红黑树而非AVL树”这个经典问题,点明红黑树在搜索效率与修改开销(插入至多两次旋转)之间取得了更好的平衡,是一种实用主义的折中方案。作者通过源码把红黑树从理论概念带到了具体的工业级实现,让那些“旋转”、“着色”的抽象描述变得清晰可触。

IT 2015-01-20 23:17:35 / 累计浏览 21,397

红黑树并没有我们想象的那么难(上)

这篇讲的是红黑树,作者从一个初学者的常见困惑出发:红黑树情况太多,似乎很难。文章给出的核心解法是“合并”——通过归结和简化情况来降低理解门槛。 作者首先回顾了红黑树必须满足的五个性质,然后直接切入数据结构定义和基础的二叉搜索树操作。全文的重点放在对插入与删除算法的拆解上。对于插入,文章将其归结为三种核心情况,通过逐步调整颜色和旋转来维持性质。对于删除,分析则更为细致,分多种情况(例如“兄弟节点颜色”或“侄节点颜色”不同)讨论了重新着色和旋转的策略,并配以直观的印象图和伪代码。 整篇文章像一份详尽的算法推演笔记,通过枚举具体场景并展示调整步骤,试图将复杂的平衡操作变得有迹可循。对于想从原理层面弄懂红黑树实现细节的读者,这种直面各种案例的讲解方式可能比单纯记忆规则更有帮助。

IT 2015-01-19 23:52:38 / 累计浏览 7,453

并发编程系列之一:锁的意义

这篇讲的是并发编程中“锁”的根本意义,远不止让一段代码串行执行那么简单。作者从一个经典的多线程计数问题出发:500个线程对一个全局变量执行++操作,若无任何保护,最终结果几乎总是小于预期的5000000,原因是++操作本身不是原子的,发生了数据竞争。 接着,文章展示了如何用自旋锁(Spinlock)保护这段代码,并成功保证了结果的正确性。但这仅仅是表象。作者深入剖析,提出了一个关键问题:在编译器和CPU都可能对指令进行乱序重排的情况下,如何确保锁保护区域内的代码不会被“甩”到锁外并发执行? 这便引出了锁更深层的两重意义:第一重是字面上的互斥,保证同一时刻只有一个线程进入临界区。第二重,也是更关键的一重,是内存操作的可见性与顺序性——即锁的“获取(Acquire)”和“释放(Release)”语义。它们构成了强大的内存屏障,防止了前后的读写操作被错误重排,从而确保了被保护的代码及其对共享数据的修改,对其他线程表现出确定且正确的顺序。理解这一点,才算真正读懂了锁在并发世界中的核心价值。

IT 2015-01-19 23:46:11 / 累计浏览 2,310

从LongAdder看更高效的无锁实现

这篇讲的是作者从阅读Guava源码时接触到AtomicLong出发,为何要深入研究Doug Lea设计的LongAdder。文章的核心在于解析LongAdder如何实现比AtomicLong更高并发性能的无锁计数。 我们知道,AtomicLong的高效依赖CAS操作,但在高并发下,大量线程竞争同一个value变量会导致频繁的CAS失败与重试,形成恶性循环。LongAdder的核心实现思路正是解决这个问题:它采用“分段更新”的策略,将单一value的压力分散到一个Cell数组中。 具体来说,当CAS更新base值失败时,LongAdder会根据线程信息将更新操作分散到cells数组的某个Cell单元上。每个线程尽可能只更新自己命中的那个Cell的value值,从而极大降低了高并发下的冲突概率。只有当需要获取总数时,才将base值与所有Cell的value累加。文章还巧妙地分析了其中的权衡:在低并发时,CAS更新base值大概率成功,不会立即进入分段逻辑,从而兼顾了空间与效率。此外,当分段更新本身也失败时,`retryUpdate`方法会进行cells数组的扩容和重哈希,进一步降低冲突。 作者通过逐行解析`add`方法和`retryUpdate`逻辑,清晰地揭示了这种以空间换时间、通过降低热点数据“热度”来换取吞吐量提升的精妙设计。这正是一篇典型的源码分析文章,带我们看到了高并发场景下无锁算法的一个经典优化范例。

IT 2015-01-14 13:54:20 / 累计浏览 2,468

从千分位格式化谈JS性能优化

作者从最基础的数字千分位格式化需求出发,系统性地探索了六种JavaScript实现路径。从最开始循环操作数组和字符串,到利用正则表达式匹配末尾三位,再到最终利用前瞻断言的“一行代码”方案,文章层层递进地展示了不同算法思路的演进与权衡。 真正的价值在于文末附上的性能测试数据。在对不同数量级的数字执行5000次操作后,结果清晰表明:基于字符串拼接和`slice`截取的方案(方法二与四)性能表现最佳;而看似简洁的分组合并法(方法五)耗时最高,揭示了正则表达式与字符串方法在不同场景下的性能成本差异。 这篇文章不仅仅是提供几个代码片段,它示范了一种面对常见需求的优化思维:从直观解法到性能洞察,最终通过基准测试来验证假设。对于开发者而言,其核心启示在于——选择“最优”代码,往往需要基于具体场景和实测数据,而非仅凭直觉或代码简洁度。

IT 2015-01-14 13:42:39 / 累计浏览 3,361

C语言的整型溢出问题

这篇讲的是C语言中一个常被忽视但极其危险的安全陷阱:整型溢出。作者从“无符号整型溢出有定义(模运算),而有符号整型溢出是未定义行为”这个核心区别出发,拆解了问题的根源。 文章的重点在于揭示溢出带来的真实危害,并用四个具体示例生动说明。例如,一个简单的`while`循环可能因`short`溢出而变成死循环;一次看似安全的内存拷贝检查,会因`int`到`size_t`的隐式类型转换而被绕过,导致缓冲区溢出。更严重的案例直接关联到OpenSSL的Heartbleed漏洞,展示了如何通过精心构造的输入,让`malloc`分配的内存远小于预期,从而引发远程代码执行。 作者还特别提醒了编译器优化带来的“反直觉”行为:编译器在`-O2`等优化级别下,会假定有符号整型不会溢出,从而直接删除你手写的溢出检查代码。这意味着,那些在调试版本下有效的保护措施,可能在发布版本中完全失效,这使得问题更加隐蔽和致命。 整篇文章就像一份简洁的安全编码警示录,它没有停留在理论定义,而是通过剖析编译器行为和真实漏洞,提醒开发者在处理整数时必须如履薄冰,尤其是在涉及内存操作和用户输入的场景中。

IT 2015-01-13 23:06:54 / 累计浏览 2,514

vfork 挂掉的一个问题

这篇讲的是 vfork 系统调用中一个经典的“坑”:为什么在子进程中使用 `return` 会导致程序崩溃,而用 `exit()` 却不会?作者从知乎上的一个实际提问出发,澄清了一些可能误导人的回答。 文章首先梳理了 fork 与 vfork 的根本区别——fork 会复制父进程内存,而 vfork 则让父子进程共享同一内存空间。vfork 的设计初衷是为了在创建子进程后立即执行 exec 时提高效率。关键点在于,vfork 保证子进程先运行,直到它调用 `exit()` 或 `exec()` 后,父进程才继续。 崩溃的根源正在于此:因为父子进程共享栈空间,如果在子进程的 main 函数中使用 `return`,它会像正常函数返回一样修改栈(弹栈、释放局部变量),这相当于破坏了父进程正在使用的栈。当父进程稍后恢复执行时,栈已被改写,导致不可预知的行为或直接崩溃。而 `exit()` 是直接终止进程,不会去操作和释放共享的函数栈,因此父进程能安全恢复。 文章最后也指出,现代操作系统已通过“写时拷贝”技术大幅优化了 fork,使得 vfork 的性能优势不再明显,Linux 手册也不鼓励使用它,除非对性能有极致要求。理解这个底层机制,有助于我们避免这类隐蔽的陷阱。

IT 2015-01-11 23:19:57 / 累计浏览 3,691

为什么超长列表数据的翻页技术实现复杂(二)

这篇讲的是如何为超长列表设计高效的翻页方案。作者延续上篇对问题的探讨,以新浪评论系统的经典演进为引子,剖析了从缓存翻页到自定义文件索引等方案的演进与局限,最终聚焦于一个核心矛盾:MySQL常用的B-TREE索引结构,虽擅长点查,却天生不利于范围查询(Range Scan),导致翻页性能低下。 为此,文章提出了两种实用的二级索引思路。一是“Count index”,通过额外记录每个非叶子节点下的条目总数,实现对任意offset位置的快速定位。二是“Offset index”,在应用层(如Redis)缓存部分offset与对应ID的映射关系,当需要定位某个offset时,可以从最近的缓存点开始扫描。 文章进一步给出了这两种思路在MySQL和Redis中的具体实现示例。比如Count index可以建一张辅助表按月份记录各用户的评论计数,而Offset index则在用户翻页时动态缓存到Redis,并在数据变更时及时清理。这两种方法都已在实际生产环境中得到验证,为解决超长列表的翻页难题提供了清晰且可落地的技术路径。

IT 2015-01-04 23:18:57 / 累计浏览 1,988

HLLC基数估算算法在腾讯数据仓库TDW中应用

这篇讲的是腾讯数据仓库TDW如何用一个巧办法,又快又省地计算海量数据里的“不同值个数”(基数)。背景很实际:传统精确去重在大数据面前太耗资源了。文章的核心方案,是引入了HLLC(HyperLogLog Counting)基数估算算法,并将其封装成一个极其简单的SQL聚合函数`est_distinct`。 文章不仅告诉你“是什么”,还深入拆解了“怎么做”。从HQL如何翻译成MapReduce作业,到Map端如何用一个64K桶的数组进行局部聚合,再到Reduce端如何合并数组并套用HLLC公式计算,整个过程图文并茂。其中的难点和巧妙之处在于内存管理:当数据分布不均、单个Key记录海量时,TDW采用“分而治之”策略动态分配内存块,并设计了专用的序列化(SerDe)方式来高效处理溢写到磁盘的Hash表片段,有效平衡了内存与IO开销。 最后通过实测对比给出了明确结论:在数据分布集中的较理想条件下,使用64K桶的HLLC估值计算(精度超99.4%),相比精确去重能带来数倍的效率提升。对于需要在大规模数据上快速获得近似唯一值计数的场景,这提供了非常清晰且可落地的实践参考。

IT 2015-01-04 23:05:15 / 累计浏览 1,709

NUMERIC和DECIMAL的区别是什么?

这篇讲的是SQL Server中两个容易混淆的精确数值类型:NUMERIC和DECIMAL。文章开篇就点明,在功能上它们是完全同义的,都用于精确存储数值,最大精度都是38位,主要区别体现在类型定义的细微处。 核心差异在于精度(p)和小数位数(s)的约束关系:对于decimal(5,2)这样的定义,p=5代表总位数(小数点左右数字之和),s=2指定小数位数。文章特别强调,p和s必须满足 0 ≤ s ≤ p ≤ 38。另一个关键点是,在Transact-SQL看来,decimal(5,5)和decimal(5,0)会被视为不同的数据类型,这种对精度组合的严格区分影响着存储和计算。 在数据转换方面,文章给出了明确的警示:从decimal/numeric转换到float/real会导致精度损失,而从整数或货币类型转换过来则可能引发溢出。这些细节对于设计需要严格数值一致性的金融或科学计算系统尤为重要。 总的来说,这篇文章厘清了这两个类型的表面相似与本质联系,适合所有需要处理精确数值的数据库开发者,帮助他们在定义表结构时做出更清晰、无歧义的选择。

IT 2015-01-04 22:54:28 / 累计浏览 12,232

Linus:为何对象引用计数必须是原子的

Linus在这篇长文里,用一个具体的编程细节,撕开了“并行计算很简单”这个流行错觉的口子。他聚焦于一个看似基础的问题:为什么在多线程环境下,对象的引用计数必须是原子操作。 文章的核心论证在于区分两种完全不同的锁机制:一种是保护“对象数据”的锁,另一种是保护“查找对象”这一过程的锁。Linus指出,引用计数的原子性之所以关键,是因为在复杂的对象图(graph)中遍历时,为了避免死锁(特别是经典的ABBA死锁),你必须在持有对象A的锁时,安全地转向对象B。此时,原子性地增加对象B的引用计数,就成了确保对象B在解锁后不会“消失”的唯一安全绳。如果你认为引用计数不需要原子化,这恰恰暴露了你对锁机制复杂性的无知。 通过这个精巧的例证,Linus抨击了那些只看到简单数组并行排序、却无视真实世界中对象动态分配与释放复杂性的乐观论调。他用这个例子揭示,许多被宣传为“容易并行化”的案例,其实都巧妙回避了并发编程中最棘手的部分。这篇文章最终指向一个硬核结论:并发设计本质上是困难的,而许多关于并行未来的讨论,建立在对这种困难严重低估的基础上。

IT 2014-12-30 12:48:15 / 累计浏览 2,858

数据分析中位数的应用

这篇讲的是如何让枯燥的折线图更直观地传达信息。作者发现,普通折线图常常无法突出数据中的关键点,于是通过对比两张图(A图是常规折线,B图则将最高的几个数据点用特殊图标标出),直观地展示了“一目了然”的视觉效果差异。 核心问题随之而来:如何从一堆数据里,自动找出那个用于区分“特殊点”与“普通点”的分界线呢?文章对比了两种常见方法——平均数和中位数。作者指出,平均数虽然反映整体水平,但极易被一两个极端的高值或低值“带偏”,无法稳定代表“大多数”情况。相比之下,中位数是把数据排序后取中间的那个数(或两个数的平均),它不受极端值影响,更能代表数据的“中间”或“典型”水平,因此成为构建这个分界线的更优选择。 为了便于实践,作者还提供了一个计算中位数的PHP函数代码示例。整篇文章从一个可视化的痛点切入,落到具体的统计概念辨析和算法实现,思路清晰,具有不错的实操参考价值。