SPI机制

SPI(Service Provider Interface),是JDK内置的一种 服务提供发现机制,可以用来启用框架扩展和替换组件,主要是被框架的开发人员使用,比如java.sql.Driver接口,其他不同厂商可以针对同一接口做出不同的实现,MySQL和PostgreSQL都有不同的实现提供给用户,而Java的SPI机制可以为某个接口寻找服务实现。Java中SPI机制主要思想是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要,其核心思想就是 解耦
SPI整体机制图如下:
SPI

服务提供方提供了接口实现后,需要在classpath下的META-INF/services/目录下创建以服务接口命名的文件,文件内容为接口的实现类名。
其他程序使用服务时,会通过查找这个jar的META-INF/services/中文件,获取实现类名,进行加载实例化,该服务就可以使用了。JDK中查找服务实现的类为java.util.ServiceLoader。

应用-JDBC

JDBC接口定义

在java中定义了接口java.sql.Driver,并没有实现,具体实现由不通厂商实现。

MySQL实现

MySQL的jar包(mysql-connector-java-8.0.30.jar)中,META-INF/services目录下有文件名java.sql.Driver的内容为com.mysql.cj.jdbc.Driver
SPI META-INF/services

使用方法
1
Connection conn = DriverManager.getConnection(url,username,password);
SPI如何实现

在使用的时候并没有指定使用哪个Driver来连接,那如何使用上MySQL的驱动的呢?这就是我们SPI在起作用。

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
package java.sql;
public class DriverManager {

static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}

private static void loadInitialDrivers() {
String drivers;
try {
drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
public String run() {
return System.getProperty("jdbc.drivers");
}
});
} catch (Exception ex) {
drivers = null;
}

AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {

ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();

try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
}
return null;
}
});

println("DriverManager.initialize: jdbc.drivers = " + drivers);

if (drivers == null || drivers.equals("")) {
return;
}
String[] driversList = drivers.split(":");
println("number of Drivers:" + driversList.length);
for (String aDriver : driversList) {
try {
println("DriverManager.Initialize: loading " + aDriver);
Class.forName(aDriver, true,
ClassLoader.getSystemClassLoader());
} catch (Exception ex) {
println("DriverManager.Initialize: load failed: " + ex);
}
}
}
}

其中
ServiceLoader loadedDrivers = ServiceLoader.load(Driver.class);
Iterator driversIterator = loadedDrivers.iterator();
负责查找classpath下及jar包中META-INF/services目录下java.sql.Driver文件中的内容获取具体实现。

SPI机制的缺陷

通过上面的解析,可以发现,我们使用SPI机制的缺陷:

  • 不能按需加载,需要遍历所有的实现,并实例化,然后在循环中才能找到我们需要的实现。如果不想用某些实现类,或者某些类实例化很耗时,它也被载入并实例化了,这就造成了浪费。
  • 获取某个实现类的方式不够灵活,只能通过 Iterator 形式获取,不能根据某个参数来获取对应的实现类。
  • 多个并发多线程使用 ServiceLoader 类的实例是不安全的.

如何生成requirements.txt

写了一个简单的python项目,希望生成一个requirements.txt文件方便在Github或其他地方运行安装依赖。
作为新手我首先是一个一个从import里面去找到写到文件中的,结果发现居然会报错。
后面找到一个工具pipreqs可以自动识别出项目中用到的所有依赖生成requirements.txt文件

How

Installation

1
pip install pipreqs

Usage

1
2
$ pipreqs /home/project/location
Successfully saved requirements file in /home/project/location/requirements.txt

生成requirements.txt内容如

1
2
3
wheel==0.23.0
Yarg==0.1.9
docopt==0.6.2

更详细的参数用法参照pipreqs Usage

Java类文件在JVM运行的生命周期

java Class文件结构

Java .class 文件是 Java 编程语言的关键组件,遵循精确且定义的结构。 这种结构不仅对于 Java 虚拟机 (JVM) 正确加载和执行字节码至关重要,而且还提供了有关编译后的 Java 代码的大量信息。 下面,我们深入研究类文件结构的基本元素,详细说明每个组件及其在整体架构中的重要性。

class文件格式

类文件由单个 ClassFile 结构组成。 该结构由 JVM 规范定义并遵循特定格式,其中包括以下主要部分:

  • Magic Number魔数:固定值 (0xCAFEBABE)。 此唯一标识符验证该文件是否是 JVM 可读的有效类文件。
  • Version Information版本号:major_version、minor_version,java版本号
  • Constant Pool常量池
  • Access Flags访问标识
  • This Class, Super Class,and Interfaces类索引、父类索引与接口索引集合
  • Fields字段集合
  • Methods方法表集合
  • Attributes属性表集合

以上信息如何查看?

1
javap [options] classes...

JVM需要使用上述信息来正确加载、验证和执行

其是常量池,它是一个集中的字典,经常被类文件中的其他部分引用,突出了它在整个架构中的重要性。

Class文件在JVM中的生命周期

The Class file Lifecycle of a Java Application

1. Loading加载

类加载过程执行以下三个功能:
从clas文件创建二进制数据流
根据内部数据结构解析二进制数据
创建 java.lang.Class 的实例
完成此操作后,类实例就可以进行链接了。

2. Linking链接
2.1 Verification验证

此步骤可确保安全性和完整性。JVM验证class文件的正确性,文件格式验证、语法是否有效、是否符合Java语言规范。

2.2 Preparation准备

在准备过程中,JVM 会为类静态变量分配内存,并将其初始化为默认值。

2.3 Resolution解析

解析阶段包括将类文件中的符号引用解析为直接引用。这就是 JVM 常量池发挥关键作用的地方。主要针对类或接口、字段、类方法、方法类型等。

3. Initializes初始化

执行静态块: 这一阶段涉及执行静态初始化程序和静态块。JVM 会初始化静态字段,并按照它们在类文件中出现的顺序执行任何静态初始化块。
设置最终值: 为类的最终变量分配值,这些值在类的生命周期内不可更改。

4. Usage使用

实例化: JVM 根据应用程序的需要创建类的实例。
执行: 根据运行程序的要求调用和执行方法,访问字段。JVM 会将字节码解释或即时编译为机器代码以便执行。

5. Unloading卸载

垃圾回收: 当一个类不再需要,也没有对其实例的实时引用时,它就可以被卸载。JVM 的垃圾回收器会回收分配给类的内存。

The Anatomy of a Java Virtual Machine Class File
The Execution Lifecycle of a Java Application
javap
Chapter 4. The class File Format

Makefile如何使用

工程化编译项目时,Java用Maven\Gradle,前端用npm,C/C++用Make
使用Make命令编译C/C++时,是通过Make工具实现。推荐使用w64devkit
w64devkit支持Linux命令

  • 根据更改的源文件,自动确定需要更新哪些文件。它还自动确定更新文件的正确顺序,以防一个非源文件依赖于另一个非来源文件。
    因此,如果您更改一些源文件,然后运行Make,则不需要重新编译所有程序。它只更新那些直接或间接依赖于您更改的源文件的非源文件。
    如何确定依赖的呢,来源与我们在Makefile中指定的dependencies
  • Make不限于任何特定的语言。所有能在命令行运行的编程语言都能处理(Java\Golang\Python…)。另外基于文件的改变然后更新另外的文件也可以。

Makefile中的一条规则告诉Make如何执行一系列命令,以便从源文件构建目标文件。它还指定了目标文件的依赖项列表。此列表应包括用作规则中命令输入的所有文件(无论是源文件还是其他目标文件)。

1
2
3
target:   dependencies ...
commands
...
1
2
3
4
hello: hello.o
g++ -o hello hello.o
hello.o: hello.cpp
g++ -c hello.cpp

更新机制

运行Make时,可以指定要更新的特定目标;否则,Make会更新makefile中列出的第一个目标。当然,必须首先更新生成这些目标所需的任何其他目标文件作为输入。
Make使用makefile来确定哪些目标文件应该更新,然后确定哪些文件实际上需要更新。如果目标文件比其所有依赖项都新,那么它已经是最新的,不需要重新生成。其他目标文件确实需要更新,但顺序正确:每个目标文件都必须重新生成,然后才能用于重新生成其他目标。

Makefile文件命名

Make自动查找makefile文件,顺序为GNUmakefile>makefile>Makefile
GUNmakefile:不建议使用,只能支持GUN make
makefile:所有版本都能识别
Makefile:推荐,最常用

运行make时没有找到上述文件会报错,但可以手动指定文件名

1
2
make -f <filename>
make --file=<filename>

香港卡全攻略

为什么要办香港卡?

保险?参与港美股?参与加密货币?OpenAI付费?公司股票不转回?对于我来说,听着牛逼想要但又不是必须的理由。最后决定办理是因为觉得港股券商入金有奖励,家人可以顺便去玩一圈。

选择哪个银行?

有那么多银行,我们可以办哪些,哪些又比较好呢?
从发币行角度考虑

  • 香港上海汇丰银行
  • 渣打银行(香港)
  • 中国银行(香港)
    三家银行都是在全球开展业务。从网上的信息来看汇丰和中国银行很好办,渣打不太好办。
    另外虚拟银行众安银行、天星银行、蚂蚁银行等,没有考虑和办理就不列了。
香港上海汇丰银行 中国银行
总部 伦敦 中国
办理难度 容易 容易
转汇费用 据说同名内地香港互转不要手续费 同名内地香港互转不要手续费

听说汇丰和中国银行从内地卡转入香港卡不要手续费。我办了汇丰之后就飘了不想再办就出去玩了。回来内地后去汇丰银行办理内地卡,没想到她直接告诉我只接待Premier客户,有存款要求不然有管理费。

怎么办理?

两种方式

  • 直接到营业网点线下排队办理

    如果人多去晚了可能无法办理,或者预约的人数多也可能被拒绝

  • 网上预约,再到网点办理

    网上预约后无需再早去现场排队拿号,直接告诉接待人员,然后就给你安排了

Read More

C++编译器有哪些

没想到C++环境搭建都这么麻烦,编译器有很多种。

没有研究是觉得有些复杂。

在编译时会将我们include,#define等合并成一个文件。那如何看到这个编译的中间文件呢?
在Visual Studio中设置
Preprocess to a File

代码

1
2
3
4
5
6
#include <iostream>

int main() {
std::cout << "Hello World!" << std::endl;
std::cin.get();
}

Ctrl + F7编译后,可以从Debug目录下看到FileName.i文件中已经包含了include的 iostream代码

注意:Visual Studio和Visual Studio Code是不同的工具

下一个看看C++如何建立link,为什么C++中不像java一样需要导入包,就可以link,然后引用呢?如果有两个同名的函数,那link的时候会报错吗?

netlink

什么是Netlink通信机制

Netlink是linux提供的用于内核和用户态进程之间的通信方式。

但是注意虽然Netlink主要用于用户空间和内核空间的通信,但是也能用于用户空间的两个进程通信。只是进程间通信有其他很多方式,一般不用Netlink。除非需要用到Netlink的广播特性时。

那么Netlink有什么优势呢?

一般来说用户空间和内核空间的通信方式有三种:/proc、ioctl、Netlink。而前两种都是单向的,但是Netlink可以实现双工通信。

Netlink协议基于BSD socket和AF_NETLINK地址簇(address family),使用32位的端口号寻址(以前称作PID),每个Netlink协议(或称作总线,man手册中则称之为netlink family),通常与一个或一组内核服务/组件相关联,如NETLINK_ROUTE用于获取和设置路由与链路信息、NETLINK_KOBJECT_UEVENT用于内核向用户空间的udev进程发送通知等。

netlink具有以下特点:

  • 支持全双工、异步通信(当然同步也支持)
  • 用户空间可使用标准的BSD socket接口(但netlink并没有屏蔽掉协议包的构造与解析过程,推荐使用libnl等第三方库)
  • 在内核空间使用专用的内核API接口
  • 支持多播(因此支持“总线”式通信,可实现消息订阅)
  • 在内核端可用于进程上下文与中断上下文

没有Linux和C的基础是真看不懂这是啥玩意儿…

什么是Redis keyspace notification

Redis消息架构两种中的一种keyspace notification、 Pub/Sub

实时监控Rdis 键值改变。

Keyspace通知允许client订阅Pub/Sub channels,以便接收影响Redis数据的事件。

功能概览

键空间通知使得客户端可以通过订阅频道或模式, 来接收那些以某种方式改动了 Redis 数据集的事件。

以下是一些键空间通知发送的事件的例子:

  • 所有修改键的命令。
  • 所有接收到 LPUSH 命令的键。
  • 0 号数据库中所有已过期的键。

事件通过 Redis 的订阅与发布功能(pub/sub)来进行分发, 因此所有支持订阅与发布功能的客户端都可以在无须做任何修改的情况下, 直接使用键空间通知功能。

因为 Redis 目前的订阅与发布功能采取的是发送即忘(fire and forget)策略, 所以如果你的程序需要可靠事件通知(reliable notification of events), 那么目前的键空间通知可能并不适合你: 当订阅事件的客户端断线时, 它会丢失所有在断线期间分发给它的事件。

未来将会支持更可靠的事件分发, 这种支持可能会通过让订阅与发布功能本身变得更可靠来实现, 也可能会在 Lua 脚本中对消息(message)的订阅与发布进行监听, 从而实现类似将事件推入到列表这样的操作。

Read More

C/C++参数中的_In_和_Out_代表什么

最近看方法参数中的疑惑

1
2
3
sai_status_t sai_api_query(_In_ sai_api_t sai_api_id, _Out_ void** api_method_table)
{
}

_In_和_Out_是用于标记参数的传递方式的注解。它们并不是C\C++语言的关键字,而是一种约定俗成的注释方式,用于提示函数的调用者和阅读者关于参数的特性。

_In_表示该参数是输入参数,即函数内部会读取参数的值,但不会修改它。通过这个标记,我们可以清楚地知道该参数在函数内部只被用于读取数据。

_Out_表示该参数是输出参数,即函数内部会修改参数的值,并将修改后的结果返回给调用者。通过这个标记,我们可以清楚地知道该参数在函数内部会被修改,我们在调用函数时需要确认传入的参数具备存储修改后结果的能力。

Read More