Hive自定義函數案例(UDF、UDAF、UDTF)

?

目錄

前提條件

背景

概念及適用場景

UDF(User-Defined Function)

概念

適用場景

UDAF(User-Defined Aggregate Function)

概念

適用場景

UDTF(User-Defined Table-Generating Function)

概念

適用場景

案例

UDF案例

UDTF案例

UDAF案例


前提條件

  • 安裝好Hive,可參考:openEuler24.03 LTS下安裝Hive3
  • 具備Java開發環境:JDK8、Maven3、IDEA

背景

Hive 作為大數據領域常用的數據倉庫工具,提供了豐富的內置函數,但在實際業務場景中,內置函數往往無法滿足復雜的計算需求。這時,Hive 的自定義函數就顯得尤為重要。Hive 支持三種類型的自定義函數:UDF、UDAF 和 UDTF,本文分別介紹它們的概念和適用場景,并給出典型案例。

概念及適用場景

UDF(User-Defined Function)

概念

UDF 是最基本的自定義函數類型,用于實現 "單行進,單行出" 的處理邏輯,即對每行數據中的一個或多個輸入值進行計算,返回一個結果值。

適用場景

  • 字符串處理(如格式轉換、編碼轉換)
  • 數學計算(如自定義計算公式)
  • 日期處理(如自定義日期格式解析)

UDAF(User-Defined Aggregate Function)

概念

UDAF 即用戶定義的聚合函數,用于實現 "多行進,一行出" 的處理邏輯,將一組數據經過計算后返回一個匯總結果,類似于 SQL 中的 SUM、COUNT 等內置聚合函數。

適用場景

  • 自定義統計指標(如計算中位數、眾數)
  • 復雜數據聚合(如分組拼接字符串)
  • 多階段聚合計算

UDTF(User-Defined Table-Generating Function)

概念

UDTF 是用戶定義的表生成函數,實現 "單行進,多行出" 的處理邏輯,將一行數據擴展為多行或多列數據。

適用場景

  • 字符串拆分(如將逗號分隔的字符串拆分為多行)
  • 數組或集合展開(如將 JSON 數組展開為多行記錄)
  • 復雜數據結構解析(如解析嵌套 JSON)

案例

UDF案例

需求:

自定義一個UDF實現計算給定基本數據類型的長度,效果如下:

hive(default)> select my_len("abcd");4

1)使用IDEA創建一個Maven工程Hive,工程名稱例如:udf

2)添加依賴

<dependencies><dependency><groupId>org.apache.hive</groupId><artifactId>hive-exec</artifactId><version>3.1.3</version></dependency>
</dependencies>

添加依賴后,刷新依賴,如下

3)創建包、創建類

創建包:在src/main/java下創建org.exapmle.hive.udf包

創建類:MyUDF.java

package org.example.hive.udf;import org.apache.hadoop.hive.ql.exec.UDFArgumentException;
import org.apache.hadoop.hive.ql.exec.UDFArgumentLengthException;
import org.apache.hadoop.hive.ql.exec.UDFArgumentTypeException;
import org.apache.hadoop.hive.ql.metadata.HiveException;
import org.apache.hadoop.hive.ql.udf.generic.GenericUDF;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory;/*** 我們需計算一個要給定基本數據類型的長度*/
public class MyUDF extends GenericUDF {/*** 判斷傳進來的參數的類型和長度* 約定返回的數據類型*/@Overridepublic ObjectInspector initialize(ObjectInspector[] arguments) throws UDFArgumentException {if (arguments.length !=1) {throw new UDFArgumentLengthException("please give me only one arg");}if (!arguments[0].getCategory().equals(ObjectInspector.Category.PRIMITIVE)){throw  new UDFArgumentTypeException(1, "i need primitive type arg");}return PrimitiveObjectInspectorFactory.javaIntObjectInspector;}/*** 解決具體邏輯的*/@Overridepublic Object evaluate(DeferredObject[] arguments) throws HiveException {Object o = arguments[0].get();if(o==null){return 0;}return o.toString().length();}/*** 用于獲取解釋的字符串*/@Overridepublic String getDisplayString(String[] children) {return "";}
}

?4)創建臨時函數

1)打成jar包上傳到Linux /opt/module/hive/datas/myudf.jar

點擊右側的Maven,點開Lifecycle,按Ctrl鍵不放,同時選中clean和package,點擊箭頭指向的三角形圖標運行

?看到BUILD SUCCESS說明打包成功,同時看到jar包所在路徑,如下

將jar包上傳到Linux合適目錄下,例如:/home/liang/testjar

[liang@node2 testjar]$ ls
udf-1.0-SNAPSHOT.jar

(2)將jar包添加到hive的classpath,臨時生效

hive (default)> add jar /home/liang/testjar/udf-1.0-SNAPSHOT.jar;

?(3)創建臨時函數與開發好的java class關聯

hive (default)> create temporary function my_len as "org.exapmle.hive.udf.MyUDF";

注意:創建臨時函數,此時只是在當前會話生效,關閉會話,臨時函數被刪除。如果需要能在其他會話能看到,且關閉會話后,不刪除自定義函數,則需要創建永久函數。

(4)查詢函數

hive (default)> show functions;
...
months_between
murmur_hash
my_len
named_struct
negative
...
Time taken: 0.024 seconds, Fetched: 291 row(s)

看到my_len函數,說明可以使用自定義函數了。?

(5)使用自定義的臨時函數

hive (default)> select my_len("abcd");

結果為

4

(6)刪除臨時函數

使用如下語句或者關閉會話(退出Hive命令行)刪除臨時函數。

hive (default)> drop temporary function my_len;

5)創建永久函數

(1)創建永久函數

把jar包上傳到hdfs

[liang@node2 ~]$ hdfs dfs -put /home/liang/testjar/udf-1.0-SNAPSHOT.jar /

創建永久函數

hive (default)> 
create function my_len2 as "org.exapmle.hive.udf.MyUDF" using jar "hdfs://node2:8020/udf-1.0-SNAPSHOT.jar";

操作過程

hive (default)> create function my_len2 as "org.exapmle.hive.udf.MyUDF" using jar "hdfs://node2:8020/udf-1.0-SNAPSHOT.jar";
Added [/tmp/944c050b-e360-48f1-b7b6-93f8fd7e2644_resources/udf-1.0-SNAPSHOT.jar] to class path
Added resources: [hdfs://node2:8020/udf-1.0-SNAPSHOT.jar]
OK
Time taken: 0.212 seconds

查看函數

hive (default)> show functions;
...
dayofweek
decode
default.my_len2
degrees
dense_rank
...
Time taken: 0.019 seconds, Fetched: 291 row(s)

看到永久函數名為庫名.函數名。

注意:永久函數創建的時候,在函數名之前需要自己加上庫名,如果不指定庫名的話,會默認把當前庫的庫名給加上。

退出hive命令行會話,重新進入hive命令行,再次查看函數,還可以看到default.my_len2

hive (default)> show functions;
...
dayofweek
decode
default.my_len2
degrees
dense_rank
...
Time taken: 0.019 seconds, Fetched: 291 row(s)

使用永久函數

hive (default)> select my_len2("abcd");

結果為

4

(3)刪除永久函數

hive (default)> drop function my_len2;

注意:永久函數使用的時候,在其他庫里面使用的話加上,庫名.函數名。

UDTF案例

需求:

將字符串按分隔符分割為多行,例如:將a,b,c按照,進行分隔,得到三行

a
b
c

代碼

package org.exapmle.hive.udtf;import org.apache.hadoop.hive.ql.exec.Description;
import org.apache.hadoop.hive.ql.exec.UDFArgumentException;
import org.apache.hadoop.hive.ql.exec.UDFArgumentLengthException;
import org.apache.hadoop.hive.ql.metadata.HiveException;
import org.apache.hadoop.hive.ql.udf.generic.GenericUDTF;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspectorFactory;
import org.apache.hadoop.hive.serde2.objectinspector.StructField;
import org.apache.hadoop.hive.serde2.objectinspector.StructObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory;
import org.apache.hadoop.hive.serde2.objectinspector.primitive.StringObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.primitive.WritableStringObjectInspector;
import org.apache.hadoop.io.Text;import java.util.Arrays;
import java.util.List;@Description(name = "explode_string",value = "將字符串按分隔符分割為多行",extended = "SELECT explode_string('a,b,c', ',') FROM table_name;"
)
public class ExplodeWordsUDTF extends GenericUDTF {private WritableStringObjectInspector inputOI;private WritableStringObjectInspector separatorOI;private final Object[] forwardObj = new Object[1];private final Text outputText = new Text();@Overridepublic StructObjectInspector initialize(StructObjectInspector argOIs) throws UDFArgumentException {// 獲取輸入參數的ObjectInspectorList<? extends StructField> inputFields = argOIs.getAllStructFieldRefs();// 檢查參數數量if (inputFields.size() != 2) {throw new UDFArgumentLengthException("explode_string需要兩個參數: 字符串和分隔符");}// 檢查參數類型ObjectInspector firstOI = inputFields.get(0).getFieldObjectInspector();ObjectInspector secondOI = inputFields.get(1).getFieldObjectInspector();if (!(firstOI instanceof WritableStringObjectInspector) || !(secondOI instanceof WritableStringObjectInspector)) {throw new UDFArgumentLengthException("參數必須是字符串類型");}inputOI = (WritableStringObjectInspector) firstOI;separatorOI = (WritableStringObjectInspector) secondOI;// 定義輸出結構List<String> fieldNames = Arrays.asList("element");List<ObjectInspector> fieldOIs = Arrays.asList(PrimitiveObjectInspectorFactory.writableStringObjectInspector);return ObjectInspectorFactory.getStandardStructObjectInspector(fieldNames, fieldOIs);}@Overridepublic void process(Object[] args) throws HiveException {if (args[0] == null) {return;}Text input = inputOI.getPrimitiveWritableObject(args[0]);Text sep = separatorOI.getPrimitiveWritableObject(args[1]);String separator = sep != null ? sep.toString() : ",";String inputStr = input.toString();// 處理空字符串if (inputStr.isEmpty()) {outputText.set("");forwardObj[0] = outputText;forward(forwardObj);return;}// 使用正則表達式分隔字符串String[] elements = inputStr.split(separator, -1);for (String element : elements) {outputText.set(element);forwardObj[0] = outputText;forward(forwardObj);}}@Overridepublic void close() throws HiveException {}
}

打jar包,上傳到Linux

注冊與使用

hive (default)> ADD JAR /home/liang/testjar/udf-1.0-SNAPSHOT.jar;
Added [/home/liang/testjar/udf-1.0-SNAPSHOT.jar] to class path
Added resources: [/home/liang/testjar/udf-1.0-SNAPSHOT.jar]hive (default)> CREATE TEMPORARY FUNCTION explode_string AS 'org.exapmle.hive.udtf.ExplodeStringUDTF';
OK
Time taken: 0.362 secondshive (default)> SELECT explode_string('a,b,c', ',');
OK
element
a
b
c
Time taken: 2.844 seconds, Fetched: 3 row(s)hive (default)> SELECT explode_string('hello,world', ',');
OK
element
hello
world
Time taken: 0.209 seconds, Fetched: 2 row(s)

UDAF案例

需求:

計算加權平均值,加權平均數=(Σ(數值×權重))/Σ權重

例如:

計算學生綜合成績時,若數學(學分4分,成績90)和語文(學分3分,成績80),其中數值為成績,權重為學分,則加權平均成績為 (4×90+3×80)/(4+3)≈85.71(4×90+3×80)/(4+3)≈85.71分。

代碼

package org.exapmle.hive.udaf;import org.apache.hadoop.hive.ql.exec.Description;
import org.apache.hadoop.hive.ql.exec.UDAF;
import org.apache.hadoop.hive.ql.exec.UDAFEvaluator;
import org.apache.hadoop.hive.serde2.io.DoubleWritable;
import org.apache.hadoop.io.Writable;import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;@Description(name = "weighted_avg",value = "計算加權平均值",extended = "SELECT weighted_avg(score, credit) FROM grades GROUP BY student_id;"
)
public class WeightedAverageUDAF extends UDAF {public static class WeightedAverageEvaluator implements UDAFEvaluator {// 存儲中間結果private double sumWeightedValues;private double sumWeights;private boolean empty;@Overridepublic void init() {sumWeightedValues = 0;sumWeights = 0;empty = true;}// 處理輸入行public boolean iterate(DoubleWritable value, DoubleWritable weight) {if (value == null || weight == null || weight.get() <= 0) {return true;}sumWeightedValues += value.get() * weight.get();sumWeights += weight.get();empty = false;return true;}// 存儲部分結果的類public static class PartialResult implements Writable {double sumWeightedValues;double sumWeights;@Overridepublic void write(DataOutput out) throws IOException {out.writeDouble(sumWeightedValues);out.writeDouble(sumWeights);}@Overridepublic void readFields(DataInput in) throws IOException {sumWeightedValues = in.readDouble();sumWeights = in.readDouble();}}// 返回部分結果public PartialResult terminatePartial() {if (empty) {return null;}PartialResult result = new PartialResult();result.sumWeightedValues = sumWeightedValues;result.sumWeights = sumWeights;return result;}// 合并部分結果public boolean merge(PartialResult other) {if (other == null) {return true;}sumWeightedValues += other.sumWeightedValues;sumWeights += other.sumWeights;empty = false;return true;}// 返回最終結果public DoubleWritable terminate() {if (empty || sumWeights <= 0) {return null;}return new DoubleWritable(sumWeightedValues / sumWeights);}}
}

打jar包,上傳到Linux

注冊

hive (default)> ADD JAR /home/liang/testjar/udf-1.0-SNAPSHOT.jar;
Added [/home/liang/testjar/udf-1.0-SNAPSHOT.jar] to class path
Added resources: [/home/liang/testjar/udf-1.0-SNAPSHOT.jar]hive (default)> CREATE TEMPORARY FUNCTION weighted_avg AS 'org.exapmle.hive.udaf.WeightedAverageUDAF';
OK
Time taken: 0.043 seconds

使用

WITH grades AS (SELECT 1 AS student_id, 'Math' AS course, 90 AS score, 4 AS creditUNION ALLSELECT 1, 'English', 85, 3UNION ALLSELECT 2, 'Math', 88, 4UNION ALLSELECT 2, 'English', 92, 3
)
SELECT student_id,weighted_avg(score, credit) AS gpa
FROM grades
GROUP BY student_id;

操作過程

hive (default)> WITH grades AS (>     SELECT 1 AS student_id, 'Math' AS course, 90 AS score, 4 AS credit>     UNION ALL>     SELECT 1, 'English', 85, 3>     UNION ALL>     SELECT 2, 'Math', 88, 4>     UNION ALL>     SELECT 2, 'English', 92, 3> )> SELECT>     student_id,>     weighted_avg(score, credit) AS gpa> FROM grades> GROUP BY student_id;
Query ID = liang_20250521165046_6335eb21-2d92-4ae1-b30c-511bcb9a98ab
Total jobs = 1
Launching Job 1 out of 1
Number of reduce tasks not specified. Estimated from input data size: 1
In order to change the average load for a reducer (in bytes):set hive.exec.reducers.bytes.per.reducer=<number>
In order to limit the maximum number of reducers:set hive.exec.reducers.max=<number>
In order to set a constant number of reducers:set mapreduce.job.reduces=<number>
Starting Job = job_1747808931389_0001, Tracking URL = http://node3:8088/proxy/application_1747808931389_0001/
Kill Command = /opt/module/hadoop-3.3.4/bin/mapred job  -kill job_1747808931389_0001
Hadoop job information for Stage-1: number of mappers: 1; number of reducers: 1
2025-05-21 16:51:04,388 Stage-1 map = 0%,  reduce = 0%
2025-05-21 16:51:12,756 Stage-1 map = 100%,  reduce = 0%, Cumulative CPU 3.89 sec
2025-05-21 16:51:28,486 Stage-1 map = 100%,  reduce = 100%, Cumulative CPU 11.36 sec
MapReduce Total cumulative CPU time: 11 seconds 360 msec
Ended Job = job_1747808931389_0001
MapReduce Jobs Launched:
Stage-Stage-1: Map: 1  Reduce: 1   Cumulative CPU: 11.36 sec   HDFS Read: 12918 HDFS Write: 151 SUCCESS
Total MapReduce CPU Time Spent: 11 seconds 360 msec
OK
student_id      gpa
1       87.85714285714286
2       89.71428571428571
Time taken: 43.272 seconds, Fetched: 2 row(s)

更多Hive自定義函數用法,請參考:Hive官方文檔

完成!enjoy it!

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

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

相關文章

Go語言的原子操作

當我們想要對某個變量并發安全的修改&#xff0c;除了使用官方提供的mutex&#xff0c;還可以使用sync/atomic包的原子操作&#xff0c;它能夠保證對變量的讀取或修改期間不被其他的協程所影響。 Golang提供的原子操作都是非侵入式的&#xff0c;由標準庫sync/atmoic包提供&am…

QNAP MEMOS 域名訪問 SSL(Lucky)

注意&#xff1a;下述是通過ssh、docker-compose方式安裝docker的&#xff0c;不是直接在container station中安裝的哈&#xff01;&#xff01;&#xff01; 一、編輯docker-compose.yml文件 用“#”號標識的&#xff0c;在保存文件的時候建議去掉&#xff0c;不然有時候會出…

C#實現遠程鎖屏

前言 這是一次提前下班沒有鎖屏進而引發的一次思考后的產物&#xff0c;思考的主要場景是當人離開電腦后&#xff0c;怎么能控制電腦鎖屏&#xff0c;避免屏幕上的聊天記錄被曝光。 首先想到通過系統的電源計劃設置閑置超時時間熄屏&#xff0c;這可能是最接近場景的解決方案&a…

[Protobuf]常見數據類型以及使用注意事項

[Protobuf]常見數據類型以及使用注意事項 水墨不寫bug 文章目錄 一、基本數據類型1、字段2、字段的修飾規則 二、自定義數據類型1、message類型2、enum類型3、Any類型4、oneof類型5、map類型 三、小工具1.hexdump2.decode 四、注意事項 一、基本數據類型 protobuf 支持多種基礎…

JS分支和循環

程序的執行順序 在程序開發中&#xff0c;程序有三種不同的執行順序 1.順序執行 2.分支執行 3.循環執行 程序的代碼塊 <script>//一個代碼塊{var num11var num22var num3num1num2}//一個休想var info{name:"chen",age:18} 1.if分支語句&#xff08;單分支語句&…

Android 開發 Kotlin 全局大喇叭與廣播機制

在 Android 開發中&#xff0c;廣播機制就像一個神通廣大的 “消息快遞員”&#xff0c;承擔著在不同組件間傳遞信息的重任。Kotlin 語言的簡潔優雅更使其在廣播機制的應用中大放異彩。今天&#xff0c;就讓我們一同深入探索 Android 開發中 Kotlin 全局大喇叭與廣播機制的奧秘…

rabbitmq AI復習

RabbitMq rabbitmq &#x1f9d1;?&#x1f4bb; User 幫我復習rabbitmq相關知識&#xff0c;我是一個經驗豐富的程序員 &#x1f916; Assistant 好的&#xff01;很高興能通過這種方式幫你復習或學習 RabbitMQ 的知識。按照你說的流程&#xff0c;我們從完全零基礎開始&…

計算機視覺---YOLOv5

YOLOv5理論講解 一、YOLOv5 整體架構解析 YOLOv5 延續了 YOLO 系列的 單階段目標檢測框架&#xff0c;包含 主干網絡&#xff08;Backbone&#xff09;、頸部網絡&#xff08;Neck&#xff09; 和 檢測頭&#xff08;Head&#xff09;&#xff0c;但在結構設計上更注重 輕量化…

C++多重繼承詳解與實戰解析

#include <iostream> using namespace std; //基類&#xff0c;父類 class ClassA { public:void displayA() {std::cout << "Displaying ClassA" << std::endl;}void testFunc(){std::cout << "testFunc ClassA" << std::e…

單細胞注釋前沿:CASSIA——無參考、可解釋、自動化細胞注釋的大語言模型

細胞類型注釋是單細胞RNA-seq分析的重要步驟&#xff0c;目前有許多注釋方法。大多數注釋方法都需要計算和特定領域專業知識的結合&#xff0c;而且經常產生不一致的結果&#xff0c;難以解釋。大語言模型有可能在減少人工輸入和提高準確性的同時擴大可訪問性&#xff0c;但現有…

STM32Cubemx-H7-17-麥克納姆輪驅動

前言 --末尾右總體的.c和.h 本篇文章把麥克納姆輪的代碼封裝到.c和.h&#xff0c;使用者只需要根據輪子正轉的方向&#xff0c;在.h處修改定義方向引腳&#xff0c;把輪子都統一正向后&#xff0c;后面的輪子驅動就可以正常了&#xff0c;然后直接調用函數驅動即可。 設置滿…

文檔核心結構優化(程序C++...)

文檔核心結構優化 一、文檔核心結構優化二、C關鍵特性詳解框架2.1 從C到C的范式遷移 三、深度代碼解析模板3.1 現代C特性分層解析 四、C vs C 關鍵差異矩陣五、交互式文檔設計策略5.1 三維學習路徑5.2 代碼缺陷互動區 六、現代C特性演進圖七、性能優化可視化呈現&#xff08;深…

PyTorch ——torchvision數據集使用

如果下載的很慢&#xff0c;可以試試下面這個

純前端實現圖片偽3D視差效果

作者&#xff1a;vivo 互聯網前端團隊- Su Ning 本文通過depth-anything獲取圖片的深度圖&#xff0c;同時基于pixi.js&#xff0c;通過著色器編程&#xff0c;實現了通過深度圖驅動的偽3D效果。該方案支持鼠標/手勢與手機陀螺儀雙模式交互&#xff0c;在保證性能的同時&#x…

英語寫作中“專注于”focus on、concentrate的用法

Focus on在論文寫作中常用&#xff0c;指出研究點&#xff0c;例如&#xff1a; There are three approaches to achieving ID authentication. Our study will focus on ……&#xff08;有三種途徑實現身份認證&#xff0c;我們的研究專注于……&#xff09; concentrate &…

go環境配置

下載對應版本的 go 版本 https://go.dev/dl/ 配置 vim ~/.zshrc export GOROOT/usr/local/go export PATH$PATH:$GOROOT/binsource ~/.zshrc >>>>>> go versiongoland 配置&#xff1a; &#x1f50d; 一、什么是GOPATH&#xff1f; GOPATH 是舊的項目結…

AI Agent智能體:底層邏輯、原理與大模型關系深度解析·優雅草卓伊凡

AI Agent智能體&#xff1a;底層邏輯、原理與大模型關系深度解析優雅草卓伊凡 一、AI Agent的底層架構與核心原理 1.1 AI Agent的基本構成要素 AI Agent&#xff08;人工智能代理&#xff09;是一種能夠感知環境、自主決策并執行行動的智能系統。其核心架構包含以下關鍵組件…

【手搓一個原生全局loading組件解決頁面閃爍問題】

頁面閃爍效果1 頁面閃爍效果2 封裝一個全局loading組件 class GlobalLoading extends HTMLElement {constructor() {super();this.attachShadow({ mode: open });}connectedCallback() {this.render();this.init();}render() {this.shadowRoot.innerHTML <style>.load…

unix/linux source 命令,其高級使用

就像在物理學中,掌握了基本定律后,我們可以開始研究更復雜的系統和現象,source 的高級用法也是建立在對其基本行為深刻理解之上的。 讓我們一起探索 source 的高級應用領域: 1. 條件化加載 (Conditional Sourcing) 根據某些條件來決定是否 source 一個文件,或者 source…

DexGarmentLab 論文翻譯

單個 專家 演示 裝扮 15 任務 場景 2500+ 服裝 手套 棒球帽 褲子 圍巾 碗 帽子 上衣 外套 服裝-手部交互 捕捉 搖籃 夾緊 平滑 任務 ...... 投擲 懸掛 折疊 ... 多樣化位置 ... 多樣化 變形 ... 多樣化服裝形狀 類別級 一般化 類別級(有或沒有變形) 服裝具有相同結構 變形 生…