commit e1f1e71f9ad0d23cf55e4c5bbc63d71eff0ca2e5
Author: taynpg <taynpg@163.com>
Date:   Thu Apr 11 09:27:21 2024 +0800

    first commit

diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..4ed25d6
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,17 @@
+# .clang-format
+
+# 风格格式化
+BasedOnStyle: Google 
+# 4 空格缩进
+IndentWidth: 4 
+# 连续对齐变量的声明
+AlignConsecutiveDeclarations: true
+# 指针左侧对齐
+PointerAlignment: Left 
+# 访问说明符(public、private等)的偏移
+AccessModifierOffset: -4
+# 大括号
+BreakBeforeBraces: Custom
+BraceWrapping:
+	# 函数定义后面大括号在新行
+	AfterFunction: true
diff --git a/.clangd b/.clangd
new file mode 100644
index 0000000..a0851df
--- /dev/null
+++ b/.clangd
@@ -0,0 +1,7 @@
+Hover:
+    ShowAKA: Yes
+Diagnostics:
+    UnusedIncludes: None
+    Suppress: [anon_type_definition]
+    ClangTidy:
+        Remove: misc-unused-alias-decls
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..424542a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,37 @@
+# Prerequisites
+*.d
+
+# Compiled Object files
+*.slo
+*.lo
+*.o
+*.obj
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Compiled Dynamic libraries
+*.so
+*.dylib
+*.dll
+
+# Fortran module files
+*.mod
+*.smod
+
+# Compiled Static libraries
+*.lai
+*.la
+*.a
+*.lib
+
+# Executables
+*.exe
+*.out
+*.app
+cmake-*
+build
+.vs
+.idea
+out
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..e0cfb5f
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,35 @@
+{
+    "files.autoSave": "onFocusChange",
+    "editor.fontSize": 17,
+    "editor.fontFamily": "'Operator Mono Lig Light', 'Operator Mono Lig Light', 'Operator Mono Lig Light'",
+    "cmake.configureOnOpen": true,
+    "cmake.debugConfig": {
+        "console": "integratedTerminal"
+    },
+    "cmake.options.statusBarVisibility": "visible",
+    "cmake.generator": "Ninja",
+    "C_Cpp.intelliSenseEngine": "disabled",
+    "clangd.arguments": [
+        "--header-insertion=never",
+        "--all-scopes-completion",
+        "--completion-style=detailed",
+        "--clang-tidy",
+        "-j=4",
+        "--pch-storage=memory",
+        "--compile-commands-dir=build",
+        "--background-index",
+        "--ranking-model=heuristics",
+        "--query-driver=/usr/bin/g++"
+    ],
+    "editor.inlayHints.enabled": "off",
+    "editor.unicodeHighlight.allowedLocales": {
+        "ja": true,
+        "zh-hans": true
+    },
+    "files.associations": {
+        "iostream": "cpp"
+    },
+    "workbench.colorCustomizations": {
+        //"editor.background": "#C0C0C0"
+    }
+}
\ No newline at end of file
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..b6de43e
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,12 @@
+cmake_minimum_required(VERSION 3.8)
+project(skip_use)
+
+set(CMAKE_CXX_STANDARD 11)
+
+if (MSVC)
+    add_compile_options(/source-charset:utf-8)
+    add_compile_options(/EHsc)
+endif()
+
+add_executable(skip_use main.cpp MSkipList.hpp)
+add_executable(skip_test stress.cpp MSkipList.hpp)
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..6cfc697
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 taynpg
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/MSkipList.hpp b/MSkipList.hpp
new file mode 100644
index 0000000..09abcc5
--- /dev/null
+++ b/MSkipList.hpp
@@ -0,0 +1,243 @@
+#ifndef MSKIPLIST_HEADER
+#define MSKIPLIST_HEADER
+
+#include <atomic>
+#include <cassert>
+#include <random>
+#include <cstring>
+
+template <typename T, typename P>
+class SkipList {
+public:
+    // 插入数据
+    void insert(const T& key, const P& value);
+    // 删除数据
+    void remove(const T& key);
+    // 查询数据
+    bool search(const T& key, P& value);
+    // 是否包含某个数据
+    bool contains(const T& key);
+    // 当前表节点个数
+    std::size_t count() const;
+    // 清除表
+    void clear();
+
+public:
+    SkipList(int max_level = 12);
+    ~SkipList();
+
+private:
+    struct SkipNode;
+    std::size_t          count_{};
+    SkipNode**           pre_{};
+    SkipNode*            header_{};
+    const int            max_level_{};       // 限定的最大高度
+    std::atomic_int      cur_max_height_{};  // 当前使用的最大高度
+    std::random_device   rd_{};
+    std::mt19937         gen_{};
+    std::uniform_int_distribution<int> dis_{};
+
+private:
+    int       random_level();
+    SkipNode* find_node(const T& key);
+};
+
+template <typename T, typename P>
+SkipList<T, P>::~SkipList()
+{
+    clear();
+    delete[] pre_;
+    delete header_;
+}
+
+template <typename T, typename P>
+void SkipList<T, P>::clear()
+{
+    SkipNode* start = header_->get_no_bar(0);
+    while (start) {
+        SkipNode* n = start;
+        start = start->get_no_bar(0);
+        delete n;
+        --count_;
+    }
+}
+
+template <typename T, typename P>
+inline int SkipList<T, P>::random_level()
+{
+    static const int base_ = 2;
+    int              height = 1;
+    while (height < max_level_ && (dis_(gen_) % base_) == 0) {
+        ++height;
+    }
+    return height;
+}
+
+template <typename T, typename P>
+struct SkipList<T, P>::SkipNode {
+    T   key_{};
+    P   value_{};
+    int level_{};
+
+public:
+    explicit SkipNode(int level = 1) { alloc(level); }
+    ~SkipNode() { release(); }
+    SkipNode(const T& key, const P& value, int level)
+    {
+        key_ = key;
+        value_ = value;
+        alloc(level);
+    }
+    void alloc(int max_level)
+    {
+        if (max_level < 1) {
+            return;
+        }
+        release();
+        next_ = new std::atomic<SkipNode*>[max_level] {};
+    }
+    void      release() { delete[] next_; }
+    SkipNode* get(int n)
+    {
+        assert(n >= 0);
+        return next_[n].load(std::memory_order_acquire);
+    }
+    SkipNode* get_no_bar(int n)
+    {
+        assert(n >= 0);
+        return next_[n].load(std::memory_order_relaxed);
+    }
+    void set(int n, SkipNode* node)
+    {
+        assert(n >= 0);
+        next_[n].store(node, std::memory_order_release);
+    }
+    void set_no_bar(int n, SkipNode* node)
+    {
+        assert(n >= 0);
+        next_[n].store(node, std::memory_order_relaxed);
+    }
+
+private:
+    std::atomic<SkipNode*>* next_{};
+};
+
+template <typename T, typename P>
+inline SkipList<T, P>::SkipList(int max_level)
+    : max_level_(max_level),
+      cur_max_height_(1),
+      gen_(rd_()),
+      dis_(0, std::numeric_limits<int>::max())
+{
+    assert(max_level_ > 0);
+    header_ = new SkipNode(max_level_);
+    pre_ = new SkipNode*[max_level_];
+}
+
+template <typename T, typename P>
+typename SkipList<T, P>::SkipNode* SkipList<T, P>::find_node(const T& key)
+{
+    memset(pre_, 0x0, sizeof(SkipNode*) * max_level_);
+    SkipNode* x = header_;
+    int       level = cur_max_height_.load() - 1;
+    while (true) {
+        SkipNode* next = x->get(level);
+        if (next && next->key_ < key) {
+            x = next;
+        } else {
+            pre_[level] = x;
+            if (level == 0) {
+                return next;
+            } else {
+                --level;
+            }
+        }
+    }
+}
+
+template <typename T, typename P>
+inline bool SkipList<T, P>::contains(const T& key)
+{
+    SkipNode* x = find_node(key);
+    if (x && x->key_ == key) {
+        return true;
+    }
+    return false;
+}
+
+template <typename T, typename P>
+inline std::size_t SkipList<T, P>::count() const
+{
+    return count_;
+}
+
+template <typename T, typename P>
+inline void SkipList<T, P>::insert(const T& key, const P& value)
+{
+    SkipNode* x = find_node(key);
+
+    if (x && x->key_ == key) {
+        x->value_ = value;
+        return;
+    }
+
+    int height = random_level();
+    if (height > cur_max_height_) {
+        for (int i = cur_max_height_; i < height; ++i) {
+            pre_[i] = header_;
+        }
+        cur_max_height_.store(height, std::memory_order_relaxed);
+    }
+    x = new SkipNode(key, value, height);
+    for (int i = 0; i < height; ++i) {
+        x->set_no_bar(i, pre_[i]->get_no_bar(i));
+        pre_[i]->set(i, x);
+    }
+    ++count_;
+}
+
+template <typename T, typename P>
+inline void SkipList<T, P>::remove(const T& key)
+{
+    memset(pre_, 0x0, sizeof(SkipNode*) * max_level_);
+    SkipNode* x = header_;
+    SkipNode* purpose = nullptr;
+    int       level = cur_max_height_.load() - 1;
+    while (true) {
+        if (level < 0) {
+            break;
+        }
+        SkipNode* next = x->get(level);
+        if (!next) {
+            --level;
+            continue;
+        }
+
+        if (next->key_ < key) {
+            x = next;
+            continue;
+        }
+
+        if (next->key_ == key) {
+            SkipNode* nx = next->get_no_bar(level);
+            x->set_no_bar(level, nx);
+            purpose = next;
+        }
+        --level;
+    }
+    delete purpose;
+    --count_;
+}
+
+template <typename T, typename P>
+inline bool SkipList<T, P>::search(const T& key, P& value)
+{
+    SkipNode* x = find_node(key);
+    if (x && x->key_ == key) {
+        value = x->value_;
+        return true;
+    }
+    return false;
+}
+
+#endif
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..b9fb27c
--- /dev/null
+++ b/README.md
@@ -0,0 +1,17 @@
+# MSkipList
+
+一个根据LevelDb源码修改的直接可用的跳表,纯C++11标准库,跨平台。
+
+# 压力测试
+
+参考[Skiplist-CPP](https://github.com/youngyangyang04/Skiplist-CPP)的压力测试代码:
+
+测得在 Visual Studio 2022 x64 release环境下(Windows 10 Pro):
+
+- 随机写(10次均值):0.05002037,即约20w条/s。
+- 随机读(10次均值):0.0369919,即约27w条/s。
+
+# 说明
+
+- 非线程安全,使用自行加锁。
+- 仅使用`MSkipList.hpp`即可。
diff --git a/main.cpp b/main.cpp
new file mode 100644
index 0000000..cd2c73e
--- /dev/null
+++ b/main.cpp
@@ -0,0 +1,36 @@
+#include <iostream>
+#include "MSkipList.hpp"
+
+int main() {
+
+    // 基本使用
+    SkipList<int, std::string> skip;
+    std::string tmp;
+
+    skip.insert(101, "医用外科口罩");
+    skip.insert(98, "C++标准库");
+    skip.insert(12, "Hello World.");
+
+    std::cout << "skip 元素个数:" << skip.count() << "\n";
+    if (skip.search(12, tmp)) {
+        std::cout << "12: " << tmp << "\n";
+    }
+
+    if (skip.search(98, tmp)) {
+        std::cout << "98: " << tmp << "\n";
+    }
+    skip.remove(98);
+    std::cout << "skip 元素个数:" << skip.count() << "\n";
+    if (!skip.search(98, tmp)) {
+        std::cout << "98: 未找到\n";
+    }
+
+    // 析构测试
+    auto* pSkip = new SkipList<int, std::string>(12);
+    pSkip->insert(101, "医用外科口罩");
+    pSkip->insert(98, "C++标准库");
+    pSkip->insert(12, "Hello World.");
+    delete pSkip;
+
+    return 0;
+}
diff --git a/stress.cpp b/stress.cpp
new file mode 100644
index 0000000..f2c4229
--- /dev/null
+++ b/stress.cpp
@@ -0,0 +1,75 @@
+#include <iostream>
+#include <thread>
+#include <chrono>
+#include <string>
+#include <mutex>
+#include <random>
+#include <climits>
+
+#include "MSkipList.hpp"
+
+#define THREAD_COUNT 1
+#define TEST_COUNT 100000
+
+std::random_device g_rd{};
+std::mt19937       g_gen(g_rd());
+std::uniform_int_distribution<int> g_dis(0, std::numeric_limits<int>::max());
+
+std::mutex g_mutex{};
+SkipList<int, std::string> skip(18);
+
+void insertFunc()
+{
+    for (int i  = 0; i < TEST_COUNT; ++i) {
+        g_mutex.lock();
+        skip.insert(g_dis(g_gen), "Hello World");
+        g_mutex.unlock();
+    }
+}
+
+void searchFunc()
+{
+    std::string tmp{};
+    for (int i  = 0; i < TEST_COUNT; ++i) {
+        g_mutex.lock();
+        skip.search(g_dis(g_gen), tmp);
+        g_mutex.unlock();
+    }
+}
+
+void insertTime()
+{
+    std::thread thread_insert[THREAD_COUNT];
+    auto start = std::chrono::high_resolution_clock::now();
+    for (int i = 0; i < THREAD_COUNT; ++i) {
+        thread_insert[i] = std::thread(insertFunc);
+    }
+    for (int i = 0; i < THREAD_COUNT; ++i) {
+        thread_insert[i].join();
+    }
+    auto finish = std::chrono::high_resolution_clock::now(); 
+    std::chrono::duration<double> elapsed = finish - start;
+    std::cout << "insert elapsed:" << elapsed.count() << std::endl;
+}
+
+void getTime()
+{
+    std::thread thread_search[THREAD_COUNT];
+    auto start = std::chrono::high_resolution_clock::now();
+    for (int i = 0; i < THREAD_COUNT; ++i) {
+        thread_search[i] = std::thread(searchFunc);
+    }
+        for (int i = 0; i < THREAD_COUNT; ++i) {
+        thread_search[i].join();
+    }
+    auto finish = std::chrono::high_resolution_clock::now(); 
+    std::chrono::duration<double> elapsed = finish - start;
+    std::cout << "get elapsed:" << elapsed.count() << std::endl;
+}
+
+int main()
+{
+    insertTime();
+    getTime();
+    return 0;
+}
\ No newline at end of file