Presto源碼分析之數(shù)據(jù)類型

Presto作為一個計算引擎蹄皱,除了支持一些常見的數(shù)字捧灰、字符串類型的數(shù)據(jù),還支持一些別的系統(tǒng)里面比較少見的自定義的 IpAddress, Geometry 等等高級類型屁使,今天來分析一下這些好玩的數(shù)據(jù)類型减细。

整數(shù)類型

tinyint, smallint, integer, bigint 是幾種整數(shù)類型,但是跟通常的數(shù)據(jù)庫不一樣的是赢笨,Presto里面的數(shù)據(jù)都是 signed 類型未蝌,下面是這些數(shù)據(jù)類型的一個基本信息:

類型 類型Size(Byte) 最大值 最小值
tinyint 1 -128 127
smallint 2 -32768 32767
integer 4 -2147483648 -2147483647
bigint 8 -9223372036854775808 9223372036854775807

Boolean

Boolean在底層是用 Byte 來表示的驮吱, 1代表true, 0代表false。

浮點數(shù)

在一般的編程語言里面浮點數(shù)會有兩種類型: floatdouble , 在Presto里面對應的是 realdouble, real 其實就是 float, 只是用了一個更專業(yè)化的名字萧吠。

real 在Presto里面是用一個int來表示的:

    @Override
    public Object getObjectValue(ConnectorSession session, Block block, int position)
    {
        if (block.isNull(position)) {
            return null;
        }
        return intBitsToFloat(block.getInt(position, 0)); // 看這里: block.getInt()
    }

需要獲取實際的值的時候才會用 Float.intBitsToFloat 來進行轉換左冬,為什么可以用一個int來表示一個float? 因為它們在內存表示的時候都是用的4個字節(jié)來表示的,占用的存儲空間是一樣的纸型。

那為什么不直接用 float 自己來表示呢? 看下代碼我們會發(fā)現(xiàn)Presto的 Block 類里面只有針對整數(shù)的方法, 沒有浮點數(shù):

Block里面所有的Getters

浮點數(shù)的不精確性

這里稍微展開一下拇砰,我們知道浮點數(shù)跟定點數(shù)不一樣的是,它們無法精確無損的表達所有的數(shù)狰腌。以float為例除破,根據(jù) IEEE-754 它在內存里面的表示方法是:

Float的表示

我們代碼里面寫的十進制的數(shù)字在內存里面實際是用如上的二進制表示的, 它一共分為三段:

  • 第一位是符號位,用來表示這個數(shù)字的正負琼腔。
  • 第二到九位瑰枫,用來表示指數(shù)(exponent)。
  • 剩下的23位丹莲,用來表示有效數(shù)字(Significant Figures)光坝。

把上面的二進制表示換算成十進制的公式如下:

Float二進制轉十進制

由于各種原因,10進制的整數(shù)和小數(shù)用二進制的float都可能無法準確表示甥材,首先來看看整數(shù)盯另,整數(shù)在理論上都是可以無損的轉換成二進制的,但是由于Float一共只有32位洲赵,其中只有23位用來表示有效數(shù)字(Significant Figures), 因此即使一個很小的數(shù)用Float都可能無法無損表示鸳惯,比如: 20014999 , 它的完整二進制表示應該是:

0 10010111 1001100010110011110010111 (33位)

但是由于float一共只有32位,最后幾位被截斷了板鬓,實際的二進制表示是:

0 10010111 00110001011001111001100 (31位)

那自然就會有精度丟失了悲敷,這就解釋了雖然 2001499 不是一個很大的數(shù),而且是一個整數(shù)俭令,但是用float無法精確表示后德。

類似的,10進制的小數(shù)在理論上就不一定 能用二進制完全表示抄腔,比如 0.9 用二進制表示是:

1100 1100 1100 1100 ... (1100一直重復)

由于有效位數(shù)是無窮大的(因為在無限循環(huán))瓢湃,不管你精度是多少都無法無損的表示0.9這個數(shù)。

Double類型跟Float類型有類似的特點和類似的問題赫蛇。它使用52位來表示有效數(shù)字 (float是23位) 绵患,因此它的精度更高;它有11位(float是8位)來表示指數(shù)(exponent)悟耘,因此它能表示的數(shù)字的范圍更大落蝙。

既然浮點數(shù)有這么明顯的精度問題,為什么我們還要用? 原因在于相對于定點數(shù)來說浮點數(shù)以相同的存儲空間可以表示更大范圍的數(shù)字, 比如同樣使用4個字節(jié)來表示,int類型能表示的最大的數(shù)字是 (2 ^ 31 - 1) , 而Float能表示的最大的數(shù)字則是: (2 ? 2 ^ ?23) × (2 ^ 127) 這可大的太多了筏勒,在一些非金融領域使用float, double完全沒問題移迫,但是一旦涉及到金融領域,必須要用定點數(shù)了管行。

定點數(shù) Decimal

Decimal跟普通浮點數(shù)不一樣的是厨埋,它在聲明的時候有兩個關鍵參數(shù): precisionscale:

decimal(3, 1)

這里的 3 是precision, 而 1 是scale,所謂的 precision, 表示這個decimal的數(shù)
字里面一共有多少個digits, 而scale表示的是小數(shù)點后面可以有多少個digits, 比如我
們上面例子里面這個類型小數(shù)點前面最多2個數(shù)字捐顷,小數(shù)點后面最多1個數(shù)字, 也就是說最大值為: 99.9

decimal還有一些其它的聲明形式如下:

decimal // == decimal(10, 0)
decimal(20) // == decimal(20, 0)

Decimal類型在Presto里面是用 BigInteger + (precision, scale) 信息來一起表示的:

    // LongDecimalType.java
    @Override
    public Object getObjectValue(ConnectorSession session, Block block, int position)
    {
        if (block.isNull(position)) {
            return null;
        }
        Slice slice = block.getSlice(position, 0, getFixedSize());
        return new SqlDecimal(decodeUnscaledValue(slice), getPrecision(), getScale());
    }
    
    // SqlDecimal.java
    public final class SqlDecimal
    {
        private final BigInteger unscaledValue;
        private final int precision;
        private final int scale;    

Decimal在Presto里面又分為兩種類型ShortDecimalTypeLongDecimalType, Short的版本最大的Precision是 18, 而Long的版本最大的Precision是 38 荡陷。分兩種類型的主要目的是為了性能,Short版本的性能更好迅涮,而且我們通常也確實使用Short版本的就夠了废赞。這兩種版本是內部實現(xiàn)細節(jié),用戶不需要感知這個逗柴。

Java里面的Decimal -- BigDecimal

定點數(shù)由于完全準確的存儲了數(shù)值蛹头,沒有什么十進制與二進制之間的轉換, 因此可以完全精準的存儲數(shù)據(jù),我們來看看Java的Decimal實現(xiàn): BigDecimal是怎么保存Decimal的數(shù)據(jù)的:

Java里面的BigDecimal

我們可以看到戏溺,BigDecimal 為了優(yōu)化性能和內存占用分了兩種情況對數(shù)據(jù)進行存儲:

  • 不管是哪種情況渣蜗,都通過 precisionscale 兩個字段來保存精度信息
  • 如果數(shù)據(jù)不大(比Long.MAX_VALUE)小,那么它會直接把數(shù)字保存在intCompact里面 (intCompact其實是一個long類型的字段)旷祸。
  • 如果數(shù)據(jù)確實很大耕拷,超過了Long類型的范圍, 它會使用BigInteger類型的 intVal 來保 存scale過后的值。
    • 而BigInteger里面則是通過一個int字段的 signum 和 一個int數(shù)組: mag 來表達托享。

其實我們上面的例子里面舉的這個 bigDecimal 值并不是特別大骚烧,用 double 表示 8個字節(jié)就夠了,而BigDecimal來表示的時候光是一個mag的int數(shù)組就有三個int, 占用了12個byte闰围。因此Decimal類型其實是通過空間的消耗來換取的精度的準確赃绊。

字符串類型

Presto里面支持4種字符串類型: varchar, char, varbinary, json

varchar 是一種可變長的字符串類型, 你可以指定一個可選的最大長度, 比如 varchar 表示這個字段的長度沒有上限(unbounded), 而 varchar(10) 則表示這個字符串最大可以容納10個字符羡榴,但是也可以只容納5個字符碧查,因此一個類型 varchar(5) 的值跟一個varchar(10) 的值是可能相等的。

char 是一種定長的字符串類型校仑,跟 char 類似長度也是可選的, 你如果不寫長度忠售,那么默認長度就是1: char == char(1) 。而如果你指定了長度迄沫,而最終你數(shù)據(jù)的長度又沒有那么長稻扬,那么會在尾部自動填充空格, 比如我們定義了 char(10) 類型的字段,我們填充一個 hello 進去羊瘩,那么最終存儲的值其實是 hello_____ (因為顯示問題泰佳,這里用下劃線代替空格), 因此兩個不同長度類型的 char 的值是絕對不可能相等的盼砍。

varbianry 表示的一種可變長的二進制字符串(binary string), 所謂的 bianry string也是一種string, 跟普通的string的區(qū)別在于普通的string是character string, 也就是說字符串里面的元素不一樣: 一個是 byte, 一個是 char惋鹅。 Presto里面的varbinary目前不接受最大長度的參數(shù)啤咽,也就是說所有的 varbinary 都是unbounded。

json 類型保存的JSON類型的數(shù)據(jù),可能是簡單類型: string, boolean, 數(shù)字, 也可能是復雜類型比如: JSONObject, JSONArray等等汽绢。

時間類型

時間類型主要有7種: date, time, time with time zone, timestamp,
timestamp with time zone, interval year to month, interval day to second

date 表示的是日期(不帶時分秒部分), Presto 里面是用從 1970-01-01 到現(xiàn)在的天數(shù)來表示的, 從它的實現(xiàn) SqlDate 就可以看出來了:

public final class SqlDate
{
    private final int days;

    // TODO accept long
    public SqlDate(int days)
    {
        this.days = days;
    }
    ...
}

time 表示的是時間(不帶日期部分), Presto內部保存的是從UTC的
1970-01-01T00:00:00 到指定時間的毫秒數(shù),由于時間跟時區(qū)是有關的侧戴,因此計算的時候一定會把當前session的時間傳入加入計算的宁昭。

timestamp 這是 datetime 的結合,既有日期酗宋,也有時間积仗,而且也是從UTC的1970-01-01T00:00:00開始算的,這個 timestamp 字段值的timezone取的是客戶端的TimeZone.

timestamp with time zone 顧名思義, 這個類型的數(shù)據(jù)的值里面是自帶了時區(qū)的, 比如: TIMESTAMP '2001-08-22 03:04:05.321 America/Los_Angeles'蜕猫。

剩下的兩種數(shù)據(jù)類型是 interval 類型的寂曹,表示時間的間隔。這兩種類型貌似是從
Oracle 里面借鑒過來的回右,其中 interval day to second, 表示的是天隆圆、時、分翔烁、秒級別的時間間隔, Presto內部保存的是時間間隔用毫秒來表示的長度渺氧;而 interval year tomonth 表示的這是年、月級別的時間間隔蹬屹,Presto內部保存的月份的數(shù)量侣背。

結構化的數(shù)據(jù)類型

Presto支持三種結構化的數(shù)據(jù)類型: ARRAY, MAP, ROW

ARRAY 很好理解慨默,就是一個數(shù)組贩耐,數(shù)組里面的元素的類型必須一致:

mysql> select ARRAY[1, 2, 3];
+----------------+
| ARRAY[1, 2, 3] |
+----------------+
| [1, 2, 3]      |
+----------------+
1 row in set (0.11 sec)

MAP 表示是一個映射類型,跟JSON不一樣的是厦取,所有的key的類型必須一致潮太,所有value的類型也必須一致。在字面量里面蒜胖,Presto是通過讓用戶指定兩個有序ARRAY: 一個key的ARRAY消别,一個value的ARRAY來表達的:

mysql> select MAP(ARRAY['foo', 'bar', 'hello'], ARRAY[1, 2, 3]);
+---------------------------------------------------+
| MAP(ARRAY['foo', 'bar', 'hello'], ARRAY[1, 2, 3]) |
+---------------------------------------------------+
| {bar=2, foo=1, hello=3}                           |
+---------------------------------------------------+
1 row in set (0.11 sec)

在內存里面的表示,MAP 的內容這是被保存成一個一個的key-value對:

        // MapType.java
        for (int i = 0; i < singleMapBlock.getPositionCount(); i += 2) {
            map.put(
                    keyType.getObjectValue(session, singleMapBlock, i),
                    valueType.getObjectValue(session, singleMapBlock, i + 1)
            );
        }

ROW 表示的是一行記錄台谢,這行記錄的數(shù)據(jù)可以是各種不同的類型寻狂,比如:

mysql> select ROW(1, 2.0);
+-------------+
| ROW(1, 2.0) |
+-------------+
| [1, 2.0]    |
+-------------+
1 row in set (0.32 sec)

IpAddress

IpAddress是一個蠻有意思的類型,它可以表示IPV4和IPV6的IP地址, 你可以通過下面的語句來試試這種類型:

CREATE TABLE foo (
       a VARCHAR, 
       b BIGINT,
       c IPADDRESS
)

IPADDRESS之間可以進行比較, 支持一些操作包括 =, >, '<' 等等, 同時 IPADDRESS和 VARCHAR兩種類型之間可以進行CAST朋沮。比如:

CAST (ipaddress AS VARCHAR)

因為IpAdress內部存儲都是以IPV6的形式來存的(IPV4也會被轉成IPV6), 而IPV6是128位的蛇券,因此從存儲空間占用上來看缀壤,IpAddress類似于BINARY(16)

Geometry

Geometry類型是表示幾何學上的一些信息纠亚,它表達的一一組相關的類型以及一些輔助函數(shù)的幾何塘慕,比如點(Point)、線(LineStrin)蒂胞、多邊形(Polygon)等图呢,用Oracle文檔上的一張圖來看特別直觀:

Geometric Types

Geometry是一類很有意思的數(shù)據(jù),Presto里面提供了大量相關的函數(shù)骗随,比如 ST_Crosses 來判斷兩個幾何圖形是否有交集, 再比如 ST_Equals 來表示兩個圖形表示的是否是同一個圖形等等蛤织。

BingTile

BingTile 也是一個地理位置相關的類型, 它表示的是微軟Bing地圖服務上地圖的一個指定區(qū)域。首先Bing把整個地圖映射到一個平面上面:

二維世界地圖

這樣地球上的任何一個點都可以用一個二維的坐標 (X,Y) 來定位了鸿染。我們平時看地圖的時候經常對地圖進行縮放指蚜,不同程度的縮放對應的地圖的詳細程度是不一樣的,這樣地圖就會有一個縮放因子的參數(shù)(zoomLevel)涨椒。相同的坐標在不通的縮放因子上對應的地理位置是不一樣的摊鸡。

但是通常我們獲取一個坐標沒太大意義,更多的時候我們是要獲取指定的一塊區(qū)域蚕冬,為了高效的獲取一個指定區(qū)域的地圖免猾,Bing把整個地球的地圖分成了很多小份,每一份叫做一個 Tile, 每個 Tile 的大小是 256 x 256(pixel)播瞳。

Bing Tile

這樣每個Tile也有了坐標掸刊,我們指定特定的的縮放程度以及對應的坐標,我們就可以獲得指定區(qū)域的地圖了, 我們看看 Presto 的代碼也可以印證這一點:

public final class BingTile
{
    public static final int MAX_ZOOM_LEVEL = 23;

    private final int x;
    private final int y;
    private final int zoomLevel;
    ...
}

HyperLogLog 和 P4HyperLogLog

HyperLogLog 是一種計算 count-distinct 的近似算法赢乓,我們知道要對數(shù)據(jù)集數(shù)據(jù)進行 count-distinct 的計算忧侧,需要的內存量跟要計算的數(shù)據(jù)量是成正比的,HyperLogLog算法可以對 10 ^ 9 以上的數(shù)據(jù)量進行高效的 count-distinct 計算牌芋,所需要的內存僅為 1.5KB, 而準確性為 98% 左右蚓炬。P4HyerLogLog 表示的也是同一個東西,只是使用的算法稍有差異躺屁。

總結

今天我們分析了Presto里面的所有標準數(shù)據(jù)類型肯夏,除了常見的簡單類型,還有一些高級的自定義類型犀暑,Presto里面添加自定義類型很簡單驯击,后面有機會專門分析一下。

參考文檔

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末耐亏,一起剝皮案震驚了整個濱河市徊都,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌广辰,老刑警劉巖暇矫,帶你破解...
    沈念sama閱讀 218,607評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件主之,死亡現(xiàn)場離奇詭異,居然都是意外死亡李根,警方通過查閱死者的電腦和手機槽奕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來房轿,“玉大人粤攒,你說我怎么就攤上這事〖叫” “怎么了琼讽?”我有些...
    開封第一講書人閱讀 164,960評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長洪唐。 經常有香客問我,道長吼蚁,這世上最難降的妖魔是什么凭需? 我笑而不...
    開封第一講書人閱讀 58,750評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮肝匆,結果婚禮上粒蜈,老公的妹妹穿的比我還像新娘。我一直安慰自己旗国,他們只是感情好枯怖,可當我...
    茶點故事閱讀 67,764評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著能曾,像睡著了一般度硝。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上寿冕,一...
    開封第一講書人閱讀 51,604評論 1 305
  • 那天蕊程,我揣著相機與錄音,去河邊找鬼驼唱。 笑死藻茂,一個胖子當著我的面吹牛,可吹牛的內容都是我干的玫恳。 我是一名探鬼主播辨赐,決...
    沈念sama閱讀 40,347評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼京办!你這毒婦竟也來了掀序?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,253評論 0 276
  • 序言:老撾萬榮一對情侶失蹤臂港,失蹤者是張志新(化名)和其女友劉穎森枪,沒想到半個月后视搏,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 45,702評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡县袱,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,893評論 3 336
  • 正文 我和宋清朗相戀三年浑娜,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片式散。...
    茶點故事閱讀 40,015評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡筋遭,死狀恐怖,靈堂內的尸體忽然破棺而出暴拄,到底是詐尸還是另有隱情漓滔,我是刑警寧澤,帶...
    沈念sama閱讀 35,734評論 5 346
  • 正文 年R本政府宣布乖篷,位于F島的核電站响驴,受9級特大地震影響,放射性物質發(fā)生泄漏撕蔼。R本人自食惡果不足惜豁鲤,卻給世界環(huán)境...
    茶點故事閱讀 41,352評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望鲸沮。 院中可真熱鬧琳骡,春花似錦、人聲如沸讼溺。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,934評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽怒坯。三九已至炫狱,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間敬肚,已是汗流浹背毕荐。 一陣腳步聲響...
    開封第一講書人閱讀 33,052評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留艳馒,地道東北人憎亚。 一個月前我還...
    沈念sama閱讀 48,216評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像弄慰,于是被迫代替她去往敵國和親第美。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,969評論 2 355

推薦閱讀更多精彩內容

  • bit 整型 bit數(shù)據(jù)類型是整型陆爽,其值只能是0什往、1或空值。這種數(shù)據(jù)類型用于存儲只有兩種可能值的數(shù)據(jù)慌闭,如Yes 或...
    mank閱讀 1,267評論 0 2
  • Spark SQL, DataFrames and Datasets Guide Overview SQL Dat...
    Joyyx閱讀 8,328評論 0 16
  • 1别威、MySQL 數(shù)據(jù)類型 MySQL中定義數(shù)據(jù)字段的類型對你數(shù)據(jù)庫的優(yōu)化是非常重要的躯舔。 MySQL支持多種類型,大...
    garvin閱讀 545評論 0 1
  • (稻盛哲學學習會)打卡第19天 姓名:陳靜 部門:分水碶 組別:待定 【知~學習】 閱讀《活法》第二章節(jié) “喜歡”...
    陳靜分水碶閱讀 530評論 0 0
  • 婷子是一個普通的女孩省古,有著普通的外貌粥庄,普通的家境,普通的身材豺妓。但她有不普通的夢想惜互,在她的腦海里有一場夢境。我她...
    yoyo的小天地閱讀 177評論 0 0