Hbase基礎操作Demo(Java版)

一、前置條件

  • HBase服務:【快捷部署】023_HBase(2.3.6)
  • 開發環境:Java(1.8)、Maven(3)、IDE(Idea 或 Eclipse)

HBase-Java代碼示例.png



二、相關代碼

代碼結構如上圖中①和②

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.huawei</groupId><artifactId>HbaseAPI</artifactId><version>1.0-SNAPSHOT</version><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.source>1.8</maven.compiler.source><maven.compiler.target>1.8</maven.compiler.target></properties><repositories><repository><id>huaweicloud2</id><name>huaweicloud2</name><url>https://mirrors.huaweicloud.com/repository/maven/</url></repository><repository><id>huaweicloud1</id><name>huaweicloud1</name><url>https://repo.huaweicloud.com/repository/maven/huaweicloudsdk/</url></repository></repositories><dependencies><dependency><groupId>org.apache.hadoop</groupId><artifactId>hadoop-common</artifactId><version>2.8.3</version></dependency><dependency><groupId>org.apache.hadoop</groupId><artifactId>hadoop-client</artifactId><version>2.8.3</version></dependency><!--hbase--><dependency><groupId>org.apache.hbase</groupId><artifactId>hbase-client</artifactId><version>1.4.13</version></dependency><dependency><groupId>org.apache.hbase</groupId><artifactId>hbase-server</artifactId><version>1.4.13</version></dependency></dependencies><build><finalName>HbaseAPI</finalName><plugins><plugin><artifactId>maven-assembly-plugin</artifactId><configuration><descriptorRefs><descriptorRef>jar-with-dependencies</descriptorRef></descriptorRefs></configuration><executions><execution><id>make-assembly</id><phase>package</phase><goals><goal>assembly</goal></goals></execution></executions></plugin></plugins></build>
</project>

Config4HBaseDemo(公共配置類)
package com.toc.demo.hbase;
/*** 公共配置類* @author cxy@toc* @date  2024-05-07**/public class Config4HBaseDemo {public static String zkQuorum = "127.0.0.1";public static String getZkQuorum(String[] args) {if (args!=null && args.length > 0) {System.out.println("接收參數:" + args[0]);zkQuorum = args[0];}return zkQuorum;}
}

CreateTable(創建Hbase表)
package com.toc.demo.hbase;import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.HColumnDescriptor;
import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.Admin;
import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.ConnectionFactory;import java.io.IOException;/*** 創建Hbase表* @author cxy@toc* @date  2024-05-07**/public class CreateTable {public static void main(String[] args) throws IOException {//鏈接hbaseConfiguration conf = HBaseConfiguration.create();//這里的zookeeper地址要改為自己集群的zookeeper地址conf.set("hbase.zookeeper.quorum",Config4HBaseDemo.getZkQuorum(args));conf.set("hbase.zookeeper.property.clientPort", "2181");Connection connection = ConnectionFactory.createConnection(conf);Admin admin = connection.getAdmin();TableName tableName = TableName.valueOf("users");if (!admin.tableExists(tableName)){//創建表描述器HTableDescriptor htd = new HTableDescriptor(tableName);htd.addFamily(new HColumnDescriptor("f"));admin.createTable(htd);System.out.println(tableName+"表創建成功");}else {System.out.println(tableName+"表已經存在");}}
}

DeleteData(刪除數據)
package com.toc.demo.hbase;import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.util.Bytes;import java.io.IOException;/*** 刪除數據* @author cxy@toc* @date  2024-05-07**/public class DeleteData {public static void main(String[] args) throws IOException {//鏈接hbaseConfiguration conf = HBaseConfiguration.create();//這里的zookeeper地址要改為自己集群的zookeeper地址conf.set("hbase.zookeeper.quorum",Config4HBaseDemo.getZkQuorum(args));conf.set("hbase.zookeeper.property.clientPort", "2181");Connection connection = ConnectionFactory.createConnection(conf);Table hTable = connection.getTable(TableName.valueOf("users"));Delete delete = new Delete(Bytes.toBytes("row5"));delete.addColumn(Bytes.toBytes("f"),Bytes.toBytes("id"));//直接刪除family,將所有row5的信息全部刪除delete.addFamily(Bytes.toBytes("f"));hTable.delete(delete);System.out.println("刪除成功");}
}

DeleteTable(刪除表)
package com.toc.demo.hbase;import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.Admin;
import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.ConnectionFactory;import java.io.IOException;/*** 刪除表* @author cxy@toc* @date  2024-05-07**/public class DeleteTable {public static void main(String[] args) throws IOException {//鏈接hbaseConfiguration conf = HBaseConfiguration.create();//這里的zookeeper地址要改為自己集群的zookeeper地址conf.set("hbase.zookeeper.quorum",Config4HBaseDemo.getZkQuorum(args));conf.set("hbase.zookeeper.property.clientPort", "2181");Connection connection = ConnectionFactory.createConnection(conf);Admin hBaseAdmin = connection.getAdmin();TableName tableName = TableName.valueOf("users");if (hBaseAdmin.tableExists(tableName)) {//判斷表的狀態if(hBaseAdmin.isTableAvailable(tableName)) {hBaseAdmin.disableTable(tableName);}hBaseAdmin.deleteTable(tableName);System.out.println("刪除表"+tableName+"成功");}else {System.out.println(tableName+"表不存在");}}
}

DescTable(查看表結構)
package com.toc.demo.hbase;import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.*;import java.io.IOException;/*** 查看表結構* @author cxy@toc* @date  2024-05-07**/
public class DescTable {public static void main(String[] args) throws IOException {//鏈接hbaseConfiguration conf = HBaseConfiguration.create();//這里的zookeeper地址要改為自己集群的zookeeper地址conf.set("hbase.zookeeper.quorum",Config4HBaseDemo.getZkQuorum(args));conf.set("hbase.zookeeper.property.clientPort", "2181");Connection connection = ConnectionFactory.createConnection(conf);Admin hBaseAdmin = connection.getAdmin();TableName tableName = TableName.valueOf("users");if(hBaseAdmin.tableExists(tableName)) {HTableDescriptor htd = hBaseAdmin.getTableDescriptor(tableName);System.out.println("查看"+tableName+"表結構");System.out.println(htd);}else {System.out.println(tableName+"表不存在");}}
}

GetData(獲取數據)
package com.toc.demo.hbase;import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.util.Bytes;import java.io.IOException;/*** 獲取數據* @author cxy@toc* @date  2024-05-07**/public class GetData {public static void main(String[] args) throws IOException {//鏈接hbaseConfiguration conf = HBaseConfiguration.create();//這里的zookeeper地址要改為自己集群的zookeeper地址conf.set("hbase.zookeeper.quorum",Config4HBaseDemo.getZkQuorum(args));conf.set("hbase.zookeeper.property.clientPort", "2181");Connection connection = ConnectionFactory.createConnection(conf);Table hTable = connection.getTable(TableName.valueOf("users"));Get get = new Get(Bytes.toBytes("row1"));Result result = hTable.get(get);byte[] family = Bytes.toBytes("f");byte[] buf = result.getValue(family,Bytes.toBytes("id"));System.out.println("id="+Bytes.toString(buf));buf = result.getValue(family,Bytes.toBytes("age"));System.out.println("age="+Bytes.toInt(buf));buf = result.getValue(family,Bytes.toBytes("name"));System.out.println("name="+Bytes.toString(buf));buf = result.getRow();System.out.println("rowkey="+Bytes.toString(buf));}
}

PutData(插入數據)
package com.toc.demo.hbase;import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.util.Bytes;import java.io.IOException;
import java.util.ArrayList;
import java.util.List;/*** 插入數據* @author cxy@toc* @date  2024-05-07**/public class PutData {public static void main(String[] args) throws IOException {//鏈接hbaseConfiguration conf = HBaseConfiguration.create();//這里的zookeeper地址要改為自己集群的zookeeper地址conf.set("hbase.zookeeper.quorum",Config4HBaseDemo.getZkQuorum(args));conf.set("hbase.zookeeper.property.clientPort", "2181");Connection connection = ConnectionFactory.createConnection(conf);Table hTable = connection.getTable(TableName.valueOf("users"));//插入一條Put put= new Put(Bytes.toBytes("row1"));put.addColumn(Bytes.toBytes("f"),Bytes.toBytes("id"),Bytes.toBytes("1"));put.addColumn(Bytes.toBytes("f"),Bytes.toBytes("name"),Bytes.toBytes("張三"));put.addColumn(Bytes.toBytes("f"),Bytes.toBytes("age"),Bytes.toBytes(27));put.addColumn(Bytes.toBytes("f"),Bytes.toBytes("phone"),Bytes.toBytes("18600000000"));put.addColumn(Bytes.toBytes("f"),Bytes.toBytes("emil"),Bytes.toBytes("123654@163.com"));hTable.put(put);//插入多個Put put1= new Put(Bytes.toBytes("row2"));put1.addColumn(Bytes.toBytes("f"),Bytes.toBytes("id"),Bytes.toBytes("2"));put1.addColumn(Bytes.toBytes("f"),Bytes.toBytes("name"),Bytes.toBytes("李四"));Put put2= new Put(Bytes.toBytes("row3"));put2.addColumn(Bytes.toBytes("f"),Bytes.toBytes("id"),Bytes.toBytes("3"));put2.addColumn(Bytes.toBytes("f"),Bytes.toBytes("name"),Bytes.toBytes("王五"));Put put3= new Put(Bytes.toBytes("row4"));put3.addColumn(Bytes.toBytes("f"),Bytes.toBytes("id"),Bytes.toBytes("4"));put3.addColumn(Bytes.toBytes("f"),Bytes.toBytes("name"),Bytes.toBytes("趙六"));List<Put> list = new ArrayList<Put>();list.add(put1);list.add(put2);list.add(put3);hTable.put(list);//檢測put,條件成功就插入,要求RowKey是一樣的Put put4 = new Put(Bytes.toBytes("row5"));put4.addColumn(Bytes.toBytes("f"),Bytes.toBytes("id"),Bytes.toBytes("5"));hTable.checkAndPut(Bytes.toBytes("row5"),Bytes.toBytes("f"),Bytes.toBytes("id"),null,put4);System.out.println("插入成功");}
}

ScanData(掃描遍歷數據)
package com.toc.demo.hbase;import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.filter.FilterList;
import org.apache.hadoop.hbase.filter.MultipleColumnPrefixFilter;
import org.apache.hadoop.hbase.util.Bytes;import java.io.IOException;
import java.util.Iterator;
import java.util.Map;
import java.util.NavigableMap;/*** 掃描遍歷數據* @author cxy@toc* @date  2024-05-07**/public class ScanData {public static void main(String[] args) throws IOException {//鏈接hbaseConfiguration conf = HBaseConfiguration.create();//這里的zookeeper地址要改為自己集群的zookeeper地址conf.set("hbase.zookeeper.quorum",Config4HBaseDemo.getZkQuorum(args));conf.set("hbase.zookeeper.property.clientPort", "2181");Connection connection = ConnectionFactory.createConnection(conf);Table hTable = connection.getTable(TableName.valueOf("users"));Scan scan = new Scan();//增加起始rowkeyscan.withStartRow(Bytes.toBytes("row1"));scan.withStopRow(Bytes.toBytes("row5"));//增加過濾filterFilterList list = new FilterList(FilterList.Operator.MUST_PASS_ALL);byte[][] prefixes = new byte[2][];prefixes[0] = Bytes.toBytes("id");prefixes[1] = Bytes.toBytes("name");MultipleColumnPrefixFilter mcpf = new MultipleColumnPrefixFilter(prefixes);list.addFilter(mcpf);scan.setFilter(list);ResultScanner rs = hTable.getScanner(scan);Iterator<Result> iter = rs.iterator();while (iter.hasNext()){Result result = iter.next();printResult(result);}}/*打印Result對象*/static void printResult(Result result){System.out.println("***********"+Bytes.toString(result.getRow()));NavigableMap<byte[],NavigableMap<byte[], NavigableMap<Long,byte[]>>> map = result.getMap();for(Map.Entry<byte[],NavigableMap<byte[],NavigableMap<Long,byte[]>>> entry: map.entrySet()){String family = Bytes.toString(entry.getKey());for(Map.Entry<byte[],NavigableMap<Long,byte[]>> columnEntry :entry.getValue().entrySet()){String column = Bytes.toString(columnEntry.getKey());String value = "";if("age".equals(column)){value=""+Bytes.toInt(columnEntry.getValue().firstEntry().getValue());}else {value=""+Bytes.toString(columnEntry.getValue().firstEntry().getValue());}System.out.println(family+":"+column+":"+value);}}}
}



三、如何使用

  1. Maven打包
    Eclipse:項目上右鍵 Run As -> Maven Install進行打包
    Idea:Maven工具欄 -> 生命周期 -> install
    打包好的jar如上圖中的③

  2. 上傳jar到Hadoop(yarn)服務器
    scp 你的target/HbaseAPI-jar-with-dependencies.jar root@xxx.xxx.xxx.xxx:/root

  3. 登錄到服務器,并查看上傳的文件

ssh root@xxx.xxx.xxx.xxx
ls
  1. 執行命令,查看效果
# 將{ZK的內網IP}改為zookeeper的ip,如過就是本機可以不寫,默認是127.0.0.1
yarn jar HbaseAPI-jar-with-dependencies.jar com.toc.demo.hbase.CreateTable {ZK的內網IP}
yarn jar HbaseAPI-jar-with-dependencies.jar com.toc.demo.hbase.DescTable {ZK的內網IP}
yarn jar HbaseAPI-jar-with-dependencies.jar com.toc.demo.hbase.PutData {ZK的內網IP}
yarn jar HbaseAPI-jar-with-dependencies.jar com.toc.demo.hbase.GetData {ZK的內網IP}
yarn jar HbaseAPI-jar-with-dependencies.jar com.toc.demo.hbase.ScanData {ZK的內網IP}
yarn jar HbaseAPI-jar-with-dependencies.jar com.toc.demo.hbase.DeleteData {ZK的內網IP}
yarn jar HbaseAPI-jar-with-dependencies.jar com.toc.demo.hbase.DeleteTable {ZK的內網IP}

更多詳細操作可參見華為云沙箱實驗:https://lab.huaweicloud.com/experiment-detail_1779


往期精彩內容推薦

云原生:10分鐘了解一下Kubernetes架構
云原生:5分鐘了解一下Kubernetes是什么
「快速部署」第二期清單
「快速部署」第一期清單

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/11562.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/11562.shtml
英文地址,請注明出處:http://en.pswp.cn/web/11562.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

IO—消息隊列+管道

使用消息隊列實現的2個終端之間的互相聊天 并使用信號控制消息隊列的讀取方式: 當鍵盤按ctrlc的時候&#xff0c;切換消息讀取方式&#xff0c;一般情況為讀取指定編號的消息&#xff0c;按ctr1c之后&#xff0c;指定的編號不讀取&#xff0c;讀取其他所有編號的消息 wftok.c …

vue項目中使用websocke即時通訊實現系統公告實時獲取并提醒

一、使用場景 發布者設置需要發布的公告內容、公告接收用戶和發布時間&#xff0c;到達發布時間時及時通知提醒已登錄系統用戶&#xff0c;使用websocke來實現前端與服務器保持長連接&#xff0c;以便實時過去公告信息。 WebSocket是一種在單個TCP連接上進行全雙工通信的協議…

調用Mertc的接口

概述 metaRTC5.0版本 API進行了重構&#xff0c;本篇文章將介紹webrtc傳輸調用流程和例子。 metaRTC5.0版本提供了C和純C兩種接口。 ICE設置 iceCandidateType參數可以在配置文件yang_config.ini中配置&#xff0c;也可以在程序中賦值。 iceCandidateType0 //0:host 1:stun 2…

2024最新大廠C++面試真題合集,大廠面試百日沖刺 bay9

騰訊實習 指針常量和常量指針 常量指針&#xff08;const Type* ptr&#xff09;&#xff1a;指針指向的內容不能被改變&#xff0c;但指針本身可以改變指向。 指針常量&#xff08;Type* const ptr&#xff09;&#xff1a;指針自身的值即內存地址不能改變&#xff0c;但指向…

draw.io 網頁版二次開發(1):源碼下載和環境搭建

目錄 一 說明 二 源碼地址以及下載 三 開發環境搭建 1. 前端工程地址 2. 配置開發環境 &#xff08;1&#xff09;安裝 node.js &#xff08;2&#xff09;安裝 serve 服務器 3. 運行 四 最后 一 說明 應公司項目要求&#xff0c;需要對draw.io進行二次開發&…

電商后臺的秘密:通過API接口提取商品信息

在電子商務的運營中&#xff0c;后臺管理是核心環節&#xff0c;而API接口則是高效管理商品信息的關鍵。API允許商家直接與電商平臺的數據庫進行交互&#xff0c;實現數據的自動化提取和更新。 一、電商后臺管理的核心作用 電商后臺管理系統是商家進行商品展示、訂單處理、庫…

存儲過程、觸發器和函數

存儲過程、觸發器和函數在數據庫中具有重要的作用&#xff0c;它們可以帶來以下幾個方面的重要性&#xff1a; 數據一致性和完整性&#xff1a; 觸發器和存儲過程可以用于實現數據一致性和完整性約束。通過在數據庫操作&#xff08;如插入、更新、刪除&#xff09;發生時自動執…

盛最多水的容器(雙指針)

解題思路&#xff1a; 1&#xff0c;暴力解法&#xff08;超時&#xff09; 我們可以使用兩層for循環進行遍歷。找到那個最大的面積即可&#xff0c;這里我就不寫代碼了&#xff0c;因為寫了也是超時。 2&#xff0c;雙指針法 先定義兩個指針一個在最左端&#xff0c;一個在…

C++ 派生類的引入與特性

一 繼承與派生 從上面的例子可以看出&#xff1a; 繼承&#xff1a;一旦指定了某種事物父代的本質特征&#xff0c;那么它的子代將會自動具有哪些性質。這就是一種樸素的可重用的概念。 派生&#xff1a;而且子代可以擁有父代沒有的特性&#xff0c;這是可擴充的概念。 1 C 的…

Today At Apple 2024.04.15 Phone15 入門

官網&#xff1a; https://www.apple.com/today/Apple 亞洲第一大商店&#xff1a;Apple 靜安零售店現已在上海開幕如下預約課程&#xff1a;下載 Apple Store&#xff08;不是app store&#xff09;&#xff0c;點擊課程預約筆記&#xff1a;Today At Apple Notes果粉加群 &am…

Mybatis進階詳細用法

目錄 條件構造器 案例 自定義SQL 案例 Service接口 案例 綜合案例 條件構造器 案例 Testvoid testQueryMapper() {// 創建 QueryWrapper 實例QueryWrapper<User> queryWrapper new QueryWrapper<>();queryWrapper.select("id," "username,&…

uniapp經驗

uniapp-ts模版在前端/vue文件夾下 npx dcloudio/uvmlatest 安裝依賴 之后tsconfig.json會報錯&#xff0c;可以在tsconfig.json文件中"compilerOptions"配置項內添加"ignoreDeprecations": "5.0"&#xff0c;解決。 ### 編譯和運行 uni-app 項目…

Postman基礎功能-變量設置與使用

如果你因失去太陽而流淚&#xff0c;那你也將失去群星了。大家好&#xff0c;在 API 測試的廣袤世界中&#xff0c;Postman 猶如一座閃耀的燈塔&#xff0c;為我們指引著前行的方向。而其中的全局變量、集合變量和環境變量&#xff0c;更是如同隱藏的寶藏&#xff0c;蘊含著巨大…

以太網網絡變壓器型號

Hqst華強盛導讀&#xff1a;以太網網絡變壓器的型號通常由一系列數字和字母組成&#xff0c;其中包括以下信息&#xff1a; 額定電壓&#xff1a;表示變壓器的額定輸入和輸出電壓&#xff0c;通常以伏特&#xff08;V&#xff09;為單位。 額定電流&#xff1a;表示變壓器的額定…

0513_IO7

練習1&#xff1a; 使用消息隊列實現的2個終端之間的互相聊天 并使用信號控制消息隊列的讀取方式&#xff1a; 當鍵盤按ctrlc的時候&#xff0c;切換消息讀取方式&#xff0c;一般情況為讀取指定編號的消息&#xff0c;按ctrlc之后&#xff0c;指定的編號不讀取&#xff0c;讀取…

孩子多大可以接觸python?學習python的好處

孩子接觸Python的年齡并沒有明確的界限&#xff0c;一般來說&#xff0c;6歲以上的孩子可以開始學習Python編程。雖然Python是一門高級編程語言&#xff0c;但它的語法簡單易懂&#xff0c;適合初學者入門。通過學習Python編程&#xff0c;孩子可以培養邏輯思維、創造力和解決問…

電商秒殺系統設計

業務流程 系統架構 系統挑戰 高并發:秒殺活動會在短時間內吸引大量用戶,系統需要能夠處理高峰時期的大量并發請求 庫存同步:在秒殺中,面臨的一個嚴重系統挑戰是如何確保在數以萬計的用戶同時搶購有限的商品時,如何正確、實時地扣減庫存,以防止超賣現象。 防止惡意搶購和…

前端 JS 經典:JS 基礎類型和 typeof

前言&#xff1a;JS 基礎類型就 8 種&#xff0c;這是官方確定的&#xff0c;毋庸置疑。其中原始類型 7 種&#xff0c;對象類型 1 種。而 typeof 關鍵字是用來判斷數據是屬于什么類型的。 1. 原始類型 Number、Boolean、String、BigInt、symbol、Undefined、null typeof 18…

貓頭虎分享已解決Error || ERROR: Failed building wheel for XXX

博主貓頭虎的技術世界 &#x1f31f; 歡迎來到貓頭虎的博客 — 探索技術的無限可能&#xff01; 專欄鏈接&#xff1a; &#x1f517; 精選專欄&#xff1a; 《面試題大全》 — 面試準備的寶典&#xff01;《IDEA開發秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鴻蒙》 …

python設計模式---觀察者模式

觀察者模式是一種行為設計模式&#xff0c;用于定義對象之間的一對多依賴關系&#xff0c;當一個對象的狀態發生變化時&#xff0c;所有依賴它的對象都會得到通知并自動更新。 from abc import ABC, abstractmethod from typing import Listclass Observable:def __init__(sel…