• 编程思考 : Java中如何用简单的数字描述更多的信息


    一 . 前言

    这一篇来趣味性的探讨一下 , 如何通过更少的空间描述更多的信息

    在数据库里面 ,通常我们会用数字的递进来描述状态等信息 , 但是如果想进行更复杂的操作 , 就有必要对二进制有一定理解了.

    二 . 单数中描述信息

    单数中保存多个信息的意思是 : 我们能把多少信息存储到一串数字里面. 这里直接来通过一些案例来说明用法

    用单个数字来表示状态

    这也是业务中最常见的一种使用方式 , 通过数字 1,2,3 等来描述一个状态 , 这种方式有一定的可读性 , 也有足够的扩展性

    如果从二进制的角度说 , 这是一种进位体现状态的方式.

    用单个数字来描述多个状态 : 包含多种状态

    单数描述多个信息这一块首先能想到的就是 Linux 的权限表示法 , 在Linux 中有四种权限 , 分别如下 :

    这种表示法的方式很简单, 从表象上来说就是两数相加 , 把初始状态设为 1 / 2 / 4 , 更复杂的状态则是初始状态的组合.

    这种模式从二进制的角度看的话能更明显 , 每一位都标识一种状态.在更复杂的场景中, 还可以通过质数的数学特性 , 来做更多的扩展

    用单个数字来描述不同维度的信息 : 包含状态和数量

    这种体现最有代表的就是线程池的表现方式. 在线程池对象ThreadPoolExecutor 中 , 有个属性可秀了 , 它叫 ctl.


     

    1. // ctl 是一个整形原子类 , 它即表示了状态 , 有记录了梳理
    2. private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    3. : 此变量 记录了 “线程池中的任务数量”和“线程池的状态”两个信息
    4. : 高3位表示"线程池状态",低29位表示"线程池中的任务数量"
    5. - RUNNING : 111 : 该线程池能接收新任务 ,且能对新任务进行处理
    6. - SHUTDOWN : 000 : 不能接收新任务 ,但是可以对任务进行处理
    7. - STOP : 001 : 不添加新任务 , 不对任务进行处理 , 会中断正在执行的任务
    8. - TIDYING : 010 : 当所有的任务已终止,ctl记录的"任务数量"0,线程池会变为TIDYING状态
    9. - 当所有的任务已终止,ctl记录的"任务数量"0,线程池会变为TIDYING状态
    10. - TERMINATED : 011 : 线程池彻底终止的状态
    11. // 对数量的统计很简单 , 因为是 低 29 位用来描述梳理 , 所以做正常的加减即可实现
    12. private boolean compareAndIncrementWorkerCount(int expect) {
    13. return ctl.compareAndSet(expect, expect + 1);
    14. }
    15. private boolean compareAndDecrementWorkerCount(int expect) {
    16. return ctl.compareAndSet(expect, expect - 1);
    17. }
    18. // 对状态的修改则是通过位运算来做的
    19. > S1 : 预先准备多种状态
    20. private static final int RUNNING = -1 << COUNT_BITS;
    21. private static final int SHUTDOWN = 0 << COUNT_BITS;
    22. private static final int STOP = 1 << COUNT_BITS;
    23. private static final int TIDYING = 2 << COUNT_BITS;
    24. private static final int TERMINATED = 3 << COUNT_BITS;
    25. > S2 : 判断是否是运行状态或停止状态
    26. private static final int COUNT_BITS = Integer.SIZE - 3;
    27. private static final int CAPACITY = (1 << COUNT_BITS) - 1;
    28. // ~CAPACITY将反转CAPACITY的值,也就是CAPACITY的高3位全部为1,低29位全部为0
    29. // & 操作则可以得到高三位的值
    30. private static int runStateOf(int c) { return c & ~CAPACITY; }
    31. final boolean isRunningOrShutdown(boolean shutdownOK) {
    32. int rs = runStateOf(ctl.get());
    33. return rs == RUNNING || (rs == SHUTDOWN && shutdownOK);
    34. }

     

    这样一个数字则将数据进行了深度扩展 , 了解一定的二进制思想就足够了解这些逻辑的变化

    三. 宏观思路

    雪花算法对数字的整合

    下面说的几种方式会跳出二进制 , 从业务的角度去看数字的玩法 , 先来看一段很常见的雪花算法

    1. 3 << 60 | timestamp - 1504000000000L << 20 | workerId << 10 | random.nextInt(128);
    2. 首位 : 可以自定义首位
    3. 时间戳 : 可以根据自己的业务情况定义毫秒级 (这里我随便给了个时间)
    4. 工作机器id:也被叫做workId,这个可以灵活配置,机房或者机器号组合都可以
    5. 随机序列号 : 自增值支持同一毫秒内同一个节点可以生成随机多少个ID

     

    类似的方式还可以有很多 , 一个数有很长的位数 , 我们可以通过这些位数来实现不同的业务逻辑 , 以表达不同的涵义.

    偏移量对进度的处理

    说到偏移量 , 最简单的方向就是 for 循环时对 size 进行判断 ,当达到 size 后直接结束.

    而稍微复杂点的就是 IO 流种对长度进行读取, 分批去读取数据.

    更深度一点的就是即进行长度的扩展 , 又包含其他的信息 , 这一篇就来看一下 ObjectStream 中如何使用偏移量的.

    ObjectStream 是一个对象流 , 通过 JDK 序列化对对象进行转换和解析.

    在下面这个场景中 , 有一个对象叫 passHandle , ObjectStream 会通过该值作为下标获取存在对象列表中的对象 , 同时通过该对象判断整体的进度已经状态


     

    1. // S1 : 初始化
    2. private static final int NULL_HANDLE = -1;
    3. private int passHandle = NULL_HANDLE;
    4. // S2 : 写入 passHandler (PS : 此处是在 ObjectOutputStream 中)
    5. bout.writeInt(baseWireHandle + passHandle);
    6. // S3 : 解析 passHandler
    7. passHandle = bin.readInt() - baseWireHandle;
    8. if (passHandle < 0 || passHandle >= handles.size()) {
    9. throw new StreamCorruptedException...
    10. }
    11. // S4 : 通过 Handler 取位数
    12. Object lookupObject(int handle) {
    13. return (handle != NULL_HANDLE &&
    14. status[handle] != STATUS_EXCEPTION) ?
    15. entries[handle] : null;
    16. }

     

    除了作为偏移量来确定进度 , 还可以通过 passHandler 来标识状态

    通常用偏移量来标识状态是需要和其他对象相配合的 , 例如集合 :

    在下面这个案例里面 , passHandler 是作为 status 的下标存在的 , 而每个下标中都会存储一个状态, 则就对整个序列化中的状态进行了统计 :

    1. private static final byte STATUS_OK = 1;
    2. private static final byte STATUS_UNKNOWN = 2;
    3. private static final byte STATUS_EXCEPTION = 3;

     

    在这个案例里面 , 游标本身只代表了定位 , 但是它可以配合多个其他的对象 , 来扩展其含义. 同时游标可以通过设置负数形式 , 来扩展其状态含义. 当其等于 -1 时 , 则标识不存在该对象 , 是另外一种层面的扩展

    总结

    对数据的信息处理其实还有很多种方式 , 总结出来主要包括以下几种 :

    • 简单的通过其递增的变化 , 来对应不同的状态
    • 通过数字的叠加 , 来记录多种状态的叠加
    • 通过二进制内不同的位数 , 来记录多种层面的数据
    • 通过 long 等长位数 , 来为不同的位数匹配不同的含义
    • 通过作为一个游标 ,配合其他的对象做更深度的扩展


     

  • 相关阅读:
    jumpserver堡垒机/跳板机
    [MyBatisPlus]DML编程控制①(多记录操作、逻辑删除)
    用Python实现感知机学习算法及其对偶算法实验报告
    【Hack The Box】Linux练习-- OpenAdmin
    covfefe 靶机/缓冲区溢出
    c++网络编程
    [动态规划] (十一) 简单多状态 LeetCode 面试题17.16.按摩师 和 198.打家劫舍
    BP神经网络算法基本原理,bp神经网络算法的原理
    NFV中:DPDK与SR-IOV应用场景及性能对比
    #QT(事件--快捷键保存文件)
  • 原文地址:https://blog.csdn.net/m0_73088370/article/details/126694732