Hbase-2.0.0_03_Hbase數據模型

来源:https://www.cnblogs.com/zhanglianghhh/archive/2018/08/20/9506006.html
-Advertisement-
Play Games

1. hbase數據模型 1.1. HBase數據模型術語 Table HBase表由多行組成。 Row HBase中的一行由一個行鍵和一個或多個列組成,列的值與這些列相關聯。存儲行時,按行鍵按字母順序排列。因此,行鍵的設計非常重要。目標是以這樣一種方式存儲數據,即相關的行彼此接近。常見的行鍵模式是 ...


 

1. hbase數據模型

 

1.1. HBase數據模型術語

Table

       HBase表由多行組成。

 

Row

       HBase中的一行由一個行鍵和一個或多個列組成,列的值與這些列相關聯。存儲行時,按行鍵按字母順序排列。因此,行鍵的設計非常重要。目標是以這樣一種方式存儲數據,即相關的行彼此接近。常見的行鍵模式是網站域。如果您的行鍵是域,您可能應該反向存儲它們(org.apache.www, org.apache.mail, org.apache.jira)。這樣,所有Apache域都在表中彼此接近,而不是基於子域的第一個字母展開。

 

Column

       HBase中的列由列族和列限定符組成,列限定符由:(冒號)字元分隔。

 

Column Family

       列族物理地混合了一組列及其值,通常是出於性能原因。每個列族都有一組存儲屬性,比如它的值是否應該緩存在記憶體中,它的數據是如何壓縮的,或者它的行鍵是如何編碼的,等等。表中的每一行都有相同的列族,儘管給定的行可能不會在給定的列族中存儲任何內容。

 

Column Qualifier

       列限定符被添加到列族中,以提供給定數據塊的索引。給定一個列族內容,一個列限定符可能是content:html,另一個可能是content:pdf。雖然列族在創建表時是固定的,但是列限定符是可變的,並且在行之間可能會有很大的不同。

 

Cell

       單元格是行、列族和列限定符的組合,並且包含一個值和時間戳,其中時間戳表示值(value)的版本。

 

Timestamp

       時間戳與每個值一起寫入,是值的給定版本標識符。預設情況下,時間戳表示寫入數據時RegionServer上的時間,但在將數據放入單元格時可以指定不同的時間戳值。

 

1.2. 概念視圖

       下麵的例子是BigTable論文第2頁的一個稍微修改過的形式。有一個名為webtable的表,它包含兩行(com.cn .www和com.example.www)和三個名為contents、anchor和people的列族。在本例中,對於第一行(com.cn.www), anchor包含兩列(anchor:cssnsi.com, anchor:my.look.ca), contents包含一列(contents:html)。這個示例包含帶有row key com.cn.www的行的5個版本,以及帶有row key com.example.www的行的一個版本。contents:html列限定符包含給定網站的全部html。anchor列族的限定詞每個都包含外部站點,該站點鏈接到由行表示的站點,以及在其鏈接的錨中使用的文本。people列族代表與站點相關的人員。

1 Column Names:
2     按照慣例,列名由列族首碼和限定符組成。例如,contents:html由列族contents和html限定符組成。冒號(:)將列族從列族限定詞中分隔開。

 

Table webtable

Row Key

Time Stamp

ColumnFamily contents

ColumnFamily anchor

ColumnFamily people

"com.cnn.www"

t9

 

anchor:cnnsi.com = "CNN"

 

"com.cnn.www"

t8

 

anchor:my.look.ca = "CNN.com"

 

"com.cnn.www"

t6

contents:html = "<html>…​"

   

"com.cnn.www"

t5

contents:html = "<html>…​"

   

"com.cnn.www"

t3

contents:html = "<html>…​"

   

"com.example.www"

t5

contents:html = "<html>…​"

 

people:author = "John Doe"

 

       表中看起來為空的單元格在HBase中不占用空間,實際上也不存在。這就是HBase“稀疏”的原因。表格視圖並不是查看HBase數據的唯一方法,甚至也不是最精確的方法。以下表示的信息與多維地圖相同。這隻是為了說明目的而做的一個模型,可能並不完全準確。

 1 {
 2   "com.cnn.www": {
 3     contents: {
 4       t6: contents:html: "<html>..."
 5       t5: contents:html: "<html>..."
 6       t3: contents:html: "<html>..."
 7     }
 8     anchor: {
 9       t9: anchor:cnnsi.com = "CNN"
10       t8: anchor:my.look.ca = "CNN.com"
11     }
12     people: {}
13   }
14   "com.example.www": {
15     contents: {
16       t5: contents:html: "<html>..."
17     }
18     anchor: {}
19     people: {
20       t5: people:author: "John Doe"
21     }
22   }
23 }

 

1.3. 物理視圖

       雖然在概念級別的表可以看作是稀疏的行集,但是它們是由列族物理存儲的。一個新的列限定符(column_family:column_qualifier)可以在任何時候添加到現有的列。

 

ColumnFamily anchor

Row Key

Time Stamp

Column Family anchor

"com.cnn.www"

t9

anchor:cnnsi.com = "CNN"

"com.cnn.www"

t8

anchor:my.look.ca = "CNN.com"

 

ColumnFamily contents

Row Key

Time Stamp

ColumnFamily contents:

"com.cnn.www"

t6

contents:html = "<html>…​"

"com.cnn.www"

t5

contents:html = "<html>…​"

"com.cnn.www"

t3

contents:html = "<html>…​"

 

       概念視圖中顯示的空單元格根本不存儲。因此,對content:html列的請求在戳記t8時不會返回任何值。類似地,請求一個anchor:my.look.ca在時間戳t9上的值不會返回任何值。但是,如果沒有提供時間戳,則返回特定列的最新值。由於時間戳是按降序存儲的,所以對於多個版本,最近的版本也是第一個找到的版本。因此,請求一個行為com.cnn.www的所有列的值,如果沒有指定時間戳那麼為:contents:html的值來自時間戳t6,anchor:cnnsi.com的值來自時間戳t9,anchor:my.look.ca的值來自時間戳t8。

 

1.4. Table

       表在模式定義時預先聲明。

 

1.5. Row

       行鍵是未解釋的位元組。行按字典順序排序,表中第一個出現的順序是最低的。空位元組數組用於表示表名稱空間的開始和結束。

 

1.6. Column Family

       Apache HBase中的列被分組為列族。列族的所有列成員具有相同的首碼。例如,列courses:history和courses:math都是列族courses系列的成員。冒號(:)將列族從列族限定詞中分隔開。列族首碼必須由可列印字元組成。限定尾(列族限定符)可以由任意位元組組成。列族必須在模式定義時預先聲明,而列不需要在模式定義時定義,但可以在表啟動並運行時動態添加。

       物理上,所有列族成員都存儲在文件系統中。由於調優和存儲規範是在列族級別執行的,因此建議所有列族成員具有相同的一般訪問模式和大小特征。

 

1.7. Cells

       一個{row, column, version}元組在HBase中確切地指定一個單元格。單元格內容是未解釋的位元組

 

1.8. Time Stamp

       HBASE 中通過rowkey和columns確定的為一個存貯單元稱為cell。每個 cell都保存 著同一份數據的多個版本。版本通過時間戳來索引。時間戳的類型是 64位整型。時間戳可以由HBASE(在數據寫入時自動 )賦值,此時時間戳是精確到毫秒的當前系統時間。時間戳也可以由客戶顯式賦值,如果應用程式要避免數據版本衝突,就必須自己生成具有唯一性的時間戳。每個cell中,不同版本的數據按照時間倒序排序,即最新的數據排在最前面。

       為了避免數據存在過多版本造成的的管理 (包括存貯和索引)負擔,HBASE提供了兩種數據版本回收方式。一是保存數據的最後n個版本,二是保存最近一段時間內的版本(比如最近七天)。用戶可以針對每個列族進行設置。

 

1.9. Versions

       一個{row, column, version}元組在HBase中確切地指定一個單元格。它可以有無限數量的單元格,其中行和列是相同的,但單元格地址僅在版本維度上不同。

 

       雖然行和列鍵表示為位元組,但是使用長整數指定版本。通常如此長的時間包含時間實例,例如java.util.Date.getTime()或System.currentTimeMillis()返回的時間,即當前時間與1970年1月1日午夜之間的差值(以毫秒為單位)。

       HBase版本維度以遞減順序存儲,因此在從存儲文件中讀取數據時,首先找到最近的值。

 

       在HBase中,對於單元版本的語義有很多混淆。特別是:

  • 如果對一個單元格的多次寫入具有相同的版本,那麼只有最後一次寫入是可讀取的。
  • 以非遞增的版本順序編寫單元格是可以的。

 

       下麵我們將描述當前HBase中的版本維度是如何工作的。有關HBase版本的討論,請參閱HBase -2406。在HBase中彎曲時間可以很好地讀取HBase的版本或時間維度。它比這裡提供的更詳細地介紹了版本控制。

 

1.9.1. Specifying the Number of Versions to Store

       為給定列存儲的最大版本數是列模式的一部分,在創建表時指定,或者通過alter命令指定,通過HColumnDescriptor.DEFAULT_VERSIONS。在HBase 0.96之前,預設保留的版本數量為3個,但是在0.96和更新版本中更改為1個。

 

Modify the Maximum Number of Versions for a Column Family

1 這個例子使用HBase Shell來保持列族f1中所有列的最多5個版本。還可以使用HColumnDescriptor。
2 hbase> alter 't1', NAME => 'f1', VERSIONS => 5

 

示例:

修改表user的info1列族VERSIONS信息

hbase(main):010:0> describe 'user'
Table user is ENABLED    
user                     
COLUMN FAMILIES DESCRIPTION
{NAME => 'info1', VERSIONS => '1', EVICT_BLOCKS_ON_CLOSE => 'false', NEW_VERSION_BEHAVIOR => 'false', KEEP_DELETED_CELLS => 'FALSE', CACHE_DATA_ON_WRITE => 'false', DATA_BLOCK_ENCODING => 'NONE', TTL => 'FOREVER', MIN_VERSIONS => '0', REPLICATION_SCOPE => '0', BLOOMFILTER => 'ROW', CACHE_INDEX_ON_WRITE => 'false', IN_MEMORY => 'false', CACHE_BLOOMS_ON_WRITE => 'false', PREFETCH_BLOCKS_ON_OPEN => 'false', COMPRESSION => 'NONE', BLOCKCACHE => 'true', BLOCKSIZE => '65536'}                                                                                                   
{NAME => 'info2', VERSIONS => '1', EVICT_BLOCKS_ON_CLOSE => 'false', NEW_VERSION_BEHAVIOR => 'false', KEEP_DELETED_CELLS => 'FALSE', CACHE_DATA_ON_WRITE => 'false', DATA_BLOCK_ENCODING => 'NONE', TTL => 'FOREVER', MIN_VERSIONS => '0', REPLICATION_SCOPE => '0', BLOOMFILTER => 'ROW', CACHE_INDEX_ON_WRITE => 'false', IN_MEMORY => 'false', CACHE_BLOOMS_ON_WRITE => 'false', PREFETCH_BLOCKS_ON_OPEN => 'false', COMPRESSION => 'NONE', BLOCKCACHE => 'true', BLOCKSIZE => '65536'}                                                                                                   
{NAME => 'info3', VERSIONS => '1', EVICT_BLOCKS_ON_CLOSE => 'false', NEW_VERSION_BEHAVIOR => 'false', KEEP_DELETED_CELLS => 'FALSE', CACHE_DATA_ON_WRITE => 'false', DATA_BLOCK_ENCODING => 'NONE', TTL => 'FOREVER', MIN_VERSIONS => '0', REPLICATION_SCOPE => '0', BLOOMFILTER => 'ROW', CACHE_INDEX_ON_WRITE => 'false', IN_MEMORY => 'false', CACHE_BLOOMS_ON_WRITE => 'false', PREFETCH_BLOCKS_ON_OPEN => 'false', COMPRESSION => 'NONE', BLOCKCACHE => 'true', BLOCKSIZE => '65536'} 
3 row(s)
Took 0.1262 seconds    
hbase(main):014:0* alter 'user' ,NAME => 'info1', VERSIONS => 5   # 註意:大小寫敏感
Updating all regions with the new schema...
1/1 regions updated.
Done.
Took 2.3592 seconds      
hbase(main):015:0> describe 'user'
Table user is ENABLED  
user                   
COLUMN FAMILIES DESCRIPTION  
{NAME => 'info1', VERSIONS => '5', EVICT_BLOCKS_ON_CLOSE => 'false', NEW_VERSION_BEHAVIOR => 'false', KEEP_DELETED_CELLS => 'FALSE', CACHE_DATA_ON_WRITE => 'false', DATA_BLOCK_ENCODING => 'NONE', TTL => 'FOREVER', MIN_VERSIONS => '0', REPLICATION_SCOPE => '0', BLOOMFILTER => 'ROW', CACHE_INDEX_ON_WRITE => 'false', IN_MEMORY => 'false', CACHE_BLOOMS_ON_WRITE => 'false', PREFETCH_BLOCKS_ON_OPEN => 'false', COMPRESSION => 'NONE', BLOCKCACHE => 'true', BLOCKSIZE => '65536'}                                                                                                   
{NAME => 'info2', VERSIONS => '1', EVICT_BLOCKS_ON_CLOSE => 'false', NEW_VERSION_BEHAVIOR => 'false', KEEP_DELETED_CELLS => 'FALSE', CACHE_DATA_ON_WRITE => 'false', DATA_BLOCK_ENCODING => 'NONE', TTL => 'FOREVER', MIN_VERSIONS => '0', REPLICATION_SCOPE => '0', BLOOMFILTER => 'ROW', CACHE_INDEX_ON_WRITE => 'false', IN_MEMORY => 'false', CACHE_BLOOMS_ON_WRITE => 'false', PREFETCH_BLOCKS_ON_OPEN => 'false', COMPRESSION => 'NONE', BLOCKCACHE => 'true', BLOCKSIZE => '65536'}                                                                                                   
{NAME => 'info3', VERSIONS => '1', EVICT_BLOCKS_ON_CLOSE => 'false', NEW_VERSION_BEHAVIOR => 'false', KEEP_DELETED_CELLS => 'FALSE', CACHE_DATA_ON_WRITE => 'false', DATA_BLOCK_ENCODING => 'NONE', TTL => 'FOREVER', MIN_VERSIONS => '0', REPLICATION_SCOPE => '0', BLOOMFILTER => 'ROW', CACHE_INDEX_ON_WRITE => 'false', IN_MEMORY => 'false', CACHE_BLOOMS_ON_WRITE => 'false', PREFETCH_BLOCKS_ON_OPEN => 'false', COMPRESSION => 'NONE', BLOCKCACHE => 'true', BLOCKSIZE => '65536'} 
3 row(s)
Took 0.0814 seconds     

  

添加數據並查看結果

 1 hbase(main):019:0> put 'user','1234','info1:name','zhang' 
 2 hbase(main):019:0> put 'user','1234','info1:name','zhang1' 
 3 hbase(main):019:0> put 'user','1234','info1:name','zhangsan' 
 4 hbase(main):019:0> put 'user','1234','info1:name','zhangsan2'
 5 hbase(main):019:0> put 'user','1234','info1:name','lisi'
 6 hbase(main):019:0> put 'user','1234','info1:name','zhaoliu'
 7 ### 產看結果
 8 hbase(main):042:0> get 'user','1234',{COLUMN=>'info1',VERSIONS=>5}  # 註意:即使VERSIONS=>6,也只顯示5個,因為VERSIONS => '5'
 9 ### 也可以指定列  get 'user','1234',{COLUMN=>'info1:name',VERSIONS=>5} 
10 COLUMN                                           CELL                                      
11  info1:name                                      timestamp=1534221416212, value=zhaoliu    
12  info1:name                                      timestamp=1534217862282, value=lisi       
13  info1:name                                      timestamp=1534217857274, value=zhangsan2  
14  info1:name                                      timestamp=1534217598847, value=zhangsan   
15  info1:name                                      timestamp=1534145642332, value=zhang1     
16 1 row(s)
17 Took 0.0215 seconds         

 

Modify the Minimum Number of Versions for a Column Family

1 你還可以指定每個列族存儲的版本的最小數量。預設情況下,該值被設置為0,這意味著該特性被禁用。下麵的示例通過HBase Shell將列族f1中所有列的最低版本數設置為2。你也可以使用HColumnDescriptor。
2 hbase> alter 't1', NAME => 'f1', MIN_VERSIONS => 2

 

       從HBase 0.98.2開始,可以通過設置hbase.column.max.version為所有新創建的列保持最大版本數,指定全局預設值在hbase-site.xml中配置。參見hbase.column.max.version。

 

1.9.2. Delete

       有三種不同類型的內部刪除標記。

  • Delete:一個列的指定版本
  • Delete column:一個列的所有版本
  • Delete family: 用於特定ColumnFamily的所有列

 

       當刪除整個行時,HBase將在內部為每個ColumnFamily創建一個墓碑(而不是每一列)。

 

       通過創建墓碑標記刪除工作。例如,假設我們要刪除一行。為此,您可以指定一個版本,或者預設使用currentTimeMillis。這意味著刪除所有版本小於或等於這個版本的單元格。HBase從不在修改數據,因此例如delete不會立即刪除(或標記為已刪除)與delete條件對應的存儲文件中的條目。相反,會寫一個所謂的墓碑,將會掩蓋刪除的值。當HBase進行一次大的壓實時,將對墓碑進行處理,實際地除去那些死值,以及墓碑本身。如果刪除一行時指定的版本大於行中任何值的版本,則你可以認為刪除完整的行。

 

1.9.3. Major compactions change query results

       在t1、t2和t3上創建三個單元格版本,最大版本設置為2。因此,在獲得所有版本時,只返回t2和t3處的值。但是如果刪除t2或t3的版本,t1的版本將再次出現。很明顯,一旦一個重要的壓縮運行,這樣的行為將不再是這樣了…(參見HBase中彎曲時間的垃圾收集)。

 

1.10. Sort Order

       所有數據模型操作HBase都以排序的順序返回數據。首先是row,然後是ColumnFamily,然後是column qualifier,最後是timestamp(反向排序,所以首先返回最新的記錄)。

 

1.11. Column Metadata

       ColumnFamily實例沒有存儲的列元數據之外的內部KeyValue信息。因此,雖然HBase可以支持每行有大數量的列,但是多行之間的列差異,是你的責任去保持跟蹤列名。

       獲得一個ColumnFamily的完整列集的唯一方法是處理所有的行。有關HBase如何在內部存儲數據的更多信息,請參閱keyvalue。

 

1.12. Joins

       HBase是否支持連接是列表中常見的問題,答案很簡單:它不支持連接,至少不支持RDBMS連接的方式(例如,SQL中的等連接或外連接)。如本章所示,HBase中的讀取數據模型操作是Get和Scan。

       然而,這並不意味著應用程式中不支持等效連接功能,但是您必須自己完成。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一.概述 mysql可以在多個平臺上運行,在windows平臺上安裝有noinstall包和圖形化包二種方式。在linux/unix平臺上有RPM包安裝,二進位包(Binary Package)安裝,源碼包(Source package)安裝。 對於RPM包的最大優點是安裝簡單,適合初學者學習使用, ...
  • with cte as ( select belongsAgent from [QPProxyDB].[dbo].[BS_ProxyInfo] where ProxyID = @ProxyID union all select a.ProxyID from [QPProxyDB].[dbo].[BS... ...
  • 一、表操作 1、創建表 1 是否可空,null表示空,非字元串 2 not null - 不可空 3 null - 可空 1 預設值,創建列時可以指定預設值,當插入數據時如果未主動設置,則自動添加預設值 2 create table tb1( 3 nid int not null defalut 2 ...
  • 問題背景 在開發項目過程中,客戶要求使用gbase8s資料庫(基於informix),簡單的分頁頁面響應很慢。排查發現分頁sql是先查詢出數據在外面套一層後再取多少條,如果去掉嵌套的一層,直接獲取則很快。日常使用中postgresql並沒有這樣的操作也很快,這是為什麼呢? 說明 在資料庫實現早期,查 ...
  • 執行計劃路徑選擇 postgresql查詢規划過程中,查詢請求的不同執行方案是通過建立不同的路徑來表達的,在生成許多符合條件的路徑之後,要從中選擇出代價最小的路徑,把它轉化為一個計劃,傳遞給執行器執行,規劃器的核心工作就是生成多條路徑,然後從中找出最優的那一條。 代價評估 評估路徑優劣的依據是用系統 ...
  • 問題背景 運維操作失誤,在沒有正常關閉sqlserver的情況下,將伺服器關閉了,重啟後某些表損壞(應該是某些頁損壞了,沒有損壞的頁還能訪問到數據,但是訪問損壞了的頁就有問題),目前資料庫只有4.20號的備份。 報錯信息 查詢腳本:select * from t_jxjs_pctq where c_ ...
  • json,jsonb區別 json和jsonb,而兩者唯一的區別在於效率,json是對輸入的完整拷貝,使用時再去解析,所以它會保留輸入的空格,重覆鍵以及順序等。而jsonb是解析輸入後保存的二進位,它在解析時會刪除不必要的空格和重覆的鍵,順序和輸入可能也不相同。使用時不用再次解析。兩者對重覆鍵的處理 ...
  • 參考博客:Hadoop HBase概念學習系列 參考博客:Hadoop HBase概念學習系列之HBase里的Zookeeper(二十一) 參考博客:Hadoop HBase概念學習系列之HBase里的客戶端和HBase集群建立連接(詳細)(十四) 參考博客:Hadoop HBase概念學習系列之M ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...