#53 如何推送消息给手机

2021-11-20

我没有做过移动端开发,我突然想到,如果我们要给手机端推送消息,会有些什么办法。
想来想去只有短信是最容易实现的,而且效果可以接受。

#52 常用工具汇总

2021-11-18

编辑器

  • VSCode
  • JetBrains 全家桶

SSH

数据库 GUI

Redis GUI

虚拟化与容器

  • VirtualBox
  • Vagrant
  • Docker

编程相关

  • gitg
  • Beyond Compare 可以一直试用
  • Meld Linux 下的简化版 Beyond Compare

其他

在线服务

#51 最近工作的一次总结

2021-11-13

最近两周开发了一个功能,其实质就是做个数据统计,没啥好说的。
我是在没有产品设计的前提下开始工作,产品的设计反倒有一点依赖我所能提供的数据。
而且,和以往自己写功能逻辑不通的是,我这次只管提供数据和方案,业务逻辑的实现由别人来做。
工作过程中遇到了一些问题,这里做个总结(复盘)。

#50 我的开发机器

2021-10-24

Notebook
我的主力开发环境是大概 14 年 4 月在 DELL 官方 (dell.com) 买的一台 Inspiron 14R (5437) 笔记本。
PS: 这台笔记本原本是我老婆办公用, 用了将近五年之后, 于 2019 年 1 月在换了一台 小米 Air 13.3, 然后我就有笔记本了...到现在我也用了两年多了。

#49 Windows 11

2021-10-15

https://www.microsoft.com/zh-cn/windows/windows-11-specifications

主要的变化

  1. 新的 UI 设计
    根据网上流传的图片,还挺好看的,令人耳目一心
  2. 支持 Android App

其他:

  1. 新的应用商城
  2. 整合了微软的 Teams 作聊天功能
  3. 游戏相关的功能
  4. DirectX 12 Ultimate
  5. DirectStorage
  6. Auto HDR
  7. XBox 相关整合

硬件要求

CPU (1GHz,双核),内存 (4G),磁盘 (64G)

  1. CPU: 1GHz,双核
  2. 内存: 4G
  3. 磁盘: 64G
  4. UEFI 安全启动
  5. TPM 2.0
  6. 显卡:支持 DirectX 12, WDDM 2.0 驱动

CPU, 内存, 磁盘方面的要求,主流的配置应该都能满足。
UEFI 安全启动

检测工具

MS 官方工具只能给出一个是否的答复,太弱了(正在开发新的监测工具)。

社区有提供一个检测工具:WhyNotWin11.exe (GitHub 下载)

第一次检测:

WhyNotWin11 Result Before

  1. 引导方式:Legacy
  2. CPU 兼容性:不支持
  3. 硬盘分区形式:分区形式不是 GPT
  4. 安全启动:禁用或未检测到
  5. TPM 版本:不存在 TPM 模块或 BIOS 禁用

除了 CPU 之外,其他几项都可以在 BIOS 中设置。
TPM:如果是 Intel 就找一下 Platform Trust Technology, AMD 可能是 fTPM。

WhyNotWin11 Result After

我的 CPU 是六代 i5,这就真的没办法了,i5 的话,最低要求是八代 (完整的 CPU 要求清单:Windows 11 supported Intel processors

抛弃才发布六年(2015/09)的六代 CPU 绝壁是一个愚蠢的决定,希望微软耗子喂汁!

Tiny11(2023-02-07)

根据微信公众号高效程序员文章《Win11 极限精简版发布:2G内存就能流畅运行!》中的信息,有人基于 Windows 11 Pro 22H2 打造了 Tiny11,不需要 TPM 和安全启动。

#48 DedeCMS 开始割韭菜了

2021-09-29

织梦官网放出上面这张公告:除了 “个人非盈利” 网站,其他没有拿到正式授权的织梦网站需要交 5800 授权费。

他们可能已经把清单列好了,律师团队也准备好了,正磨刀霍霍向着小站长们。

这可能就是中国特色开源协议的正确使用方式。

PS: 虽然我觉得 织梦CMS 是个垃圾,但我也不知道为啥,据说很多小站都是用的这个系统。

#47 URI

2021-09-24

URI 与 URL

                    hierarchical part
        ┌───────────────────┴─────────────────────┐
                    authority               path
        ┌───────────────┴───────────────┐┌───┴────┐
  abc://username:password@example.com:123/path/data?key=value&key2=value2#fragid1
  └┬┘   └───────┬───────┘ └────┬────┘ └┬┘           └─────────┬─────────┘ └──┬──┘
scheme  user information     host     port                  query         fragment

  urn:example:mammal:monotreme:echidna
  └┬┘ └──────────────┬───────────────┘
scheme              path

URI

URI (统一资源标识符),标识一个资源主要有两种方法,根据名称(URN),根据路径(URL)。

URI 语法:

URL Syntax

有一个问题就是注册的 URI scheme 基本上都可以认为是用于资源定位,所以 URI 和 URL 的概念就比较模糊了。

有的 scheme,比如说 data:<mediatype>[;base64],<data>,既不算 URL,也不算 URI。
有的 scheme,比如说 sms:<phone number>?<action>,我觉得是属于名称标识,但是不符合 URN 的定义(必须 urn:)。

而且实际上,就比如说 URL http://example.com/user/create,我们用来创建一个用户,如果套用万维网创建时的学院派设想,我们只能把创建这个动作理解成一个资源了。所以,我认为深入讨论这几个 “统一资源” 的概念之间到底存在什么差异没有什么意义,大致知道他们的渊源就够了:先随着 WWW 出来了 URL 的概念,然后又有人整出来一个 URN,再后来统一为 URI 标准,这几个概念本来就是混乱的,这就是现实生活,泾渭分明的概念只存在于那些教授们的理想中。
如果感兴趣可以参考 RFC 3305,这是一个 Infomational RFC,就是试图解释这几个概念,并向社区作出使用建议。

URL

URL (统一资源定位符) = 协议://认证信息@主机名:端口号/路径?查询字符串#片段

协议必须是在 IANA 注册的合法 URI scheme。

我们最熟悉的是 HTTP URL,协议是 http 或者 https,除此之外还有其他的 IANA 批准的协议,比如:

  • mailto:<address>[?<header1>=<value1>[&<header2>=<value2>]]
  • imap://[<user>[;AUTH=<type>]@]<host>[:<port>]/<command>
  • git://github.com/user/project-name.git
  • file://[host]/path or file:[//host]/path
  • view-source:<absolute-URI>

URN

URN (统一资源名称) is 资源名称(唯一标识),格式:urn:<NID>:<NSS>

  1. urn 就是 IANA 注册的众多 URI scheme 之一。
  2. 这个命名空间标识 NID 也需要在 IANA 注册。

  3. urn:isbn:0451450523

  4. urn:ietf:rfc:2648

实际上,我好像没有见过什么地方在使用 URN,更别提设想中的统一资源属性 URC 了。

就好比上面提到的两个例子,ISBN: 0451450523,RFC 2648,直接这么写就是了,按照 RFC 定义写成标准的那一串实在是没见过。

总结

URL 是 URI 中的因特网部分,用来指示网络资源位置。

Python 示例

import urllib.parse

a = urllib.parse.urlunparse('http', 'user:pass@example.com:8080', '/path', 'param1=a', 'a=1&b=2', 'Title')
# 'http://user:pass@example.com:8080/path;param1=a?a=1&b=2#Title'

b = urllib.parse.urlparse(a)
b.scheme    # 0 'http'
b.netloc    # 1 'user:pass@example.com:8080'
b.path      # 2 '/path'
b.params    # 3 'param1=a'
b.query     # 4 'a=1&b=2'
b.fragment  # 5 'Title'
b.username  #   'user'
b.password  #   'pass'
b.hostname  #   'example.com'
b.port      #   8080

print(b[2]) # '/path'

b = urllib.parse.urlparse(('pymysql+mysql://root:111111@1.1.1.1:3306/xiaorui_master?charset=utf8mb4'))
print(repr((b.scheme, b.netloc, b.path, b.params, b.query, b.fragment, b.username, b.password, b.hostname, b.port)))
('pymysql+mysql', 'root:111111@1.1.1.1:3306', '/xiaorui_master', '', 'charset=utf8mb4', '', 'root', '111111', '1.1.1.1', 3306)

参考资料与拓展阅读

#46 GitHub 上那些徽章是怎么弄出来的?

2021-09-19

比如:Tornado 框架的 stars 数量:,再比如说这种:

外国人真是会玩,总有这样的好点子,在有限的环境(Markdown)中也能弄的丰富多彩的。

上面都是用的 shields.io 提供的服务,它内置了很多种类的图标,GitHub 关注数、Fork 数、Star 数、协议,Twitter 关注数、NPM 包大小、PyPI 下载数量等等,这些看他们官网提供的示例,照着用就是了。

还有一种,就是用户定制接口,格式:

  1. Static:URLPath https://img.shields.io/badge/<LABEL>-<MESSAGE>-<COLOR>
  2. Static:QueryString https://img.shields.io/static/v1?label=<LABEL>&message=<MESSAGE>&color=<COLOR>
  3. Endpoint https://img.shields.io/endpoint?url=<URL>&style<STYLE>
  4. Dynamic https://img.shields.io/badge/dynamic/json?url=<URL>&label=<LABEL>&query=<$.DATA.SUBDATA>&color=<COLOR>&prefix=<PREFIX>&suffix=<SUFFIX>

还有好多更加细致的规则,用来定制自己的图标,就不细说了,自己看文档就行。

其实我用的多的还是上面的第一种接口,因为它比较简单,只要把 label 和 message 修改就可以了。

https://img.shields.io/badge/site-markjour.com-brightgreen.svg?style=plastic&logo=nginx

https://img.shields.io/badge/-doing-green

参考资料与拓展阅读

#44 不简单的绝对值

2021-09-17

这篇文章讲到了绝对值计算的问题:One does not simply calculate the absolute value

IEEE 754

三个特殊值:

  1. 如果指数是0并且尾数的小数部分是0,这个数 ±0(和符号位相关)
  2. 如果指数 = 2^e - 1 并且尾数的小数部分是 0,这个数是 ±∞(同样和符号位相关)
  3. 如果指数 = 2^e - 1 并且尾数的小数部分非 0,这个数表示为非数(NaN)。

abs 的实现

class Test {
    public static double abs(double value) {
        if (value < 0) {
            return -value;
        }
        return value;
    }
    public static void main(String[] args) {
        double x = -0.0;
        if (1 / abs(x) < 0) {
            System.out.println("oops");
        }
    }
}

if 中加上条件:value == -0.0 是行不通的,因为 +0.0 == -0.0,可以使用 JDK 中的 Double.compare:

public static double abs(double value) {
    if (value < 0 || Double.compare(value, -0.0) == 0) {
        return -value;
    }
    return value;
}

这样确实有效,不过效率上可能会受到影响,abs 的复杂性就上了一个台阶。

JDK 17 中的实现

java/lang/Double.java

public static int compare(double d1, double d2) {
    if (d1 < d2)
        return -1;           // Neither val is NaN, thisVal is smaller
    if (d1 > d2)
        return 1;            // Neither val is NaN, thisVal is larger

    // Cannot use doubleToRawLongBits because of possibility of NaNs.
    long thisBits    = Double.doubleToLongBits(d1);
    long anotherBits = Double.doubleToLongBits(d2);

    return (thisBits == anotherBits ?  0 : // Values are equal
            (thisBits < anotherBits ? -1 : // (-0.0, 0.0) or (!NaN, NaN)
             1));                          // (0.0, -0.0) or (NaN, !NaN)
}

重新实现

参考 JDK 中的实现,重写 abs

private static final long MINUS_ZERO_LONG_BITS = Double.doubleToLongBits(-0.0);
public static double abs(double value) {
    if (value < 0 || Double.doubleToLongBits(value) == MINUS_ZERO_LONG_BITS) {
        return -value;
    }
    return value;
}

新的问题:NaN 的处理,处理方法:把 doubleToLongBits 改成 doubleToRawLongBits

private static final long MINUS_ZERO_LONG_BITS = Double.doubleToRawLongBits(-0.0);
public static double abs(double value) {
    if (value < 0 || Double.doubleToRawLongBits(value) == MINUS_ZERO_LONG_BITS) {
        return -value;
    }
    return value;
}

JVM 的 JIT 会替换这次调用为底层的 CPU 寄存器操作,效率非常可观。

PS:如果可以省去这个分支的判断逻辑,JVM 可以给我们更好的性能优化?

  1. 中间涉及 CPU 分支预测(branch predictor),如果预测错误,可能会付出相对昂贵的代码。

    We know that branches are bad. If the CPU branch predictor guesses incorrectly, they can be very expensive.

  2. 有传言说,这个调用(doubleToRawLongBits)会导致浮点数寄存器转换到通用集成器。

    Although there are rumors saying that this call may still lead to a transfer from a floating-point register to a general-purpose register. Still it's very fast.

进一步优化

采用 0 减负数等于正数,并且 0 - -0 = 0 的规则:

public static double abs(double value) {
    if (value <= 0) {
        return 0.0 - value;
    }
    return value;
}

这就是长期以来(直到最新的 Java 17),JDK 使用的方法(return (a <= 0.0D) ? 0.0D - a : a;)。

参考:JDK 17 中的的实现:java/lang/Math.java

再进一步

有人提出了意见,认为目前官方的实现 too slow(6506405: Math.abs(float) is slow #4711)。

这就是 jdk-18+6 中引入的新方案(java/lang/Math.java#L1600~L1604):

public static double abs(double a) {
    return Double.longBitsToDouble(Double.doubleToRawLongBits(a) & DoubleConsts.MAG_BIT_MASK);
}

DoubleConsts.MAG_BIT_MASK 就是 0x7fffffffffffffffL, 0 + 63 个 1。

原理就是,通过位运算,清除符号位(使之为 0)。

参考资料与拓展阅读