presto读取oracle,Presto源码分析之数据类型
Presto作為一個計算引擎,除了支持一些常見的數字、字符串類型的數據,還支持一些別的系統里面比較少見的自定義的 IpAddress, Geometry 等等高級類型,今天來分析一下這些好玩的數據類型。
整數類型
tinyint, smallint, integer, bigint 是幾種整數類型,但是跟通常的數據庫不一樣的是,Presto里面的數據都是 signed 類型,下面是這些數據類型的一個基本信息:類型類型Size(Byte)最大值最小值tinyint1-128127
smallint2-3276832767
integer4-2147483648-2147483647
bigint8-92233720368547758089223372036854775807
Boolean
Boolean在底層是用 Byte 來表示的, 1代表true, 0代表false。
浮點數
在一般的編程語言里面浮點數會有兩種類型: float 和 double , 在Presto里面對應的是 real 和 double, real 其實就是 float, 只是用了一個更專業化的名字。
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個字節來表示的,占用的存儲空間是一樣的。
那為什么不直接用 float 自己來表示呢? 看下代碼我們會發現Presto的 Block 類里面只有針對整數的方法, 沒有浮點數:
Block里面所有的Getters
浮點數的不精確性
這里稍微展開一下,我們知道浮點數跟定點數不一樣的是,它們無法精確無損的表達所有的數。以float為例,根據 IEEE-754 它在內存里面的表示方法是:
Float的表示
我們代碼里面寫的十進制的數字在內存里面實際是用如上的二進制表示的, 它一共分為三段:第一位是符號位,用來表示這個數字的正負。
第二到九位,用來表示指數(exponent)。
剩下的23位,用來表示有效數字(Significant Figures)。
把上面的二進制表示換算成十進制的公式如下:
Float二進制轉十進制
由于各種原因,10進制的整數和小數用二進制的float都可能無法準確表示,首先來看看整數,整數在理論上都是可以無損的轉換成二進制的,但是由于Float一共只有32位,其中只有23位用來表示有效數字(Significant Figures), 因此即使一個很小的數用Float都可能無法無損表示,比如: 20014999 , 它的完整二進制表示應該是:0?10010111?1001100010110011110010111?(33位)
但是由于float一共只有32位,最后幾位被截斷了,實際的二進制表示是:0?10010111?00110001011001111001100?(31位)
那自然就會有精度丟失了,這就解釋了雖然 2001499 不是一個很大的數,而且是一個整數,但是用float無法精確表示。
類似的,10進制的小數在理論上就不一定 能用二進制完全表示,比如 0.9 用二進制表示是:1100?1100?1100?1100?...?(1100一直重復)
由于有效位數是無窮大的(因為在無限循環),不管你精度是多少都無法無損的表示0.9這個數。Double類型跟Float類型有類似的特點和類似的問題。它使用52位來表示有效數字 ?(float是23位) ,因此它的精度更高;它有11位(float是8位)來表示指數(exponent),因此它能表示的數字的范圍更大。
既然浮點數有這么明顯的精度問題,為什么我們還要用? 原因在于相對于定點數來說浮點數以相同的存儲空間可以表示更大范圍的數字, 比如同樣使用4個字節來表示,int類型能表示的最大的數字是 (2 ^ 31 - 1) , 而Float能表示的最大的數字則是: (2 ? 2 ^ ?23) × (2 ^ 127) 這可大的太多了,在一些非金融領域使用float, double完全沒問題,但是一旦涉及到金融領域,必須要用定點數了。
定點數 Decimal
Decimal跟普通浮點數不一樣的是,它在聲明的時候有兩個關鍵參數: precision 和 scale:decimal(3,?1)
這里的 3 是precision, 而 1 是scale,所謂的 precision, 表示這個decimal的數
字里面一共有多少個digits, 而scale表示的是小數點后面可以有多少個digits, 比如我
們上面例子里面這個類型小數點前面最多2個數字,小數點后面最多1個數字, 也就是說最大值為: 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里面又分為兩種類型ShortDecimalType 和 LongDecimalType, Short的版本最大的Precision是 18, 而Long的版本最大的Precision是 38 。分兩種類型的主要目的是為了性能,Short版本的性能更好,而且我們通常也確實使用Short版本的就夠了。這兩種版本是內部實現細節,用戶不需要感知這個。
Java里面的Decimal -- BigDecimal
定點數由于完全準確的存儲了數值,沒有什么十進制與二進制之間的轉換, 因此可以完全精準的存儲數據,我們來看看Java的Decimal實現: BigDecimal是怎么保存Decimal的數據的:
Java里面的BigDecimal
我們可以看到,BigDecimal 為了優化性能和內存占用分了兩種情況對數據進行存儲:不管是哪種情況,都通過 precision 和 scale 兩個字段來保存精度信息
如果數據不大(比Long.MAX_VALUE)小,那么它會直接把數字保存在intCompact里面 ?(intCompact其實是一個long類型的字段)。
如果數據確實很大,超過了Long類型的范圍, 它會使用BigInteger類型的 intVal 來保 ?存scale過后的值。而BigInteger里面則是通過一個int字段的 signum 和 一個int數組: mag 來表達。
其實我們上面的例子里面舉的這個 bigDecimal 值并不是特別大,用 double 表示 8個字節就夠了,而BigDecimal來表示的時候光是一個mag的int數組就有三個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) 。而如果你指定了長度,而最終你數據的長度又沒有那么長,那么會在尾部自動填充空格, 比如我們定義了 char(10) 類型的字段,我們填充一個 hello 進去,那么最終存儲的值其實是 hello_____ (因為顯示問題,這里用下劃線代替空格), 因此兩個不同長度類型的 char 的值是絕對不可能相等的。
varbianry 表示的一種可變長的二進制字符串(binary string), 所謂的 bianry string也是一種string, 跟普通的string的區別在于普通的string是character string, 也就是說字符串里面的元素不一樣: 一個是 byte, 一個是 char。 Presto里面的varbinary目前不接受最大長度的參數,也就是說所有的 varbinary 都是unbounded。
json 類型保存的JSON類型的數據,可能是簡單類型: string, boolean, 數字, 也可能是復雜類型比如: 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 到現在的天數來表示的, 從它的實現 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 到指定時間的毫秒數,由于時間跟時區是有關的,因此計算的時候一定會把當前session的時間傳入加入計算的。
timestamp 這是 date 和 time 的結合,既有日期,也有時間,而且也是從UTC的1970-01-01T00:00:00開始算的,這個 timestamp 字段值的timezone取的是客戶端的TimeZone.
timestamp with time zone 顧名思義, 這個類型的數據的值里面是自帶了時區的, 比如: TIMESTAMP '2001-08-22 03:04:05.321 America/Los_Angeles'。
剩下的兩種數據類型是 interval 類型的,表示時間的間隔。這兩種類型貌似是從
Oracle 里面借鑒過來的,其中 interval day to second, 表示的是天、時、分、秒級別的時間間隔, Presto內部保存的是時間間隔用毫秒來表示的長度;而 interval year tomonth 表示的這是年、月級別的時間間隔,Presto內部保存的月份的數量。
結構化的數據類型
Presto支持三種結構化的數據類型: ARRAY, MAP, ROW。
ARRAY 很好理解,就是一個數組,數組里面的元素的類型必須一致: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?
map.put(
keyType.getObjectValue(session,?singleMapBlock,?i),
valueType.getObjectValue(session,?singleMapBlock,?i?+?1)
);
}
ROW 表示的是一行記錄,這行記錄的數據可以是各種不同的類型,比如: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之間可以進行比較, 支持一些操作包括 =, >, '
因為IpAdress內部存儲都是以IPV6的形式來存的(IPV4也會被轉成IPV6), 而IPV6是128位的,因此從存儲空間占用上來看,IpAddress類似于BINARY(16)。
Geometry
Geometry類型是表示幾何學上的一些信息,它表達的一一組相關的類型以及一些輔助函數的幾何,比如點(Point)、線(LineStrin)、多邊形(Polygon)等,用Oracle文檔上的一張圖來看特別直觀:
Geometric Types
Geometry是一類很有意思的數據,Presto里面提供了大量相關的函數,比如 ST_Crosses 來判斷兩個幾何圖形是否有交集, 再比如 ST_Equals 來表示兩個圖形表示的是否是同一個圖形等等。
BingTile
BingTile 也是一個地理位置相關的類型, 它表示的是微軟Bing地圖服務上地圖的一個指定區域。首先Bing把整個地圖映射到一個平面上面:
二維世界地圖
這樣地球上的任何一個點都可以用一個二維的坐標 (X,Y) 來定位了。我們平時看地圖的時候經常對地圖進行縮放,不同程度的縮放對應的地圖的詳細程度是不一樣的,這樣地圖就會有一個縮放因子的參數(zoomLevel)。相同的坐標在不通的縮放因子上對應的地理位置是不一樣的。
但是通常我們獲取一個坐標沒太大意義,更多的時候我們是要獲取指定的一塊區域,為了高效的獲取一個指定區域的地圖,Bing把整個地球的地圖分成了很多小份,每一份叫做一個 Tile, 每個 Tile 的大小是 256 x 256(pixel)。
Bing Tile
這樣每個Tile也有了坐標,我們指定特定的的縮放程度以及對應的坐標,我們就可以獲得指定區域的地圖了, 我們看看 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 的近似算法,我們知道要對數據集數據進行 count-distinct 的計算,需要的內存量跟要計算的數據量是成正比的,HyperLogLog算法可以對 10 ^ 9 以上的數據量進行高效的 count-distinct 計算,所需要的內存僅為 1.5KB, 而準確性為 98% 左右。P4HyerLogLog 表示的也是同一個東西,只是使用的算法稍有差異。
總結
今天我們分析了Presto里面的所有標準數據類型,除了常見的簡單類型,還有一些高級的自定義類型,Presto里面添加自定義類型很簡單,后面有機會專門分析一下。
作者:xumingmingv
鏈接:https://www.jianshu.com/p/71f523e2688b
與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的presto读取oracle,Presto源码分析之数据类型的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: oracle不开归档对效率会快吗,关于性
- 下一篇: 将一个c 语言源程序文件中所有注释去掉后