使用 sqlparse 库解析 SQL 查询语句

以下是一个简单的 SQL 解析器的 Python 代码示例,这个代码使用了 Python 的 SQL 解析库 sqlparse,它实现了一个完整的 SQL 解析器。它接受一个 SQL 查询语句作为输入,并返回一个字典,包含了查询类型和查询目标。具体来说,它使用 sqlparse.parse 函数解析 SQL 查询语句,并根据查询类型调用相应的解析函数来获取查询目标。目前,它支持解析 SELECT、INSERT、UPDATE 和 DELETE 四种类型的查询语句。对于 SELECT 查询语句,它将返回一个包含字段列表、表名和条件的字典;对于 INSERT 查询语句,它将返回一个包含表名、字段列表和值列表的字典;对于 UPDATE 查询语句,它将返回一个包含表名、字段列表、值列表和条件的字典;对于 DELETE 查询语句,它将返回一个包含表名和条件的字典。请注意,这个解析器可能无法处理所有的 SQL 查询语句,因此您可能需要根据实际情况进行修改和扩展。

import sqlparse

def parse_sql(sql):
    # 使用 sqlparse 库解析 SQL 查询语句
    parsed = sqlparse.parse(sql)[0]

    # 获取查询类型(SELECT、INSERT、UPDATE 或 DELETE)
    query_type = parsed.get_type()

    # 获取查询目标(表名、字段列表、值列表等)
    if query_type == 'SELECT':
        target = parse_select(parsed)
    elif query_type == 'INSERT':
        target = parse_insert(parsed)
    elif query_type == 'UPDATE':
        target = parse_update(parsed)
    elif query_type == 'DELETE':
        target = parse_delete(parsed)
    else:
        target = None

    return {'type': query_type, 'target': target}

def parse_select(parsed):
    # 获取字段列表
    fields = []
    for token in parsed.tokens:
        if isinstance(token, sqlparse.sql.IdentifierList):
            for identifier in token.get_identifiers():
                fields.append(identifier.value)
        elif isinstance(token, sqlparse.sql.Identifier):
            fields.append(token.value)

    # 获取表名
    for token in parsed.tokens:
        if isinstance(token, sqlparse.sql.Where):
            table = token.tokens[2].value
            break
    else:
        table = None

    # 获取条件
    for token in parsed.tokens:
        if isinstance(token, sqlparse.sql.Where):
            condition = token.tokens[4].value
            break
    else:
        condition = None

    return {'fields': fields, 'table': table, 'condition': condition}

def parse_insert(parsed):
    # 获取表名
    table = parsed.tokens[2].value

    # 获取字段列表
    fields = []
    for token in parsed.tokens:
        if isinstance(token, sqlparse.sql.Parenthesis):
            for identifier in token.get_identifiers():
                fields.append(identifier.value)

    # 获取值列表
    values = []
    for token in parsed.tokens:
        if isinstance(token, sqlparse.sql.Values):
            for value_list in token.get_parameters():
                values.append([value.value for value in value_list])

    return {'table': table, 'fields': fields, 'values': values}

def parse_update(parsed):
    # 获取表名
    table = parsed.tokens[2].value

    # 获取字段列表和值列表
    fields = []
    values = []
    for token in parsed.tokens:
        if isinstance(token, sqlparse.sql.IdentifierList):
            for identifier in token.get_identifiers():
                fields.append(identifier.value)
        elif isinstance(token, sqlparse.sql.Assignment):
            fields.append(token.left.value)
            values.append(token.right.value)

    # 获取条件
    for token in parsed.tokens:
        if isinstance(token, sqlparse.sql.Where):
            condition = token.tokens[4].value
            break
    else:
        condition = None

    return {'table': table, 'fields': fields, 'values': values, 'condition': condition}

def parse_delete(parsed):
    # 获取表名
    table = parsed.tokens[2].value

    # 获取条件
    for token in parsed.tokens:
        if isinstance(token, sqlparse.sql.Where):
            condition = token.tokens[4].value
            break
    else:
        condition = None

    return {'table': table, 'condition': condition}

只要你见性志诚,念念回首处,即是灵山

三藏在马上欢喜道:“徒弟,我一向西来,经历许多山水,都是那嵯峨险峻之处,更不似此山好景,果然的幽趣非常。若是相近雷音不远路,我们好整肃端严见世尊。”行者笑道:“早哩,早哩!正好不得到哩!”沙僧道:“师兄,我们到雷音有多少远?”行者道:“十万八千里,十停中还不曾走了一停哩。”八戒道:“哥啊,要走几年才得到?”行者道:“这些路,若论二位贤弟,便十来日也可到;若论我走,一日也好走五十遭,还见日色;若论师父走,莫想,莫想!”唐僧道:“悟空,你说得几时方可到?”行者道:“你自小时走到老,老了再小,老小千番也还难。只要你见性志诚,念念回首处,即是灵山。”沙僧道:“师兄,此间虽不是雷音,观此景致,必有个好人居止。”行者道:“此言却当。这里决无邪祟,一定是个圣僧仙辈之乡,我们游玩慢行。”不题。

网络中的DMZ

在计算机安全中,DMZ 或非军事区(有时称为外围网络或屏蔽子网)是一个物理或逻辑子网,它包含组织的面向外部的服务并将其暴露给不受信任的、通常更大的网络,例如 Internet。 DMZ 的目的是为组织的局域网 (LAN) 添加额外的安全层:外部网络节点只能访问 DMZ 中公开的内容,而组织网络的其余部分则受到防火墙保护。 DMZ 用作位于 Internet 和专用网络之间的小型隔离网络。

不要将其与 DMZ 主机相混淆,DMZ 主机是一些家用路由器中的一种功能,通常与普通 DMZ 有很大不同。

这个名字来自非军事区一词,这是国家之间不允许军事行动的区域。

DMZ原理参考 https://en.wikipedia.org/wiki/DMZ_(computing)

提升英语水平的十个步骤

前置条件

你的英文词汇量已经达到高三人教版教材要求的水平,也就是掌握英文常用单词数目2000个。如果你还没达到,建议你退学重读高一。我有很多高中可以推荐,例如郴州市明星学校。需要的可以私信我。

这2000个单词,我分为如下几个部分:

  1. 1000个名词;重要生活物件的名词,表述思维的抽象名词等。apple,orange 当然是需要的;inspiration, sabotage 也是需要的。
  2. 250个动词;描述任意动作和抽象动作。do,make是需要的;reinforce, heighten 更是需要的。
  3. 250个副词;修辞动作。happily, luckily 当然是需要的;figuratively, ubiquitously 也是需要的;
  4. 500个形容词;修辞名词。big,large当然是需要的;blunt, riveting 更是需要的。

步骤一

学会基本的语法。我建议买一本【薄冰英语语法】来看。千万不要听高中老师胡说八道。

下面是几个例子:

  1. 理解为什么 there is a woman swallowing banana 里面的swallowing 必须是ing形式;
  2. 理解为什么 Do you know where he heads 里面的 heads 后面为什么不加 to;
  3. 理解为什么 Given time, we would heal 里面的 given 为什么不是 giving;
  4. 理解为什么 Why you always take things for granted 里面的 granted 要用被动。

步骤二

买一本英英词典(例如牛津词典),并且把你的汉英、英汉词典烧掉;该辞典的好处是用2000个常用字解释所有词条。每次看见新单词请打开这本辞典找到词条,用你的语法知识和单词量理解这个词。

步骤三

使用学会的单词;看到一个学会的新单词,例如unanimously,你觉得太罕见不会用;不是的,请看他在词典中的例句,把例句背下来。

步骤四

参加校园英语角,在英语角里面疯狂使用辞典例句,并且虚构故事,吓死他们。。逃(

步骤五

上一个步骤是虚构的;在现实生活中,充分使用学习到的例句;例如早上起来了你要说什么?交作业忘记写了你要说什么?食堂打饭你要说什么?洗澡你要说什么?OOXX你要说什么?就这个意思。

步骤六

用英文的逻辑改造你的思维。基于步骤五,你已经可以完全使用英文进行思考了。语言决定了你的思维能力;在思考的过程中,你会进一步具象化英文中的名词,动词,词组等;并且连接你听说读写之功能。因为说白了,听说读写的核心其实是让你用英文想。如果你都必须用中文想,说一口标准的大白东北英语,那你还是放弃治疗吧。

步骤七

准备一个记录本,把你认为有用的动词,副词,形容词和名词按照这样的顺序(动副形名的顺序,因为他们在语言中的重要性依次递减)记录下来;方便日后参照。使用任何一种记忆法防止你忘记(一般来说忘记3次之后你就不会忘记了)。除非你过目不忘,这种形式还是很重要的。

步骤八

买一本适合自己词汇量的英文杂志,例如空中英语教室。另外找一本新概念英语或者本科英语教材每天早上按照要求背诵一下。其实这都不重要,关键是把你字典上学会的东西找个地方用起来。

步骤九

买一本Economist自己看,或者看Friends的英文原版英文字幕。尝试用英文开始写总结。其实这都不重要。对于程序员来说,能看懂就可以了,不需要写==

步骤十

坚持1年。学完一年之后,相信你就可以自己去看C++ PRIMER了。另外,你不会还不知道PRIMER啥意思把。去看牛津词典,然后找那个例句自己练习100次。。

WALDEN 瓦尔登湖英文版 梭罗

WALDENWALDEN

by Henry David Thoreau

Economy

When I wrote the following pages, or rather the bulk of them, I lived alone, in the woods, a mile from any neighbor, in a house which I had built myself, on the shore of Walden Pond, in Concord, Massachusetts, and earned my living by the labor of my hands only. I lived there two years and two months. At present I am a sojourner in civilized life again.

I should not obtrude my affairs so much on the notice of my readers if very particular inquiries had not been made by my townsmen concerning my mode of life, which some would call impertinent, though they do not appear to me at all impertinent, but, considering the circumstances, very natural and pertinent. Some have asked what I got to eat; if I did not feel lonesome; if I was not afraid; and the like. Others have been curious to learn what portion of my income I devoted to charitable purposes; and some, who have large families, how many poor children I maintained. I will therefore ask those of my readers who feel no particular interest in me to pardon me if I undertake to answer some of these questions in this book. In most books, the I, or first person, is omitted; in this it will be retained; that, in respect to egotism, is the main difference. We commonly do not remember that it is, after all, always the first person that is speaking. I should not talk so much about myself if there were anybody else whom I knew as well. Unfortunately, I am confined to this theme by the narrowness of my experience. Moreover, I, on my side, require of every writer, first or last, a simple and sincere account of his own life, and not merely what he has heard of other men’s lives; some such account as he would send to his kindred from a distant land; for if he has lived sincerely, it must have been in a distant land to me. Perhaps these pages are more particularly addressed to poor students. As for the rest of my readers, they will accept such portions as apply to them. I trust that none will stretch the seams in putting on the coat, for it may do good service to him whom it fits.

什么是自治系统?| 什么是 ASN?

什么是自治系统?

Internet 是由不同网络组成的网络*,自治系统是组成 Internet 的大型网络。更具体地说,自治系统(AS)是具有统一路由策略的巨型网络或网络群组。连接到 Internet 的每台计算机或设备都连接到一个 AS。

as

可以认为 AS 类似于一个城镇的邮局。邮件从一个邮局到另一个邮局,直到到达正确的城镇为止,然后该城镇的邮局将在该城镇内传递邮件。与之类似,数据包在整个互联网范围内通过从 AS 跳到 AS,直到它们到达包含其目的地互联网协议 (IP) 地址的 AS。该 AS 中的路由器将数据包发送到 IP 地址。

每个 AS 都控制一组特定的 IP 地址,就像每个镇的邮局负责将邮件传递到该镇内的所有地址一样。给定 AS 可以控制的 IP 地址范围称为其“IP 地址空间”。

大多数 AS 连接到其他几个 AS。如果一个 AS 仅连接到另一个 AS 并共享相同的路由策略,则可以将其视为第一个 AS 的子网。

通常,每个 AS 由单个大型组织(例如 Internet 服务提供商(ISP)、大型企业技术公司、大学或政府机构)运营。

*网络是由两台或更多台连接的计算机组成的群组。

什么是 AS 路由策略?

AS 路由策略是由 AS 控制的 IP 地址空间的列表以及与其连接的其他 AS 的列表。此信息对于将数据包路由到正确的网络是必需的。AS 使用边界网关协议(BGP)向 Internet 通知此信息。

什么是 IP 地址空间?

IP 地址的指定群组或范围称为“IP 地址空间”。每个 AS 控制一定数量的 IP 地址空间。(一组 IP 地址也可以称为 IP 地址“块”。)

这种情况类似于如果世界上的所有电话号码都按顺序列出,并且为每个电话公司分配一个范围:电话公司A 控制号码 000-0000 至 599-9999,电话公司 B 控制号码 600-0000 至 999-9999。如果爱丽丝拨打 555-2424 致电米歇尔,她的致电将通过电话公司 A 路由到米歇尔。如果她拨打 867-5309 致电珍妮,她的致电将通过电话公司 B 路由到珍妮。

这就是 IP 地址空间的工作方式。假设 Acme 公司运营一家 AS,并控制一个 IP 地址范围,其中包括地址 192.0.2.253。如果计算机将数据包发送到 192.0.2.253,则该数据包最终将到达由 Acme 公司控制的 AS。如果该第一台计算机也将数据包发送到 198.51.100.255,则数据包将到达另一个 AS(尽管它们可能会在途中通过 Acme 公司的 AS)。

什么是 IP 地址前缀?

当网络工程师通知哪个 IP 地址由哪个 AS 控制时,他们通过提及每个 AS 拥有的 IP 地址“前缀”来实现。IP 地址前缀是 IP 地址的范围。根据 IP 地址的写入方式,IP 地址前缀通过以下方式表示:192.0.2.0/24。这表示 IP 地址 192.0.2.0 到 192.0.2.255,而不是 192.0.2.0 到 192.0.2.24。

什么是自治系统编号(ASN)?

每个 AS 都分配有一个官方编号或自治系统编号(ASN),类似于每个企业如何获得具有唯一官方编号的营业执照。但是,与企业不同,外部各方通常仅通过编号来引用 AS。

AS 编号或 ASN 是介于 1 和 65534 之间的唯一 16 位数字或介于 131072 和 4294967294 之间的 32 位数字。它们通过这种格式表示:AS(数字)。例如,Cloudflare 的 ASN 是 AS13335。据不完全估计,全世界有超过 90,000 个 ASN 在使用。

只有与网络间路由器的外部通信才需要 ASN(请参阅下面的“什么是 BGP?”)。AS 中的内部路由器和计算机可能不需要知道该 AS 的编号,因为它们仅与该 AS 内的设备通信。

在分配 ASN 的管理机构给它编号之前,AS 必须满足某些资格。它必须具有唯一的路由策略,具有一定的大小,并且与其他 AS 具有多个连接。可用的 ASN 数量有限,如果过度分发它们,则供应会用光,路由也会变得更加复杂。

什么是 BGP?

AS 通过边界网关协议(BGP)通知其到其他 AS 和路由器的路由策略。BGP 是在 AS 之间路由数据包的协议。如果没有这些路由信息,大规模运行 Internet 将很快变得不切实际:数据包将丢失或花费太长时间才能到达目的地。

每个 AS 使用 BGP 通知它们负责的 IP 地址以及它们连接的其他 AS。BGP 路由器从世界各地的 AS 中获取所有这些信息,并将其放入称为路由表的数据库中,以确定从 AS 到 AS 的最快路径。当数据包到达时,BGP 路由器会参考其路由表来确定数据包接下来应转到哪个 AS。

全世界有许多 AS,BGP 路由器不断更新其路由表。网络脱机时,新网络联机时,AS 扩展或收缩其 IP 地址空间,所有这些信息都必须通过 BGP 通知,以便 BGP 路由器可以调整其路由表。

为什么需要 BGP 路由?IP 不用于路由吗?

IP(Internet 协议)确实用于路由,因为它指定每个数据包将到达的目的地。BGP 负责将数据包以最快的路由定向到目的地。如果没有 BGP,IP 数据包将在 Internet 上从 AS 到 AS 随机反弹,类似于驾驶员试图通过猜测走哪条路来到达目的地。

自治系统如何相互连接?

AS 之间相互连接,并通过称为对等的过程交换网络流量(数据包)。AS 彼此对等的一种方式是通过在称为 Internet 交换点(IXP)的物理位置进行连接。IXP 是一个大型局域网(LAN),具有许多路由器、交换机和电缆连接。

参考

https://www.cloudflare.com/zh-cn/learning/network-layer/what-is-an-autonomous-system/

文本分析处理

1. 背景知识

1.1 自然语言处理

1.1.1 文本向量化

文本向量化(又称“词向量模型”、“向量空间模型”)即将文本表示成计算机可识别的实数向量,根据粒度大小不同可将文本特征表示分为字、词、句子或篇章几个层次。文本向量化的方法主要分为离散表示和分布式表示。

1.1.1.1 离散表示

一种基于规则和统计的向量化方式,常用的方法包括词集模型和词袋模型,都是基于词之间保持独立性、没有关联为前提,将所有文本中单词形成一个字典,然后根据字典来统计单词出现频数,不同的是:

  • 词集模型:例如One-Hot Representation,只要单个文本中单词出现在字典中,就将其置为1,不管出现多少次
  • 词袋模型:只要单个文本中单词出现在字典中,就将其向量值加1,出现多少次就加多少次。

其基本的特点是忽略了文本信息中的语序信息和语境信息,仅将其反映为若干维度的独立概念,这种情况有着因为模型本身原因而无法解决的问题,比如主语和宾语的顺序问题,词袋模型天然无法理解诸如“我为你鼓掌”和“你为我鼓掌”两个语句之间的区别。

One-Hot Representation

将每个词都表示成一个长向量,向量的维度是词表的大小,词的当前位置用1表示,其他位置用0表示。

import numpy as np
import pandas as pd
import jieba

def doc2onthot_matrix(file_path):
    # 读取待编码的文件
    with open(file_path, encoding="utf-8") as f:
        docs = f.readlines()
    with open(file_path1, encoding="utf-8") as f:
        docs1 = f.readlines()

    # 将文件每行分词,分词后的词语放入words中
    words=[]
    for i in range(len(docs)):
        docs[i] = jieba.cut(docs[i].strip("\n"))
        words += docs[i]

    # 找出分词后不重复的词语,作为词袋,是后续onehot编码的维度
    vocab = sorted(set(words), key=words.index)

    # 建立一个M行V列的全0矩阵,M是文档样本数,这里是行数,V为不重复词语数,即编码维度
    V = len(vocab)
    M = len(docs)
    onehot = np.zeros((M,V))

    for i,doc in enumerate(docs1):
        words = ""
        for word in doc:
            if word != " ":
                words = words + word
                continue
            if words in vocab:
                pos = vocab.index(words)
                onehot[i][pos] = 1
                words = ""
            else:
                words = ""
                continue
    onehot=pd.DataFrame(onehot, columns=vocab)
    return onehot

file_path = "./test.txt"
file_path1 = "./word.txt"
onehot = doc2onthot_matrix(file_path)
onehot

One-Hot编码的优点是简单快捷,缺点是数据稀疏、耗时耗空间、不能很好地展示词与词之间的相似关系,且还未考虑到词出现的频率,因而无法区别词的重要性。

one-hot matrix

one-hot matrix

1.2 特征提取

特征提取(英语:Feature extraction)在机器学习、模式识别和图像处理中有很多的应用。特征提取是从一个初始测量的资料集合中开始做,然后建构出富含资讯性而且不冗余的导出值,称为特征值(feature)。它可以帮助接续的学习过程和归纳的步骤,在某些情况下可以让人更容易对资料做出较好的诠释。特征提取是一个降低维度的步骤,初始的资料集合被降到更容易管理的族群(特征)以便于学习,同时保持描述原始资料集的精准性与完整性。

当一个算法的输入资料太过于庞大冗余以至于不便处理(如:一样的测量方法但是分别使用英尺和米表示,或是影像中像素的重复性),这些资料可以被转换成化简后的特征集合,也称作特征向量(feature vector),决定这些原始资料子集的步骤称为特征提取 。成功的情形下,被选择的特征包含跟输入资料相关的资讯,因此这些被化简后的特征能够被用来做理想的任务,而不使用原始完整的初始资料来做这个任务。

1.2.1 概论

相较于原始庞大的资料集合需要很大量的资源来描述,特征提取可以减少需要描述这些资料的资源。当我们分析复杂资料时,其中一个主要的问题是源自于变数的数量过多。分析很多个变数一般来说需要很大量的内存以及计算能力,同时太多变数也可能造成分类问题的算法有过度拟合于训练资料的现象,因此对新的采样无法有效地归纳。特征提取是处理变数组合并维持资料充足的准确性时,常通称的术语。很多机器学习的实作者认为适当的特征提取是有效模型构建的关键。[3]

可以利用已经建构好的应用相关的特征集合来改善结果,通常这样的特征集合是被专家所建构。其中一种此类处理被叫做特征工程师。除此之外,我们也可以使用一般的降维技术,如下:

  • 独立成分分析
  • 等距特征映射
  • 核主成分分析
  • 潜在语义学
  • 偏最小二乘回归
  • 主成分分析
  • 多因子降维法
  • 非线性降维
  • 多线性主成分分析
  • 半定式嵌入
  • 自编码器

1.2.2 图像处理

特征提取其中一个非常重要的应用领域为图像处理,其中的算法可以被用来侦测跟分离数位影像跟影片串流中,想要提取的部分或形状(特征)。常见的影像处理相关的特征处理如下:

低阶的特征

  • 边缘检测
  • 角检测
  • 斑点检测
  • 脊检测
  • 尺度不变特征转换
    曲率
  • 边缘方向
  • 改变强度
  • 自相关
    影像动作
  • 移动侦测
  • 微分方法
  • 光流法
    形状相关
  • 二值化
  • 连通分量标记
  • 模板匹配
  • 霍夫变换
  • 广义霍夫变换

1.2.3 深度学习的特征提取

以往主成分分析为特征提取极常使用的降维方法,近来利用深度学习神经网络的自编码器则相当常被使用。他可以跟深度学习中的各种技术(例如:深度神经网络,卷积神经网络)结合。其中,卷积神经网络能十分有效的撷取影像中的特征,因此对于影像的资料降维撷取特征的效果特别杰出。此外,卷积神经网络在大型数据库中影像辨识相关的议题上(例如:物件分类)取得相当杰出的成果。因此也有人使用在大型数据库上预先训练好的卷积神经网络来做特征提取。

2. 文本分析简介

文本分析分类

参考
https://zh.wikipedia.org/zh-my/%E7%89%B9%E5%BE%B5%E6%8F%90%E5%8F%96#:~:text=%E7%89%B9%E5%BE%81%E6%8F%90%E5%8F%96%EF%BC%88%E8%8B%B1%E8%AF%AD%EF%BC%9AFeature%20extraction,%E5%87%BA%E8%BE%83%E5%A5%BD%E7%9A%84%E8%AF%A0%E9%87%8A%E3%80%82

golang通过json-iterator提供的扩展点,实现json编解码过程可定制

json-iterator/go是一个golang编解码库,主要用于比特序列与map/struct的互转,在使用过程中,我们可能需要对编解码过程进行处理,比如客户端发过来的请求体中某个字段为加密或编码过的,业务代码不想感知这种行为,如何在反序列化的过程中优雅地处理掉这种问题,json-iterator提供了Extension的机制,来满足一些复杂的编解码场景。
通过extendsion机制可实现下列能力:

  • 定制化的codec
  • 操作struct属性

下面根据官方文档介绍其使用方式。

ValEncoder/ValDecoder接口

在介绍Extension的使用之前,需要先介绍一下ValEncoder和ValDecoder,因为Extension的本质上就是针对不同的类型创建不同的ValEncoder和ValDecoder实现的。注意,ValEncoder/ValDecoder和json.Encoder/json.Decoder是不一样的概念,不要混淆了。

ValEncoder
type ValEncoder interface {
    IsEmpty(ptr unsafe.Pointer) bool
    Encode(ptr unsafe.Pointer, stream *Stream)
}

ValEncoder实际上是json-iterator内部用于针对某个类型的数据进行序列化编码的编码器,它的两个成员函数说明如下:

  • Encode

Encode函数用于实现某个类型数据的编码,ptr是指向当前待编码数据的指针,stream提供不同的接口供使用者将各种类型的数据写入到输出设备(详见Stream章节)。那么,在这个函数里面,我们怎么实现编码呢?实际上,我们大部分时间做的,就是将ptr转换成这个ValEncoder对应的数据类型的指针,然后调用stream的接口,将ptr指向的数值进行编码输出

  • IsEmpty

IsEmpty是跟omitempty这个tag相关的函数。我们都知道,在一个结构体里面,如果某个字段的tag带上了omitempty属性,那么当这个字段对应的"数值为空"时,这个字段在序列化时不会被编码输出。那么什么叫"数值为空"呢?对于不同类型的数据,恐怕应该是有不同的定义的。因此IsEmpty这个函数里面,就是需要你去实现,你的ValEncoder对应的数据类型在实际数值是什么的时候,称作"数值为空"

我们看一个具体的例子,来帮助我们理解ValEncoder。json-iterator提供了一个内置的TimeAsInt64Codec,来看看它的实现:

func (codec *timeAsInt64Codec) IsEmpty(ptr unsafe.Pointer) bool {
    ts := *((*time.Time)(ptr))
    return ts.UnixNano() == 0
}

func (codec *timeAsInt64Codec) Encode(ptr unsafe.Pointer, stream *jsoniter.Stream) {
    ts := *((*time.Time)(ptr))
    stream.WriteInt64(ts.UnixNano() / codec.precision.Nanoseconds())
}

Encode函数中,将ptr转换成指向time.Time类型的指针,然后对其解引用拿到了其指向的time.Time对象。接下来调用其成员函数计算出它对应的unix时间,最后调用stream的写入接口将这个int64的unix时间数值进行编码输出,这样就完成了将原本以对象方式输出的time.Time数值,转换成int64类型的unix时间输出

IsEmpty通过同样方式拿到ptr指向的time.Time对象,然后将time.Time类型"数值为空"定义为其转换出来的unix时间为0

ValDecoder
type ValDecoder interface {
    Decode(ptr unsafe.Pointer, iter *Iterator)
}

ValEncoder实际上是json-iterator内部用于针对某个类型的数据进行反序列化解码的解码器,它的成员函数说明如下:

  • Decode

Decode函数用于实现某个类型数据的解码,ptr是指向当前待写入数据的指针,iter提供不同的接口供使用者将各种类型的数据从输入源读入(详见Iterator章节)。那么,在这个函数里面,我们怎么实现解码呢?首先,我们调用iter提供的接口,从json串的输入源读入ValDecoder对应类型的数据,然后将ptr做一个强转,将其转换成指向ValDecoder对应类型的指针,然后将该指针指向的数据设置成我们通过iter接口读取出来的值

还是看TimeAsInt64Codec的例子

func (codec *timeAsInt64Codec) Decode(ptr unsafe.Pointer, iter *jsoniter.Iterator) {
    nanoseconds := iter.ReadInt64() * codec.precision.Nanoseconds()
    *((*time.Time)(ptr)) = time.Unix(0, nanoseconds)
}

Decode函数中,调用iter的接口从json输入源中读取了一个int64的数值,接下来因为我们这个ValDecoder对应的数据类型是time.Time,这里把ptr转换成指向time.Time类型的指针,并以我们读入的int64数值为unix时间初始化了一个time.Time对象,最后将它赋给ptr指向的数值。这样,我们就完成了从json串中读入unix时间,并将其转换成time.Time对象的功能

定制你的扩展

要定制序列化/反序列化扩展,需要实现Extension接口,并通过RegisterExtension进行注册,Extension包含以下方法:

type Extension interface {
    UpdateStructDescriptor(structDescriptor *StructDescriptor)
    CreateMapKeyDecoder(typ reflect2.Type) ValDecoder
    CreateMapKeyEncoder(typ reflect2.Type) ValEncoder
    CreateDecoder(typ reflect2.Type) ValDecoder
    CreateEncoder(typ reflect2.Type) ValEncoder
    DecorateDecoder(typ reflect2.Type, decoder ValDecoder) ValDecoder
    DecorateEncoder(typ reflect2.Type, encoder ValEncoder) ValEncoder
}

当然,很多情况下,我们只需要用到里面的部分功能。json-iterator里面提供了一个DummyExtension,它是一个最基础的Extension实现(基本什么都不做或返回空)。当你在定义自己的Extension时,你可以匿名地嵌入DummyExtension,这样你就不需要实现所有的Extension成员,只需要关注自己需要的功能。下面我们通过一些例子,来说明Extension的各个成员函数可以用来做什么

  • UpdateStructDescriptor

UpdateStructDescriptor函数中,我们可以对结构体的某个字段定制其编码/解码器,或者控制该字段序列化/反序列化时与哪些字符串绑定

type testCodec struct{
}

func (codec *testCodec) Encode(ptr unsafe.Pointer, stream *jsoniter.Stream){
    str := *((*string)(ptr))
    stream.WriteString("TestPrefix_" + str)
}

func (codec *testCodec) IsEmpty(ptr unsafe.Pointer) bool {
    str := *((*string)(ptr))
    return str == ""
}

type sampleExtension struct {
    jsoniter.DummyExtension
}

func (e *sampleExtension) UpdateStructDescriptor(structDescriptor *jsoniter.StructDescriptor) {
    // 这个判断保证我们只针对testStruct结构体,对其他类型无效
    if structDescriptor.Type.String() != "main.testStruct" {
        return
    }

    binding := structDescriptor.GetField("TestField")
    binding.Encoder = &testCodec{}
    binding.FromNames = []string{"TestField", "Test_Field", "Test-Field"}
}

func extensionTest(){
    type testStruct struct {
        TestField string
    }

    t := testStruct{"fieldValue"}
    jsoniter.RegisterExtension(&sampleExtension{})
    s, _ := jsoniter.MarshalToString(t)
    fmt.Println(s)
    // Output:
    // {"TestField":"TestPrefix_fieldValue"}

    jsoniter.UnmarshalFromString(`{"Test-Field":"bbb"}`, &t)
    fmt.Println(t.TestField)
    // Output:
    // bbb
}

上面的例子,首先我们用testCodec实现了一个ValEncoder,它编码时在字符串的前面加了一个"TestPrefix"的前缀再输出。接着我们注册了一个sampleExtension,在UpdateStructDescriptor函数中我们将testStruct的TestField字段的编码器设置为我们的testCodec,最后将其与几个别名字符串进行了绑定。得到的效果就是,这个结构体序列化输出时,TestField的内容会添加上"TestPrefix"前缀;而反序列化时,TestField的别名都将映射成这个字段

  • CreateDecoder

  • CreateEncoder

CreateDecoder和CreateEncoder分别用来创建某个数据类型对应的解码器/编码器

type wrapCodec struct{
    encodeFunc  func(ptr unsafe.Pointer, stream *jsoniter.Stream)
    isEmptyFunc func(ptr unsafe.Pointer) bool
    decodeFunc func(ptr unsafe.Pointer, iter *jsoniter.Iterator)
}

func (codec *wrapCodec) Encode(ptr unsafe.Pointer, stream *jsoniter.Stream) {
    codec.encodeFunc(ptr, stream)
}

func (codec *wrapCodec) IsEmpty(ptr unsafe.Pointer) bool {
    if codec.isEmptyFunc == nil {
        return false
    }

    return codec.isEmptyFunc(ptr)
}

func (codec *wrapCodec) Decode(ptr unsafe.Pointer, iter *jsoniter.Iterator) {
    codec.decodeFunc(ptr, iter)
}

type sampleExtension struct {
    jsoniter.DummyExtension
}

func (e *sampleExtension) CreateDecoder(typ reflect2.Type) jsoniter.ValDecoder {
    if typ.Kind() == reflect.Int {
        return &wrapCodec{
            decodeFunc:func(ptr unsafe.Pointer, iter *jsoniter.Iterator) {
                i := iter.ReadInt()
                *(*int)(ptr) = i - 1000
            },
        }
    }

    return nil
}

func (e *sampleExtension) CreateEncoder(typ reflect2.Type) jsoniter.ValEncoder {
    if typ.Kind() == reflect.Int {
        return &wrapCodec{
            encodeFunc:func(ptr unsafe.Pointer, stream *jsoniter.Stream) {
                stream.WriteInt(*(*int)(ptr) + 1000)
            },
            isEmptyFunc:nil,
        }
    }

    return nil
}

func extensionTest(){
    i := 20000
    jsoniter.RegisterExtension(&sampleExtension{})
    s, _ := jsoniter.MarshalToString(i)
    fmt.Println(s)
    // Output:
    // 21000

    jsoniter.UnmarshalFromString(`30000`, &i)
    fmt.Println(i)
    // Output:
    // 29000
}

上面的例子我们用wrapCodec实现了ValEncoder和ValDecoder,然后我们注册了一个Extension,这个Extension的CreateEncoder函数中设置了wrapCodec的Encode函数,指定对于Int类型的数值+1000后输出;CreateDecoder函数中设置了wrapCodec的Decode函数,指定读取了Int类型的数值后,-1000再进行赋值。这里要注意的是,不管是CreateEncoder还是CreateDecoder函数,我们都通过其typ参数限定了这个编码/解码器只对Int类型生效

  • CreateMapKeyDecoder

  • CreateMapKeyEncoder

CreateMapKeyDecoder和CreateMapKeyEncoder跟上面的CreateDecoder和CreateEncoder用法差不多,只不过他们的生效对象是map类型的key的,这里不再举例详述了。

  • DecorateDecoder

  • DecorateEncoder

DecorateDecoder和DecorateEncoder可以用于装饰现有的ValEncoder和ValEncoder。考虑这么一个例子,在上述的CreateDecoder和CreateEncoder的说明中所举例的基础上,我们想再做一层扩展。当我们遇到数字字符串时,我们希望也可以解析成整形数,并且要复用基础例子中的解码器,这时候我们就需要用到装饰器。

type decorateExtension struct{
    jsoniter.DummyExtension
}

type decorateCodec struct{
    originDecoder jsoniter.ValDecoder
}

func (codec *decorateCodec) Decode(ptr unsafe.Pointer, iter *jsoniter.Iterator) {
    if iter.WhatIsNext() == jsoniter.StringValue {
        str := iter.ReadString()
        if _, err := strconv.Atoi(str); err == nil{
            newIter := iter.Pool().BorrowIterator([]byte(str))
            defer iter.Pool().ReturnIterator(newIter)
            codec.originDecoder.Decode(ptr, newIter)
        }else{
            codec.originDecoder.Decode(ptr, iter)
        }
    } else {
        codec.originDecoder.Decode(ptr, iter)
    }
}

func (e *decorateExtension) DecorateDecoder(typ reflect2.Type, decoder jsoniter.ValDecoder) jsoniter.ValDecoder{
    if typ.Kind() == reflect.Int {
        return &decorateCodec{decoder}
    }

    return nil
}

func extensionTest(){
    var i int
    jsoniter.RegisterExtension(&sampleExtension{})
    jsoniter.RegisterExtension(&decorateExtension{})

    jsoniter.UnmarshalFromString(`30000`, &i)
    fmt.Println(i)
    // Output:
    // 29000

    jsoniter.UnmarshalFromString(`"40000"`, &i)
    fmt.Println(i)
    // Output:
    // 39000
}

在CreateDecoder和CreateEncoder的例子基础上,我们在注册一个Extension,这个Extension只实现了装饰器功能,它兼容字符串类型数字的解析,并且解析出来的数字依然要-1000再赋值

作用域

json-iterator有两个RegisterExtension接口可以调用,一个是package级别的jsoniter.RegisterExtension,一个是API(说明见Config章节)级别的API.RegisterExtension。这两个函数都可以用来注册扩展,但是两种注册方式注册的扩展的作用域略有不同。jsoniter.RegisterExtension注册的扩展,对于所有Config生成的API都生效;而API.RegisterExtension只对其对应的Config生成的API接口生效,这个需要注意