自定义博客皮肤VIP专享

*博客头图:

格式为PNG、JPG,宽度*高度大于1920*100像素,不超过2MB,主视觉建议放在右侧,请参照线上博客头图

请上传大于1920*100像素的图片!

博客底图:

图片格式为PNG、JPG,不超过1MB,可上下左右平铺至整个背景

栏目图:

图片格式为PNG、JPG,图片宽度*高度为300*38像素,不超过0.5MB

主标题颜色:

RGB颜色,例如:#AFAFAF

Hover:

RGB颜色,例如:#AFAFAF

副标题颜色:

RGB颜色,例如:#AFAFAF

自定义博客皮肤

-+
  • 博客(30)
  • 收藏
  • 关注

原创 手撕环形队列系列三:多生产者并行写入

本文是手撕环形队列系列的第三篇,之前的文章链接如下:《手撕环形队列》《手撕环形队列系列二:无锁实现高并发》之前系列文章介绍的环形队列,已经能够支持多生产者和多消费者并发操作,数据操作模式为:生产者准备好数据,然后push()方法放入环形队列中;消费者准备好接收的缓冲区,然后调用pop()方法将数据写入接收缓冲区中。这种模式下,生产者准备数据过程中,需要分配内存。把数据写入环形队列后,这个内存就不需要了,得释放掉。这样,会导致生产者频繁进行内存分配和释放的动作。因此,对性能要求更苛刻

2021-08-04 09:14:22 202

原创 手撕环形队列系列二:无锁实现高并发

本文是手撕环形队列系列的第二篇,之前的文章链接如下:《手撕环形队列》前面文章介绍的是一个比较基本的环形队列,能够在多线程中使用,但有一个前提:任意时刻,生产者和消费者最多都只能有一个。也就是说,如果有多个生产者要并发向队列中写入,需要在外部进行加锁或其它方式的并发控制,保证任意时刻最多只有一个生产者真正向环形队列进行写入。同样的,多个消费者要从队列中读取进行消费,也需要在外部进行加锁或其它方式的并发控制,保证任意时刻最多只有一个消费者从环形队列进行读取。本文的内容,就是介绍如何能够支持多线程场景

2021-08-04 09:09:06 860

原创 手撕环形队列

环形队列,是一种非常高效的数据结构,在操作系统、数据库、中间件和各种应用系统中大量使用。今天咱们就来盘它。下面是一个环形队列的示意图:环形队列,有两个指针:头指针和尾指针。在队尾写入,移动尾指针;从队列头部读取,移动头指针。环形队列,是一种特殊的队列。因此具有队列的显著特征:先进先出。其特殊性在于"环形", 内存空间可以不断重复使用,无需频繁分配和释放内存。并且,可以非常容易实现无锁的数据结构,在多生产者多消费者的多线程并发场景中,效率非常高。今天,我们先来实现一个最基本的

2021-07-29 11:31:22 318

原创 手写基数排序算法

基数排序(Radix sort)是一种非比较型整数排序算法,其基本思想为:一个待排序整数序列,将其中每个整数看成由不同位构成(比如,个位十位百位千位...)。可以先按个位的数值,将这些数分配到0~9的10个桶中,然后再按从0到9的顺序把这些数从10个桶中收集回来,这时这些数就已经按照个位排好序了。然后再按照10位上的数值,把这些数分配到10个桶中,分配完毕后再次收集回来,这时这些数就已经按照十位和个位排好序了。再按百位、千位依次进行上述过程,直到排序的位数到达数列中最大数的最高位,则排序结束。

2021-07-28 12:40:23 117

原创 手写计数排序算法

计数排序算法的思想为:待排序的数列中,先找出最小值min和最大值max,形成一个值区间[min, max]。然后建立一个存储空间,存储空间中的每个位置,对应值区间[min, max] 中的一个值,形成一对一的关系。每个位置上,存储这个值出现的次数,​叫做计数。初始计数值都为0。然后遍历待排序的数列,对每个值,将这个值对应的存储空间上​对应位置的计数值加一。遍历完成后,存储空间上每个位置对应的值和计数,就形成了最终排好序的数列。计数排序算法,本质上是一种桶排序,只是每个桶的大小为1,每个桶用一个

2021-07-27 09:16:54 104

原创 手写桶排序算法

桶排序算法的思想是:把待排序数据,按照一定的规则,分配到不同的桶中,使得桶与桶之间形成有序的关系。然后每个桶内再单独排序,这样整个数列就全局有序了。因此,桶排序的核心要点,包括:1)桶的数量2)数据分配到桶的规则3)桶内排序的算法桶的数量如果太多,可能会导致有些桶没有数据,白白浪费这些桶的空间。如果桶的数量太少,可能会导致桶内数据太多,桶内排序时间过长。数据分配到桶的规则,如果与数列不匹配,可能会导致有些桶分配数据过多,有些桶数据很少甚至没有数据的情况发生。桶内排序的算法,可以使

2021-07-26 11:25:34 119

原创 手写堆排序算法

堆排序算法,是通过堆这种数据结构来实现排序。堆,其实就是二叉树。由于排序有从小到大、从大到小两种排序方式,对应的堆也分为最小堆和最大堆。最小堆,就是一颗完全二叉树,每个节点都比其左子节点和右子节点小,整个树的根节点最小。最大堆,就是一颗完全二叉树,每个节点都比其左子节点和右子节点大,整个树的根节点最大。用一个连续的数组内存空间,就能表示堆。如下图所示:​数组中每个元素,代表最小(大)堆中的一个节点,每个节点最多有两个子节点: 左子节点和右子节点。上图中的箭头,是从父节点指向.

2021-07-26 10:51:32 417

原创 手写归并排序算法

归并排序算法,是基于分而治之的思想,核心思路为:要对一个数组进行归并排序,则先把这个数组从中间切开,变成2个子数组。然后对每个子数组进行归并排序,最后,2个排好序的子数组再合并成一个有序的数组,这样整个数组的排序就完成了。而对每个子数组进行归并排序,又使用了上述的算法过程,因此是个递归调用。当子数组只有一个元素时,归并排序函数直接返回。因此,归并排序算法的过程,包括:二分法,切分成2个子数组; 递归对2个子数组进行归并排序; 将2个排好序的子数组,合并为1个有序数组。归并排序算法

2021-07-21 20:59:09 236

原创 手写希尔排序算法

昨天给大家介绍了插入排序算法,今天介绍的希尔排序算法,其实是插入排序算法的更高效改进版。该算法因D.L.Shell于1959年提出而得名。希尔排序是基于插入排序的以下两点性质而提出改进方法的:插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率; 但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位;希尔排序的基本思想是:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录"基本有序"时,再对全体记录进行依次直接插入排序。希

2021-07-20 14:05:02 105

原创 手写插入排序算法

今天给大家介绍插入排序算法,并且给出源码实现。插入排序算法的思想是,对于一个包含N个未排序元素的数组,我们可以从位置1开始,通过比较元素1和元素0,把元素1插入到元素0的前面或者后面,实现这两个元素的有序排列。然后再取元素2,这个元素前面的序列都已经是有序的了,因此只要找到元素2在前面序列中的位置,进行插入,那么元素0至元素2就都排好序了。再取元素3,向前面有序序列进行插入,依次进行,一直到最后一个N-1元素插入完成,这样整个数组就都排好序了。因此,插入算法的核心步骤是查找和插入。查找,可以通

2021-07-19 08:33:52 182

原创 手写冒泡排序和选择排序算法

今天给大家介绍另外2种排序算法:冒泡排序和选择排序。它们的思路有些相似,因此一并介绍,并且给出源码实现。一、冒泡排序自然界中,经常会看到一个气泡从水底冒出,最开始很小,浮向水面的过程中会变得越来越大,到达水面时最大。冒泡排序算法,就是从上面的场景中得到的灵感启发,其想法如下:要对一个未被排序的数组,实现将元素从小到大的顺序进行排序,那么可以从第一个元素开始,与下一个元素进行比较,如果比下一个元素大,则二者交换顺序。接着,按同样的方式,比较第二个元素和第三个元素,如果比后一个元素大,

2021-07-16 08:58:05 427

原创 手写QuickSort算法

快速排序(QuickSort),是一种比较经典的排序算法,在很多基础库中都有实现。今天给大家介绍一下快速排序(QuickSort)原理,并且咱们自己动手实现一把。QuickSort的原理如下:我们先从数组中取一个基准元素,然后通过多次比较和元素交换,最终找到一个位置,使得左侧元素都比这个基准元素小,右侧元素都比这个基准元素大。然后把基准元素放到这个位置上,这个位置,就是这个基准元素在最终排好序的数组中的位置,因此它就不需要变动了。然后,我们左侧和右侧分别变成一个数组,应用上面的方法,对其进行排

2021-07-15 10:40:56 101

原创 神奇的Duff‘s device

C语言编程领域,达夫设备(Duff's device)是一种将循环展开执行,从而提高执行效率的一种技术方法。汤姆·达夫于1983年11月发明了这种方法,可能是迄今为止利用C语言switch语句特性所作的最巧妙的实现。通过一个简单的例子,来理解一下什么是Duff's device.比如,一个循环复制的函数实现(只是例子,不考虑memcpy):void send( int * to, int * from, int count) { for (int i = 0; i <...

2021-07-14 11:42:55 335

原创 Structured Concurrency for C

今天给大家介绍一个开源组件libdill,用C编写,实现了Structured Concurrency。什么是 Concurrency?什么又是 Structured Concurrency呢?Concurrency,翻译为“并发”。最直接的技术解释,就是一个线程处理任务1一会儿,然后处理任务2,过一会儿又切换回来处理任务1,这样在多个任务之间不断切换。从这些任务来看,每个任务都是在不断执行和处理的。这就叫并发。有一个和并发非常容易混淆的术语,叫并行(Parallelism)。并行是指多个线程,

2021-07-13 09:19:47 213

原创 什么?C语言也能try...catch!

现代编程语言,基本都提供了try...catch 语法,用于进行程序逻辑的异常处理。为什么要用try...catch?我们来看一下常规的程序写法:int biz_xxx(...) { // step1: do logic1 .... if (ret != 0) { // 错误处理1 ... } // step2: do logic2 ... if (iret != 0) { // 错误处...

2021-07-08 14:01:33 1722

原创 巧妙!如何检测一个链表是否有环?

题目:如何检测一个链表中,是否存在环形?就像下图所示:能够直接想到的方法,就是:创建一个hashtable,用于记录链表中的节点地址。然后开始遍历这个链表,对每个节点,查找其地址是否存在于hashtable中,如果存在,则认为产生了环形,算法结束。如果不存在,把这个节点的地址添加到hashtable中,继续判断下一个节点。如果遍历完链表,就认为没有环形,算法结束。上述算法,对于一个比较长的链表,需要消耗大量的内存来存储hashtable,并且查找和添加操作都需要消耗cpu时间,因此不是一个

2021-07-07 08:44:04 1398 1

原创 学习哪门编程语言好,我来回答

哪门编程语言最好,一直是个争论不休的话题。历史上最出名的话题就是“PHP是最好的编程语言”。TIOBE公司,每月会发布一版编程语言的流行度排行榜,见 https://www.tiobe.com/tiobe-index/。但TIOBE编程排行榜,统计的是全球范围的数据,与中国国内的就业情况并不一致。中国国内的就业情况,大家只要去各大招聘网站上看一下软件开发相关的工作岗位,从数量上就能看出哪门编程语言更具有就业前景。单纯从就业岗位数量来说,Java、Python、Web前端(h5+css+js)位居

2021-07-06 08:42:55 815 2

原创 软件工程,其实没有任何工程而言

将大型软件研发过程,叫做软件工程,实在是个笑话。能够叫做工程的,必然是以科学为基础,将科学知识凝结为定理和公式,从而能够系统地传承。这样,工程人员经过系统化培训后,就可以胜任工程中的工作岗位,从而能够进行大规模的工程实施。土木、建筑、航空、交通、能源等等行业,都是将数学、物理、化学等科学知识凝结为行业定理和方法,如何分析土质,如何测量风力,如何评估抗震能力,这些都是有系统的方法的。经过系统化培训,从业人员都可以掌握这些方法,从而胜任工程中的具体工作。但这些,在软件行业中都是不存在的。...

2021-06-16 08:39:24 96

原创 为什么聪明的程序员会写出糟糕的代码

最近阅读一本书《软件困局:为什么聪明的程序员会写出糟糕的代码》,作者对自身30多年的软件开发经历进行回顾,阐述了大学教育与企业产品研发之间的巨大鸿沟,对程序员为什么会写出糟糕的代码,软件质量如此之差,软件开发为什么这么难...., 这些问题都进行了深入的剖析。我自己从事一线软件开发,也已经20多年了。作者的很多经历,我自己也亲身经历过。因此,我准备将很多精彩的观点分享给大家。本文是第一篇。在学校里,我们学习到一些编程相关的知识,并完成了一些上机作业。感觉到自己学会了很多东西。但一旦到企业里,进

2021-06-11 11:32:00 202 1

原创 内嵌双向链表的设计与实现

前一篇文章《双向链表,还能这么实现》,介绍了双向链表如何彻底消除内部的NULL指针,从而代码实现更加简洁,并且不增加任何额外的内存。实际项目中,对双向链表的需求场景非常多,并且有很多变体的实现。今天介绍内嵌双向链表。先说说场景的需求:双向链表的经典实现结构,如下图所示:链表本身维护各个节点,业务使用者需要将自己的数据结构以指针的方式放入链接节点中。这意味着,向链表增加一个数据,需要做两次内存分配:一次是链表节点自身的内存分配,另一次是业务数据结构的内存分配。在一些对性能要求严苛的场景中,希望

2021-06-09 09:10:12 215

原创 双向链表,还能这么实现

双向链表,是一种常用的数据结构。在C语言中,双向链表的实现,通常采用下面的数据结构:typedef struct dlist_node_t { struct dlist_node_t* pprev; struct dlist_node_t* pnext; void* pdata;} dlist_node_t;typedef struct dlist_t { dlist_node_t* phead; dlist_node_t* ptail;} dlist_t;...

2021-06-08 10:59:17 176

原创 用C语言实现interface

本文是C语言封装设计的第四篇文章,前三篇请见《C 语言面向对象的封装方式》、《 C语言面向对象的封装方式(示例)》和《C语言数据结构的封装方法》。本文介绍C语言中如何实现接口(interface),应用场景为:多个提供者提供的服务接口函数完全相同,但实现不同,因而在性能、可靠性等方面有所差异。比如,两个HashMap的实现,一个性能非常快,但内存占用大;另一个内存占用非常少,但性能一般。但二者提供的服务接口函数都是一样的,如get、put等。通过interface,我们可以把服务提供者的服务界面抽象

2021-06-07 09:07:01 1604 1

原创 C 语言数据结构的封装方法

本文是C语言封装设计的第三篇文章,前两篇请见《C 语言面向对象的封装方式》和《 C语言面向对象的封装方式(示例)》。本文介绍C语言中如何封装数据结构,让调用者可以引用这个数据结构,但无法获知这个数据结构的内部构成,也无法读写这个数据结构的成员变量。解决这个问题,常见的手法是提供一个new_xxx 函数,返回一个数据结构的指针。比如,数据结构为 dlist_t, 那么在 dlist.h和dlist.c中定义如下:// dlist.h#ifndef DLIST_H#define DLIST_H

2021-06-04 09:08:29 1628 1

原创 C 语言面向对象的封装方式(示例)

前一篇文章,我介绍了C语言编程常见的两种代码组织方式:1)函数和数据结构分离 2)封装,从原理上讲述这两种方式的根本区别。大型项目中,推荐采用封装的方式,有利于团队协作和每个模块独立演进。本文,给出一个代码示例,具体展示这两种方式在代码实现上的差别。业务场景描述如下:对于数据库、文件系统、存储系统等,数据通常以页(Page)为单位,在数据文件中进行组织。服务进程以页为最小IO单位从磁盘上读出,并在内存中缓存这个页面。后续业务过程如果读取的数据在这个页中,则直接从内存页获取数据。如果要写入新数据.

2021-06-03 10:28:37 569

原创 C 语言面向对象的封装方式

C 语言面向对象的封装方式C语言是面向过程的编程语言,一个程序的运行逻辑就是由一个个函数调用构成,函数里面会操作各种各样的数据结构。因此,函数和数据结构,是C程序中的两大组成部分。通常,我们接触的大多数C程序,代码的逻辑组织关系就如下图所示:这种程序中,数据结构与函数是一种松耦合关系,数据结构的内部成员,对所有函数都是可见的,都是可读写的。一个函数会直接操纵多个数据结构,从而实现特定的业务逻辑。这种设计的优点是:简单。数据结构和函数可以分开进行设计,适用于小型项目、快速构建原型。但这种.

2021-06-02 09:31:03 353

原创 C 语言面向对象的封装方式

C语言是面向过程的编程语言,一个程序的运行逻辑就是由一个个函数调用构成,函数里面会操作各种各样的数据结构。因此,函数和数据结构,是C程序中的两大组成部分。通常,我们接触的大多数C程序,代码的逻辑组织关系就如下图所示:这种程序中,数据结构与函数是一种松耦合关系,数据结构的内部成员,对所有函数都是可见的,都是可读写的。一个函数会直接操纵多个数据结构,从而实现特定的业务逻辑。这种设计的优点是:简单。数据结构和函数可以分开进行设计,适用于小型项目、快速构建原型。但这种方式最大的问题是:当数据结构需.

2021-06-02 09:29:33 401

原创 为什么不推荐C++?

C++是一门强大的语言,已经得到了广泛的应用。C++开创了面向对象编程(Object Oriented Programming, 简称OOP)的时代。想当初,抱着一本厚厚的C++书,啃了好几个月,总算把C++的各种概念都看了一遍。时至今日,C++版本号已经从11,14,17升级到20,后面C++ 23 预计很快推出。每个版本,都带来众多的强大特性。因此,强大、复杂,是C++最鲜明的特点。但是,国外很多大牛,都对C++持否定态度,最著名的就是Linus Torvalds了,他说:C++ is a

2021-05-28 07:42:59 1085 2

原创 要想成为牛人,推荐学习哪种编程语言?

程序员绝大多数时间,都在与编程语言进行亲密接触,写代码,读代码,改代码,调试代码,分析代码,测试代码......因此,精通编程语言,是成为技术牛人的必备功力。自从发明计算机后,各种编程语言层出不穷,多达数百种。编程语言之间的竞争,极其激烈,新的编程语言还在不断推出,已有的编程语言还在不断扩展和增强。TIOBE公司,每月会发布一版编程语言的流行度排行榜,见 https://www.tiobe.com/tiobe-index/哪种编程语言是世界上最好的语言,程序员之间的争论从未平息。因此,本文的观

2021-05-27 08:10:32 240

原创 掌握学习方法,成为技术大牛

上篇《为什么很多程序员,成为不了技术大牛?》中,介绍了一个核心认知:工作任务,是公司给了你一个练手的机会。并介绍了知识点扩展学习的方法。本文继续分享成为技术大牛的学习成长方法。方法2:动手实践编程,是一门实践课。各种知识点、设计模式、思想,都是要经过大量代码编写实践的过程才能熟练掌握、并且需要长时间积累和深入思考才能融会贯通的。因此,新学到的知识点,一定要马上动手写程序进行实际验证,并且在工作任务完成过程中一定要强迫自己使用新学到的技术进行代码实现,在实现的过程中反复体会这个新技术带来了哪些好处?

2021-05-26 07:54:00 143

原创 为什么很多程序员,成为不了技术大牛?

这个话题,有点敏感,可能会让很多程序员心里非常不舒服。但现实世界中,技术平庸的程序员比比皆是,水平精湛的程序员少之又少。尽管每年有大量的学生毕业后成为程序员,但从企业招聘的角度却一直难于招到高水平的程序员。也就是说,低水平的程序员过剩,高水平的程序员严重不足,这种两级分化的情况非常严重。我本人,从事产品研发和技术管理已经超过20年,我也面试过非常多的程序员。很多5年甚至10年以上的程序员,做过很多项目,但却连内存分配、字符串查找替换这些基本的题目都答不上来。就像一个人,习武多年,就只学会了花拳绣

2021-05-25 06:48:54 1013 2

空空如也

空空如也

TA创建的收藏夹 TA关注的收藏夹

TA关注的人

提示
确定要删除当前文章?
取消 删除