前言
由于项目组里的老大工程能力实在太强,各种技术栈玩得6到飞起,于是我们也跟着开阔了不少视野。业务的一些特性也导致允许我们尝试一些最新的技术来完成工作内容。刚进来的时候接触clojure,这种lisp方言,开始我是抗拒的。具有一定的入门门槛,开始时常常看几个函数处理逻辑,要花个半天的时间。再加上逆天的可扩展性极强的宏特性,老实说我是崩溃的。(不过后期熟练后用起来,写业务接口真的是好简洁的说,,,基于jvm的好处是要啥有啥,,,这里按下不表。)
倘若要谈起编程功底,编码能力啥的,语言这种东西实在是浮于表面,大佬眼中对于“世界上最好的语言”这种争论常常是不屑一顾的,因为什么东西都能在他们手里玩出花儿来。(为了不显得过于低端,当然不能讨论语言啊)
但是,最近开始用kdb+,搭配的Q 语言,实在是把三观毁得天崩地裂,以前还觉得clojure非常难懂,现在看来,真是年轻人啊,又傻又胖又稚嫩。。。
说下业务场景:
现在有一张业务明细表,字段若干(100左右),量级百万左右,经常需要查看某条ID的明细数据,某个列的透视情况,多个列的透视情况等等,页面上需要多个列的组合group显示等等(其实就是excel中的各种透视操作)。这种数据量级上事实上mysql就能够解决,但是因为页面上可视化拖拽会导致拼出的sql比较复杂,且对字段优化不佳的情况下也会导致查询时间偏长。这种对于列的频繁操作的场景,再加上量级不大,老大拍板说,那就玩下kdb+吧~
折腾这东西期间各种颠覆三观,,觉得还是有必要撕破伪装的假面,诚实的回到一个低端程序员,来讨论下语言。。。
KDB+ 简介
kdb+ 号称 最强的内存数据库之一。列式存储的特性,使得对于某个列的统计分析操作异常方便。全球顶尖的投行,高盛,摩根,国内的国信等证券公司也开始使用,在延迟性上有着苛刻要求的金融领域,kdb+可谓一家独大。当然在优秀的性能背后,超高的费用也是必不可少的,貌似单核价格在3w+美刀以上?...
kdb+:
- 单体架构,轻松支持 billion以上数据
- 分布式扩展,无性能损耗
- 超低延迟+高并发支持
- 列式存储+内存数据库
- 灵活的Q语言,内置非常多的统计计算方法。
等等等。。。
当然官方也提供了32位免费版,单进程支持最大4G内存。实用中80万数据,100+列,在无任何优化的情况下,内存占用大概是200+M。(kdb+还会对数据进行压缩等)所以单进程支持500~1000万数据应该不是大问题。
在个人研究一些历史股票数据时,利用q/kdb+来跑一些简单的量化策略,还是非常方便的。
Q语言 基础
Q 数据类型
Q语言有的数据类型,跟其他语言不相上下,也就是基本的int long,double,string。但是在表现形式上,两者有着巨大的生殖隔离...
基本数据类型
Q语言 使用 数字+字符 来表示一个数据类型。基础的有如下几个数据类型(只列出一些基础的数据类型,详细可查官方reference)
数字 | 字符 | 占用字节大小 | q数据类型 | 对应java数据类型 | ||
---|---|---|---|---|---|---|
0 | l | list | ||||
1 | b | 1 | boolean | Boolean | ||
4 | x | 1 | byte | Byte | ||
5 | h | 2 | short | Short | ||
6 | i | 4 | int | Integer | ||
7 | j | 8 | long | Long | ||
8 | e | 4 | real | Float | ||
9 | f | 8 | float | Double | ||
10 | c | 1 | char | Charater | ||
11 | s | symbol | String | |||
14 | d | 4 | date | Date | ||
15 | z | 8 | datetime | TimeStamp | ||
16 | n | 8 | timespan | TimeSpan | ||
19 | t | 4 | time | Time |
a:6 /定义 a = 6 (q中数字默认定义为Long类型)
type a
\表示注释开始
输出 -7h 。负号表示这是a 变量是一个atom值,表示只有一个值,跟 list类型相反。
查表 ,7表示为long类型,h 表示type的输出类型 是short,查表,h对应类型为short。
注释结束/
b:1.2
type b / 输出 -9h 。即b是一个atom值,值类型为我们通常用的Double形
想要定义特定类型的数据,也非常的简单,只需在 值前面加上 对应的 类型字符+"$"。
int2:`i$2
type int2 /输出 -6h。
char 与symbol:
q语言中symbol就是我们常理解 string类型,用 ` 开始。
char类型则用 双引号 "" 表示。(有点反常理的样子....)
c:"h"
cs:"hello world"
type c / 输出 -10h。 表示这个是单个的值,值的类型为char
type cs / 输出 10h,正号表示这是个list,且是个char类型的list。
假如想要定义一个有多个字符的字符串(而不是char list),就得使用 symbol
s:`hello /type s 输出 -11h。 表示为单个的string。
space: `$("hello world") / string类型中包含空格会解析错误,得使用类型转换将 char list转为 symbol....
date 日期时间等
q语言中针对日期时间操作非常简单方便且强大。(kdb+本意也是为股票证券等时序数据而准备的,毕竟每毫秒钟就几千万上下...)
日期类型一般限制与 1709.01.01 ~ 2290.12.31 任何日期操作的结果值则不会有这个限制...
日期格式在kdb+存储为 yyyy.mm.dd形式,日期值以2000.01.01为相对"坐标"。即任何一个date形式的值,都可以转为int类型,其值的结果就是相对于 2000.01.01的相对天数
dt:2000.01.02
`int $ dt / 输出 1,表示与2000.01.01相差为1天。在此日期之前,则值为负数
time 类型就是一天中的时分秒毫秒。time类型在kdb+表示为32位的int,表示相对于今天0点0分0毫秒的相对值。最小单位为毫秒。因此,int 和time类型也能够轻松转换,时间操作上也轻松方便。
mtime: `time $ 60000
/以毫秒为最小单位,则mtime表示 第一个分钟。mtime = 00:01:00.000
mtime - 30000 / 00:00:30.000 时间可以直接 + - * %
mtime * 2 / 00:02:00.000
当然datetime 形式就是 date+time合并在一起。
q语言中还提供了 分钟秒等类型
list
q中的list 与python的list概念上并没有太大的差别,都可以认为是多个元素的集合体。支持多重嵌套
q语言中生成 list主要由几种方法
ali: 1 2 3 4 /直接赋值,ali 为 list,list中元素为1,2,3,4
ali:(1 2 3 4)/ 同上
ali: enlist 1 2 3 4 / 同上
/其他的如 # take操作符等等。
dict 字典
dict 字典也是一种常见的数据类型,key,value格式存储,支持多重嵌套
dict:`a`b`c!1 2 3 / 使用感叹号!定义一个字典 ,即{"a":1,"b":2,"c":3}
不太相同的是,q中允许一个字典包含相同的key值,但使用key 查找时,只会命中第一个出现该key的值。
dict:`a`b`a`c!10 20 30 20
[图片上传失败...(image-ddad66-1536989081986)]
查找 key为a 的值时,只会命中第一个key出现的值,更新也只会更新第一个key的值。
删除则会把所有相同的key都删掉.
table
table 是q中的"一等"数据结构。
table 可以认为是字典的集合。将字典转置一下,就可以当成table。
dict:`a`b`c!(1 2 3; 1 2 3;1 2 3)
a| 1 2 3
b| 1 2 3
c| 1 2 3
/dict 的key,a b c 的值都为 1 2 3。
/将dict 的key value 九十度转换一下,就变成了table。dict 的key 变成了table 的header,dict 的value变成了table每行的值。
table:flip dict
a b c
-----
1 1 1
2 2 2
3 3 3
table 的每一列就是一个key value格式的字典。因此也认为table是列式的。
table `a /取 a 列的所有值.
1 2 3
table[0] /取table的第一行。返回一个字典。
a| 1
b| 1
c| 1
keyed /unkeyed
table 除了可以通过 转置一下字典获得外,还能使用table的标准定义
unkey:([]name:`a`b`c; val:1 2 3)
name val
--------
a 1
b 2
c 3
keyed:([name:`a`b`c] val:1 2 3)
name| val
----| ---
a | 1
b | 2
c | 3
/keyed 与unkeyed的展示上区别是 name 和val 之间多了一个竖条分隔。
table 分为 keyed 和unkeyed。在定义上的区别是,中括号[] 中是否有定义列。
unkeyd table 就是 list,list里面的值为为一个字典。如 (d1 d2 d3 d4)。d1 到d4 是四个不同的字典,字典的key充当table的列名,字典的每个值分别对应table同一行的值。
unkey table 可以通过 对列名获取整个列的值。也可以利用index下标(因为是一个list),如,table[0],返回一个字典,也即{"d1":d1_val0,"d2",d2_val0,"d3":d3_val0,"d4":d4_val0}
keyed table与unkey table 不同,keyed table 其实是存成了一个字典,字典的key 为中括号[] 定义的值。因此也与字典一样,允许拥有相同的key值,但只会针对第一个出现的key值做操作。
因为 keyed table存成了字典,只能通过[]中的key进行获取,无法通过列名称获取整列的值。这里的[]中定义的值有点像mysql中的主键,每次获取一行的信息通过主键获取。(不过这里允许主键重复)
unkey `val /操作合法,取val 这一列的值。返回一个list
unkey[0] /操作合法。取表的第一行数据,返回一个dict
name| `a
val | 1
unkey `val /操作不合法。
unkey[0] /操作不合法
keyed `a /操作合法,取表中主键为 a的行记录 返回 val| 1
针对 table的操作,更多时候是使用类sql的方式进行操作,后面会找个时间记录下这部分
数据基本操作
Q 语言基本操作符
operator | desc |
---|---|
+ - * % | 加 减 乘 除 |
& | | &(最大) |(最小) |
= <> | 相等/不等 ,一个一个比较,若操作数是list,则每个atom一一比较 |
~ | 比较,与 = 不同,将操作数看成一个整体比较 |
:: | 别名,可看是是一个引用。 |
Q 执行顺序 与操作符优先级
Q语言在执行顺序是从右到左,刚接触的时候,这一点可能会非常的不习惯,再加上本身的是 一种vector language,当参数是标量和参数是向量时,常常会有不一样的效果,特别是操作符有各种前缀,中缀,后缀用法,这也常被人诟病,天书一样的表达方式。
Q语言不存在操作符优先级
重温下三种表达式:前缀表达式(prefix),中缀表达式(infix),后缀表达式(postfix)
下面简单列举两个常见运算,三种式子 是等价的。
中缀表达式 | 前缀表达式 | 后缀表达式 | desc |
---|---|---|---|
A * B + C / D | A B * C D / + | + * A B / C D | A 乘B,C除以D,结果相加 |
A * (B + C) / D | A B C + * D / | / * A + B C D | B加C,乘A,除以D |
其中 中缀表达式(infix),是最符合人类阅读习惯的,操作符之间也是有优先级的。Q语言中为了追求解析速度,抛弃了操作符之间的优先级,这种情况下,解释器不用等到完整得读到整个语句,便可以执行。
例如 A * B + C
由于 Q 语言是从右往左执行的,所以 先读到 B + C ,马上就eval求值,假如要加上操作符优先级的话,得整行读完,
A * B + C,发现 * 优先级 比 + 高,先执行 A * B,再加上C。
去掉操作符优先级的最大好处就是提高了解析速度。金融领域每一个小的速度提升都是非常重要的。
2 * 3 + 4
/输出值为 14. 从右到左执行。无操作符优先级
原子操作符的扩展性
+ - * % mod 等操作时,当操作数为list时,默认会自动将操作符一个一个作用于list中的每个元素。
100+ 1 2 3
/ 1 2 3,为list, 加号 为list每一个元素都加上100,返回 101 102 103
l:(1 2 3; 4 5)
100+l
/l 是一个 嵌套的list,list中第一个list为 1 2 3,第二个list为 4 5。加法嵌套的为每一个list中的元素都加上100.返回结果为(101 102 103;104 105)
比较符 = 与 ~ 的区别.
= 与 ~ 都可以用来 比较两个操作数是否相等。不同的是,当 = 的两边操作数为list时,会输出 一对一的比较结果。而~则将 两边操作数当成一个整体比较。
= | ~ |
---|---|
atomic,原子的单个的进行比较,当操作数为list时,输出list中对应是否相等 | non-atomic,将操作数当成一个整体进行比较 |
操作数两边可以为整数,字符,日期等,两两相互兼容,symbol值则只能与symbol值对比 | 对操作数为类型要求 |
"abc" = "abd" /输出 110b。 1代表比较结果为相等,0则反之。
0 = 2000.01.01 /输出 1b 前面提到过,日期格式以 2000.01.01 为 坐标。因为 2000.01.01 可以与 0 直接比较
1 = 00:00:00.001 /输出 1b, 同理,time类型存成语每天0点的offset值,因此也能与整数进行直接。
42="*" /输出1b, 字符 * 在ascii中存储值为 42,因此与整数 42相等
`b = "b" /执行异常,symbol值也就是string 在使用 = 比较时,两边值类型必须都是symbol类型。
"abc" ~ "abd" /输出 0b, ~ 将两边参数 看成一个整体,此语句将 两个char list进行比较。内容不相同,则返回 0b。
(1 2;3 4)~(1;2 3 4) /输出 0b,两个list 结构不同,
(1; 2 3 4)~(1; (2; 3; 4)) /输出 1b,两个list 相同
相对于 ~ 操作符的不同,下面有几个 atomic的操作符,在list 情况下,同样是一对一的比较。
> 大于
< 小于
>= 大于等于
<= 小于等于
| 取最大值
& 取最小值。
"abc" <= "aef" /输出 100b
12 32 | 3 44 / 输出 12 44
"accg" & "abed" /输出 "abcd",对于两个 char list中,一一比较,取最小值。
Q语言中简单的数据类型和操作规则就先介绍到这,下次介绍下 list,dict 与 function的一些用法与特性~~