0%

idea中的正则表达式

部分替换

  • 贪婪模式
    • hello((.),.)
    • $1 表示上面被括号包起来的部分,类似sed, $0 表示全部, $1 表示第一个
  • 非贪婪模式
    • 使用?表示这次匹配是非贪婪的, 采用最小匹配原则

高效的ID管理,随机,删除方式

  • 在测试时,需要随机生成很多key进行管理,还需要随机取出,删除
    • 最早使用rb_tree_map管理,但是性能不行,反倒是ID管理成为了瓶颈,无法测出程序真正的性能瓶颈
  • 使用裸数组和hash_map,可以实现O(1)的随机,添加和删除也只是hash_map的复杂的,性能提高了很多
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#pragma once
#include <cstdlib>
#include <map>
#include <unordered_map>
#include <vector>

#define RAND(begin, end) ((rand() % ((end) - (begin))) + (begin))

template<class KeyType>
class KeyHolder
{
private:
KeyType *keys{};
int point{0};
const int MAX_SIZE;
std::unordered_map<KeyType, int> keyMap{};

public:
KeyHolder(KeyHolder &) = delete;
KeyHolder(KeyHolder &&) = delete;

KeyHolder(int maxSize) : MAX_SIZE(maxSize)
{
keys = new KeyType[maxSize];
}
~KeyHolder()
{
if (keys != nullptr)
{
delete[] keys;
keys = nullptr;
}
}
int Add(const KeyType &key)
{
if (point >= MAX_SIZE)
{
return -2;
}
auto ret = keyMap.insert(std::make_pair(key, point));
if (!ret.second)
{
return -1;
}
keys[point] = key;
++point;
return 0;
}
int Rand(KeyType *outKey)
{
if (point == 0)
{
return 1;
}
int randNum = RAND(0, point);
(*outKey) = keys[randNum];
return 0;
}
int Remove(const KeyType &key)
{
auto it = keyMap.find(key);
if (it == keyMap.end())
{
return -1;
}
int index = it->second;

// 和最后一个交换位置
keys[index] = keys[point - 1];
keyMap[keys[index]] = index;
--point;
return 0;
}
};

改进的SnowFlake,多机唯一ID生成器

  • 所谓的snowFlake ID生成器, 其实就是把int64分解分解为几个段,通过时间,机器ID,自增序列来生成唯一ID
  • 好用又没有什么实现难度
  • 可以灵活的按照自己的需要调整位的分配

这里用c++实现了下, 同时完善了下,添加了如下特性

  • 设计每秒4095k个ID\
  • 对于过高的请求频率(超出了每ms请求上限,就陷入自旋等待)
  • 对于时间发生倒流,报错并陷入自旋等待
  • 如果出错时想要报错返回而不是自旋等待, 吧continue改为return就行

测试方案

  • 每次生成1亿个ID,使用set保存,不应该有重复
  • 连续跑200次,不应该有错误

linux版实现(win自行修改getMs函数)

  • head only, 直接使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
/************************************************
* @desc 改进的snowflake算法, 设计每ms生成4095个id, 过高将自旋等待
* 依赖机器时间(ms), 如果时间被回调, 那么将陷入自旋等待, 并不断的告警
* 通过的测试用例: 单次生成1亿个id并插入set, 不应有错误, 连续跑100次, 不应有错误
* 测试用例在我的练手项目中, 后续想办法搬过来
*
* !!!多线程不安全, 如需多线程使用, 自行加锁!!!
************************************************/

#pragma once

#include <sys/time.h>

#include <chrono>
#include <ctime>
#include <mutex>


class SnowFlake
{
private:
int64_t m_epoch{};
int64_t m_lastUpdateTime{};
int64_t m_curr{};
int m_id{};
int m_sequence{};

uint64_t GetNowMs()
{
struct timeval tv
{
};

gettimeofday(&tv, NULL);

return tv.tv_sec * 1000 + tv.tv_usec / 1000;
}

public:
std::string DumpInfo()
{
std::stringstream oss{};
oss << "m_epoch:\t" << m_epoch << "\n";
oss << "m_lastUpdateTime:\t" << m_lastUpdateTime << "\n";
oss << "m_curr:\t" << m_curr << "\n";
oss << "m_id:\t" << m_id << "\n";
oss << "m_sequence:\t" << m_sequence << "\n";
return oss.str();
}
/**
* 系统起始时间 ms
* @param epoch
*/
void SetEpoch(uint64_t epoch)
{
this->m_epoch = epoch;
}
/**
* 序列的ID, 可以用来区分多个机器 或者 单个机器内区分不同的类型以提高效率
* @param id
*/
void SetID(int id)
{
this->m_id = id;
}

virtual int64_t Generate()
{
int64_t value{};
int64_t timeGap{};
// 防止时间回调 每ms不够时自旋等待的 snowflake
while (true)
{
m_curr = GetNowMs();
timeGap = m_curr - m_lastUpdateTime;
// 防止时间被回调
// 没人回调生产环境的时间吧? 如果真有人手贱改了生产环境的时间, 就只能自旋等了
if (timeGap < 0)
{
//ERROR("time go back!!! is any one change it ??? " << timeGap)
continue;
}
else if (timeGap == 0)
{
if (m_sequence >= 0xFFF)
{
continue;
}
else
{
// 唯一出口, 会造成seq 0 不会出现 可接受
++m_sequence;
break;
}
}
else
{
m_sequence = 0;
m_lastUpdateTime = m_curr;
continue;
}
}

// timeGap 始终为0 但是还是加一下好
value |= (m_lastUpdateTime - m_epoch + timeGap) << 22; // 41 位表示时间(自epoch 起 ms), 可用69.7年
value |= m_id & 0x3FF << 12; // 10 位(1024个) 表示机器ID或者类型ID, 用来表示机器ID时, 可以做到多机唯一
value |= m_sequence & 0xFFF; // 12 位(4095个) 表示序列, 没毫秒4095个可用ID
value &= 0x7FFFFFFFFFFFFFFF; // 最高位不应该有数字强制抹去最高位
return value;
}
};

class ThreadSafeSnowFlake : public SnowFlake
{
private:
std::mutex m_lock{};

public:
/**
* 多线程安全的获取唯一ID
* @return
*/
int64_t Generate() override
{
std::lock_guard<std::mutex> lockGard(m_lock);
return SnowFlake::Generate();
}
};

测试逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
int main(int argc, char **argv) {
int iRet = 0;
// iRet = beforeRun();
// if (iRet) {
// std::cerr << "init fail with " << iRet << std::endl;
// }
// testing::InitGoogleTest(&argc, argv);
// iRet = RUN_ALL_TESTS();
SnowFlakeChange snowFlakeChange{};
snowFlakeChange.SetID(10);
// unix time : +8 2020-04-18 14:50:44
// snowFlakeChange.SetEpoch(1587192644152);
snowFlakeChange.SetEpoch(getNowMs());

// gen
TimeGap gap{};
std::set<long> uidSet{};
{
// LOG_DEBUG("start correct test")
for (int i = 0; i < 100000000; ++i) {
auto uid = snowFlakeChange.Generate();
if (!uidSet.insert(uid).second) {
LOG_ERROR("uid fail ")
LOG_ERROR("\n" << snowFlakeChange.DumpInfo())
iRet = -1;
}
}
}
LOG_DEBUG("time use(us) : " << gap.gap())
return iRet;
}

我的设计优先级

  • 平行扩容 > 易于扩展 > 模块化与结构化 > 多线程扩容 > 单线程性能
  • newer is better
  • 不要重复造轮子

scope guard cpp 版的defer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
template <class Function>
class ScopeGuard {
private:
Function m_func;
bool m_dismiss{false};

public:
explicit ScopeGuard(Function func) : m_func(func), m_dismiss(false) {}
~ScopeGuard() {
if (!m_dismiss) m_func();
}
ScopeGuard() = delete;
ScopeGuard(const ScopeGuard&) = delete;
ScopeGuard& operator=(const ScopeGuard&) = delete;

void dismiss() { m_dismiss = true; }

ScopeGuard(ScopeGuard&& rhs) : m_func(std::move(rhs.m_func)), m_dismiss(rhs.m_dismiss) { rhs.dismiss(); }
};
template <class Fun>
ScopeGuard<Fun> MakeScopeGuard(Fun f) {
return ScopeGuard<Fun>(std::move(f));
}
  • 类似go的defer,java的finally,离开作用域时执行注册的代码
  • 再也不用忘记资源回收了^v^

cpp模仿CSharp事件机制(2) 任意数量参数支持

  • 上一篇中实现的只能在注册事件时绑定自己的变量,无法在fireevents时传递参数,这次实现下

实现

  • 原理很简单,就是打一个参数包,fire event和注册函数时,时同样使用这个参数包
  • 但是不能用void来声明变量,所以void版的需要特化下
  • 这同样造成了不能使用一个完全抽象的接口来定义,所以就完全拆开了

使用方式

  • 和之前的基本一致,声明一个方法,然后注册进去就好
  • 不过这次得额外声明下需要的参数

void特化

  • 基类中不声明fireEvent方法
  • 特化的时候再声明

源码

cpp模仿C#事件机制(1) 无参数版实现

起因

  • 之前写unity时,感觉c#的事件机制超好用,就想在万能的c++中实现一下
  • 看了网上的一些实现,大部分都是只能注册一个回调函数的,这有啥用???
  • 要么就是使用时要继承一大堆东西,一点也不方便

设计

  • 本身很简单,就是一个观察者模式,麻烦的是对回调函数的管理
    • 需要一个容器存储回调函数,set,vector都行
    • 要装进容器中,那么你的回调函数就要支持一些符号
  • 这里实现了两种事件发布器
    • 基于std::unordered_set,重复插入会返回false,调用顺序和插入顺序无关
    • 基于std::vector,允许重复插入,调用顺序和插入顺序相同
  • 两种事件发布器都提供注册常驻回调函数和一次性回调函数接口(扩展了下,还是有这方面的需求的)
  • 多线程只是简单的分类锁了下,有需要的话可以扩展各种无锁生产者消费者模型或者由订阅器维护回调函数,发布器维护订阅器引用
  • 回调函数过期
    • 回调函数已经被释放会导致空指针问题,程序会core,c#中也有这个问题
    • 这里尽可能解决下
      • 使用std::shared_ptr包裹回调函数,订阅器自己必须维护ptr
      • 发布器使用std::weak_ptr来指向回调函数
      • 已经过期的直接从队列中删除,报告下
    • 这样就解决了大部分过期问题
    • 现存的问题
      • 当回调函数线程已经通过检测了,准备调用,这时订阅器被销毁了,还会有问题
      • 如果再引入锁的话开销有些大,先这样吧
  • 最终设计
    • 订阅器使用std::weak_ptr<std::function<void()>>作为回调函数
    • 发布器使用std::shared_ptr<std::function<void()>>来保存回调函数引用
    • 有参数的,类中的方法,仿函数之类的使用std::bind包装为std::function<void()>

用法

  • 不需要任何继承

  • 任何我能想到的c++调用方式都是支持的

实现

  • 本身挺简单的,就是一个观察者模式
  • 额外实现了
    • 多线程安全:使用锁
    • 一次性任务:执行完成后删除自身
    • 空指针任务抛弃:weak_ptr lock失败的直接删掉
  • 只需要给std::weak_ptr实现hash和equal函数就行
    • using DelegrateWeakSave = std::weak_ptr<std::function<void()>>;
      using DelegrateDefine = std::shared_ptr<std::function<void()>>;
      
      struct HandleBindHash {
          std::size_t operator()(DelegrateWeakSave const& ptr) const {
              if (auto spt = ptr.lock()) {  // 使用之前必须复制到 shared_ptr
                  return reinterpret_cast<size_t>(&(*spt));
              } else {
                  return 0;
              }
          }
          std::size_t operator()(DelegrateDefine const& ptr) const { return reinterpret_cast<size_t>(&(*ptr)); }
      };
      struct HandleBindEqual {
          bool operator()(const DelegrateWeakSave& lhs, const DelegrateWeakSave& rhs) const {
              return HandleBindHash()(lhs) == HandleBindHash()(rhs);
          }
      };
      
    • 这里简单使用地址作为hash地址,由于是用shared_ptr来包裹std::function的,所以没问题
  • 剩下的就是一个观察者模式了,没啥说的

完整代码

GitPages和Hexo搭建blog采坑记录

干啥不采坑是不可能的,只能勉强爬爬坑维持生活的亚子

本地访问没问题,github无法访问

  • 看了下生成的html,明显是相对路径的问题,找了下看注释是通过以下这两条替换的
  • 改了下果然没问题了^v^

链接next主题生成的tags,home之类的链接全部失效

  • 看404是链接尾部多出了%20,转换为文本就是空格
  • 看了下next配置
  • 感觉是分隔符前面的空格没有被trim
  • 改了下果然如此,这都不trim下?
  • 后续
    • 看了下是hexo的地址搬家了,我拉到的版本太老了,这个bug没有修复,去github上找到最新的地址替换下hexo就好

图片无法在主页显示

  • 开启post_asset_folder: true后,图片放在文章同名目录下,md和生成的html都可以访问到,但是主页看不到,看了下是相对路径的问题
  • 网上的各种插件都不靠谱
    • hexo-asset-image会导致在第一步中指定的root也出现在链接中,有bug
    • 这样造成找不到图片
    • 干掉root有导致github上css,js变成绝对路径
    • 干起来了???
  • 还是按照官方的说法使用assert吧
    • 图片地址可以不加相对路径,解析的到的,但我还是喜欢加上,IDE有代码提示和预览
    • assert宏不需要相对地址
    •   - {% asset_img link_err.png%}
      
  • 关于hexo d后的生效时间
    • 并没有很多人说的那么长,基本清理完缓存回来刷新两三次就能看到
    • 个人体验没有超过一分钟的
  • 被搜索引擎发现
    • 参考的这篇文章
    • 百度放弃了,太麻烦,程序猿还有用百度的吗(手动狗头)

  • 更新
    • 关于第一条,是由于我把项目名傻傻的设置为blog了,换为{github名字}.github.io,就可以直接在根目录下访问到了
    • 调整后: