Guava——基本工具(二)

基本工具

7、文本拆分器(Splitter)

com.google.common.base.Splitter 是 Guava 提供的字符串拆分工具。相比 JDK 原生的 String.split(),它解决了 忽略空字符串、修剪空格(trim) 以及 流式延迟计算 等常见痛点,且不会静默丢弃末尾的空字符串。

7.1、核心API

工厂构造方法(指定拆分规则)

工厂方法 说明 示例
Splitter.on(char) 按指定单个字符拆分 Splitter.on(',')
Splitter.on(String) 按固定字符串拆分 Splitter.on("", "")
Splitter.on(CharMatcher) 按字符匹配器拆分 Splitter.on(CharMatcher.whitespace())
Splitter.on(Pattern) 按正则表达式拆分 Splitter.on(Pattern.compile(""\\d+""))
Splitter.onPattern(String) 按正则表达式字符串拆分 Splitter.onPattern(""\\s+"")
Splitter.fixedLength(int) 按固定字符长度等分` Splitter.fixedLength(3)

链式修饰方法(配置处理逻辑)

Splitter 实例是不可变的(Immutable),修饰方法会返回一个新的 Splitter 实例。

修饰方法 说明
omitEmptyStrings() 自动过滤拆分后产生的空字符串 ""
trimResults() 对拆分后的每个元素执行 trim() 去除前后空白
trimResults(CharMatcher) 对拆分后的每个元素去除匹配到的特定字符
limit(int) 限制最大拆分份数(达到 limit 数量后,剩余部分不再拆分)

终止方法(执行拆分并返回)

终止方法 返回类型 说明
split(CharSequence) Iterable<String> 默认延迟计算,遍历时才真正执行拆分
splitToList(CharSequence) List<String> 立即执行拆分并返回不可变 List(Java 8+ 常用)
splitToStream(CharSequence) Stream<String> 转换为 Java Stream 流(Guava 28.2+ 支持)
withKeyValueSeparator(...) MapSplitter 进一步配置,用于将字符串直接拆分为 Map

7.2、使用示例

java 复制代码
import com.google.common.base.CharMatcher;
import com.google.common.base.Splitter;

import java.util.List;
import java.util.Map;

public class SplitterDetailExample {

    public static void main(String[] args) {

        // 1. 基础拆分与空白处理 (omitEmptyStrings + trimResults)
        String input1 = "  apple , , banana,   orange , ";
        List<String> list1 = Splitter.on(',')
                .trimResults()          // 去除每个元素的前后空格
                .omitEmptyStrings()     // 忽略空字符串
                .splitToList(input1);
        System.out.println("示例 1: " + list1); 
        // 输出: [apple, banana, orange]


        // 2. 限制拆分份数 (limit)
        String input2 = "a,b,c,d,e";
        List<String> list2 = Splitter.on(',')
                .limit(3)               // 最多拆分为 3 份,剩余部分保持原样
                .splitToList(input2);
        System.out.println("示例 2: " + list2); 
        // 输出: [a, b, c,d,e]


        // 3. 按固定长度拆分 (fixedLength)
        String input3 = "12345678";
        List<String> list3 = Splitter.fixedLength(3)
                .splitToList(input3);
        System.out.println("示例 3: " + list3); 
        // 输出: [123, 456, 78]


        // 4. 按正则/字符匹配器拆分 (onPattern / CharMatcher)
        String input4 = "foo123bar456baz";
        List<String> list4 = Splitter.onPattern("\\d+")
                .splitToList(input4);
        System.out.println("示例 4: " + list4); 
        // 输出: [foo, bar, baz]


        // 5. 拆分为 Map (withKeyValueSeparator)
        String input5 = "name=Alice; age=25; city=Beijing";
        Map<String, String> map = Splitter.on(';')
                .trimResults()
                .withKeyValueSeparator('=') // 指定键值对分隔符
                .split(input5);
        System.out.println("示例 5: " + map); 
        // 输出: {name=Alice, age=25, city=Beijing}
    }
}
java 复制代码
示例 1: [apple, banana, orange]
示例 2: [a, b, c,d,e]
示例 3: [123, 456, 78]
示例 4: [foo, bar, baz]
示例 5: {name=Alice, age=25, city=Beijing}

8、字符串连接器(Joiner)

com.google.common.base.Joiner 是 Guava 提供的字符串拼接工具。与 JDK 8+ 的 String.join() 或 StringJoiner 相比,Guava 的 Joiner 提供了对 null 值的安全处理(忽略或替换)、数组/集合/迭代器的直接拼接以及 Map 映射关系的优雅格式化。

8.1、核心API

工厂构造方法(指定连接符)

工厂方法 说明 示例
Joiner.on(String) 使用指定的字符串作为分隔符 Joiner.on(", ")
Joiner.on(char) 使用指定的字符作为分隔符 Joiner.on('-')

链式配置方法(处理 null 与扩展配置)

Joiner 实例是 不可变(Immutable)且线程安全 的,所有配置方法都会返回一个新的 Joiner 实例。

配置方法 说明
skipNulls() 拼接时自动跳过/忽略为 null 的元素
useForNull(String) 拼接时将为 null 的元素替换为指定的字符串
withKeyValueSeparator(String/char) 指定键值对之间的分隔符,返回 MapJoiner,用于拼接 Map

注意:skipNulls() 和 useForNull() 是互斥的,不能同时调用。如果都不配置,拼接包含 null 的序列时会抛出 NullPointerException。

终止方法(执行拼接并返回)

  1. 普通 Joiner 的终止方法

    终止方法 返回/操作 说明
    join(Iterable/Iterator/Object[]) String 拼接集合、迭代器或数组中的元素
    join(Object, Object, Object...) String 拼接离散传入的多个对象
    appendTo(A, ...) A (追加目标),将结果直接追加到 StringBuilder、Writer 或 Appendable 中
  2. MapJoiner 的终止方法

    通过 Joiner.on(...).withKeyValueSeparator(...) 构建后使用:

    终止方法 返回/操作 说明
    join(Map<?, ?>) String 将 Map 拼接为形如 k1=v1&k2=v2 的字符串
    appendTo(A, Map<?, ?>) A (追加目标),将 Map 拼接结果追加到 Appendable 目标对象中

8.2、使用示例

java 复制代码
import com.google.common.base.Joiner;

import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class JoinerDetailExample {

    public static void main(String[] args) throws IOException {

        // 包含 null 元素的测试列表
        List<String> listWithNull = Arrays.asList("Apple", null, "Banana", "Orange", null);

        // 1. 跳过 null 元素 (skipNulls)
        String skipNullsResult = Joiner.on(", ")
                .skipNulls()
                .join(listWithNull);
        System.out.println("示例 1 (skipNulls): " + skipNullsResult);
        // 输出: Apple, Banana, Orange


        // 2. 将 null 替换为指定默认值 (useForNull)
        String useForNullResult = Joiner.on(" | ")
                .useForNull("N/A")
                .join(listWithNull);
        System.out.println("示例 2 (useForNull): " + useForNullResult);
        // 输出: Apple | N/A | Banana | Orange | N/A


        // 3. 追加到现有的 StringBuilder 或 Appendable (appendTo)
        StringBuilder builder = new StringBuilder("Selected fruits: ");
        Joiner.on(" + ")
                .skipNulls()
                .appendTo(builder, listWithNull);
        System.out.println("示例 3 (appendTo): " + builder.toString());
        // 输出: Selected fruits: Apple + Banana + Orange


        // 4. Map 拼接 (withKeyValueSeparator)
        Map<String, String> queryParams = new HashMap<>();
        queryParams.put("search", "guava");
        queryParams.put("page", "1");
        queryParams.put("lang", "zh");

        String queryString = Joiner.on("&")
                .withKeyValueSeparator("=")
                .join(queryParams);
        System.out.println("示例 4 (MapJoiner): " + queryString);
        // 输出: search=guava&page=1&lang=zh


        // 5. 拼接变长参数 / 离散对象
        String discreteResult = Joiner.on("-").useForNull("0").join("ID", 101, null, "ACTIVE");
        System.out.println("示例 5 (离散对象): " + discreteResult);
        // 输出: ID-101-0-ACTIVE
    }
}
java 复制代码
示例 1 (skipNulls): Apple, Banana, Orange
示例 2 (useForNull): Apple | N/A | Banana | Orange | N/A
示例 3 (appendTo): Selected fruits: Apple + Banana + Orange
示例 4 (MapJoiner): search=guava&page=1&lang=zh
示例 5 (离散对象): ID-101-0-ACTIVE

9、字符匹配与清洗器(CharMatcher)

com.google.common.base.CharMatcher 是 Guava 提供的字符匹配与判定工具。它将字符判定逻辑与字符串操作(过滤、保留、替换、修剪、计数等)完美解耦,相比正则表达式性能更高,且代码表达力极强。

9.1、核心API

9.1、常量与工厂方法(创建匹配器)

1. 预定义常量匹配器

常量/方法 匹配内容
CharMatcher.none() 不匹配任何字符
CharMatcher.any() 匹配任意字符
CharMatcher.whitespace() 匹配所有空白字符(空格、换行、制表符等)
CharMatcher.ascii() 匹配所有 ASCII 字符 (0x00 - 0x7F)
CharMatcher.javaDigit() 匹配 Java 语言定义的数字字符
CharMatcher.javaLetter() 匹配 Java 语言定义的字母字符
CharMatcher.javaLetterOrDigit() 匹配 Java 语言定义的字母或数字
CharMatcher.breakingWhitespace() 匹配可换行的空白字符

2. 自定义工厂方法

工厂方法 说明 示例
CharMatcher.is(char) 匹配指定字符 CharMatcher.is('a')
CharMatcher.isNot(char) 匹配非指定字符 CharMatcher.isNot('a')
CharMatcher.anyOf(CharSequence) 匹配给定字符集中的任意字符 CharMatcher.anyOf("aeiou")
CharMatcher.noneOf(CharSequence) 不匹配给定字符集中的任何字符 CharMatcher.noneOf("123")
CharMatcher.inRange(char, char) 匹配指定字符范围内的字符 CharMatcher.inRange('a', 'z')
CharMatcher.forPredicate(Predicate) 使用自定义断言创建匹配器 CharMatcher.forPredicate(c -> ...)
9.1.2、组合方法(逻辑运算)

CharMatcher 实例可以像布尔逻辑一样进行组合,构建复杂的匹配规则。

组合方法 说明 示例
negate() 取反(逻辑非) CharMatcher.is('a').negate()
and(CharMatcher) 取交集(逻辑与) matcher1.and(matcher2)
or(CharMatcher) 取并集(逻辑或) matcher1.or(matcher2)
9.1.3、操作方法(对字符串进行处理)

1. 判定与检索

方法 返回类型 说明
matchesAnyOf(CharSequence) boolean 序列中是否包含至少一个匹配字符
matchesAllOf(CharSequence) boolean 序列中的字符是否全部匹配
matchesNoneOf(CharSequence) boolean 序列中是否完全不包含匹配字符
countIn(CharSequence) int 统计匹配字符的数量
indexIn(CharSequence) int 返回第一个匹配字符的索引(未找到返回 -1)
indexIn(CharSequence, int) int 从指定位置开始查找第一个匹配字符的索引

2. 移除与留存

方法 返回类型 说明
removeFrom(CharSequence) String 移除所有匹配的字符
retainFrom(CharSequence) String 仅保留所有匹配的字符

3. 替换与折叠

方法 返回类型 说明
replaceFrom(CharSequence, char) String 将所有匹配字符替换为指定字符
replaceFrom(CharSequence, CharSequence) String 将所有匹配字符替换为指定字符串
collapseFrom(CharSequence, char) String 将连续匹配的字符序列压缩替换为单个字符
trimAndCollapseFrom(CharSequence, char) String 先执行 trimFrom,再执行 collapseFrom

4. 修剪(Trim)

方法 返回类型 说明
trimFrom(CharSequence) String 移除首尾匹配的字符
trimLeadingFrom(CharSequence) String 仅移除头部匹配的字符
trimTrailingFrom(CharSequence) String 仅移除尾部匹配的字符

9.2、使用示例

java 复制代码
import com.google.common.base.CharMatcher;

public class CharMatcherDetailExample {

    public static void main(String[] args) {

        // 1. 提纯/仅保留数字 (retainFrom)
        String phoneInput = "+86 138-1234-5678 (Mobile)";
        String digitsOnly = CharMatcher.javaDigit().retainFrom(phoneInput);
        System.out.println("示例 1 (提纯数字): " + digitsOnly);
        // 输出: 8613812345678


        // 2. 移除指定字符 (removeFrom)
        String rawText = "Hello, World! #2026";
        // 移除标点符号或特定符号
        String cleanText = CharMatcher.anyOf(",!#").removeFrom(rawText);
        System.out.println("示例 2 (移除特定符号): " + cleanText);
        // 输出: Hello World 2026


        // 3. 规整连续空格 (collapseFrom & trimAndCollapseFrom)
        String messySpacing = "   Guava   CharMatcher    is    awesome!   ";
        // 将连续的空格压缩为一个空格,并去掉首尾空格
        String normalized = CharMatcher.whitespace().trimAndCollapseFrom(messySpacing, ' ');
        System.out.println("示例 3 (压缩连续空格): " + normalized);
        // 输出: Guava CharMatcher is awesome!


        // 4. 组合匹配器 (or / inRange)
        // 匹配所有小写英文字母及数字
        CharMatcher lowerOrDigit = CharMatcher.inRange('a', 'z').or(CharMatcher.javaDigit());
        String mixedStr = "ABC-abc-123-XYZ";
        String retainedLowerAndDigit = lowerOrDigit.retainFrom(mixedStr);
        System.out.println("示例 4 (组合匹配): " + retainedLowerAndDigit);
        // 输出: abc123


        // 5. 字符计数与判定 (countIn / matchesAllOf)
        String passwordCandidate = "123456";
        int digitCount = CharMatcher.javaDigit().countIn(passwordCandidate);
        boolean isPureDigits = CharMatcher.javaDigit().matchesAllOf(passwordCandidate);
        System.out.println("示例 5 (计数与纯数字检查): 数字个数=" + digitCount + ", 是否全为数字=" + isPureDigits);
        // 输出: 数字个数=6, 是否全为数字=true


        // 6. 敏感信息脱敏/掩码 (replaceFrom)
        String cardNum = "4532-7512-8899-1234";
        // 将连字符以外的数字部分模糊化处理
        String masked = CharMatcher.javaDigit().replaceFrom(cardNum, '*');
        System.out.println("示例 6 (掩码替换): " + masked);
        // 输出: ****-****-****-****
    }
}
java 复制代码
示例 1 (提纯数字): 8613812345678
示例 2 (移除特定符号): Hello World 2026
示例 3 (压缩连续空格): Guava CharMatcher is awesome!
示例 4 (组合匹配): abc123
示例 5 (计数与纯数字检查): 数字个数=6, 是否全为数字=true
示例 6 (掩码替换): ****-****-****-****

10、命名规范转换(CaseFormat)

com.google.common.base.CaseFormat 是 Guava 提供的枚举类,用于在不同的命名规范(如驼峰命名、下划线命名、短横线分隔等)之间快速转换字符串。

CaseFormat 枚举常量

常量名 示例格式 说明 / 常见别名
LOWER_HYPHEN test-variable 小写短横线分隔(Kebab-case)
LOWER_UNDERSCORE test_variable 小写下划线分隔(Snake-case)
LOWER_CAMEL testVariable 小驼峰命名(lowerCamelCase)
UPPER_CAMEL TestVariable 大驼峰命名(PascalCase)
UPPER_UNDERSCORE TEST_VARIABLE 大写下划线分隔(常量命名规范)

10.1、核心API

方法签名 说明
to(CaseFormat targetFormat, String str) 将符合当前 CaseFormat 格式的字符串 str 转换为目标 targetFormat 格式
converterTo(CaseFormat targetFormat) 返回一个 Converter<String, String> 转换器实例,支持双向转换与函数式复用

10.2、使用示例

1. 直接转换格式(to 方法)

java 复制代码
import com.google.common.base.CaseFormat;

public class CaseFormatExample {
    public static void main(String[] args) {
        // 蛇形 (lower_underscore) -> 小驼峰 (lowerCamel)
        String camelResult = CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, "user_first_name");
        System.out.println(camelResult); // 输出: userFirstName

        // 蛇形 (lower_underscore) -> 大驼峰 (UpperCamel)
        String pascalResult = CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, "user_first_name");
        System.out.println(pascalResult); // 输出: UserFirstName

        // 小驼峰 (lowerCamel) -> 短横线 (lower-hyphen)
        String kebabResult = CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_HYPHEN, "orderDetailId");
        System.out.println(kebabResult); // 输出: order-detail-id

        // 小驼峰 (lowerCamel) -> 常量格式 (UPPER_UNDERSCORE)
        String constResult = CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, "maxRetryCount");
        System.out.println(constResult); // 输出: MAX_RETRY_COUNT
    }
}
java 复制代码
userFirstName
UserFirstName
order-detail-id
MAX_RETRY_COUNT

2. 使用转换器(converterTo 方法)

适用于需要重复使用同一种转换逻辑或配合流式 API 进行批量处理的场景:

java 复制代码
import com.google.common.base.CaseFormat;
import com.google.common.base.Converter;

public class ConverterExample {
    public static void main(String[] args) {
        // 创建"下划线转小驼峰"的转换器对象
        Converter<String, String> underscoreToCamel =
                CaseFormat.LOWER_UNDERSCORE.converterTo(CaseFormat.LOWER_CAMEL);

        // 正向转换:下划线 -> 小驼峰
        String camel = underscoreToCamel.convert("create_time");
        System.out.println(camel); // 输出: createTime

        // 反向转换:小驼峰 -> 下划线
        String underscore = underscoreToCamel.reverse().convert("updateTime");
        System.out.println(underscore); // 输出: update_time
    }
}
java 复制代码
createTime
update_time

11、字符串处理(Strings)

com.google.common.base.Strings 是 Guava 提供的轻量级字符串处理工具类,主要用来简化 null / 空字符串的判断与转换、固定长度填充、重复拼接以及前缀后缀比对等常见操作。

11.1、核心API

方法名 说明
isNullOrEmpty(String string) 判断字符串是否为 null 或空字符串 ""
nullToEmpty(String string) 若为 null 则转换为 "",否则返回原值
emptyToNull(String string) 若为空字符串 "" 则转换为 null,否则返回原值
padStart(String string, int minLength, char padChar) 前缀填充:若长度小于 minLength,在开头补齐 padChar
padEnd(String string, int minLength, char padChar) 后缀填充:若长度小于 minLength,在结尾补齐 padChar
repeat(String string, int count) 重复拼接原字符串 count 次
commonPrefix(CharSequence a, CharSequence b) 获取两字符串的最长公共前缀
commonSuffix(CharSequence a, CharSequence b) 获取两字符串的最长公共后缀
lenientFormat(String template, Object... args) 宽容格式化:类似 String.format 但参数缺失或异常时绝不抛出 Exception

11.2、使用示例

java 复制代码
import com.google.common.base.Strings;

public class StringsExample {
    public static void main(String[] args) {
        // 1. null 与空字符串转换 & 判断
        System.out.println(Strings.isNullOrEmpty(null));      // true
        System.out.println(Strings.isNullOrEmpty(""));        // true
        System.out.println(Strings.isNullOrEmpty(" "));       // false(注意:空白字符不视为空)

        System.out.println(Strings.nullToEmpty(null));        // ""
        System.out.println(Strings.emptyToNull(""));          // null

        // 2. 字符串填充 (常用于生成固定位数的订单号/流水号)
        System.out.println(Strings.padStart("42", 5, '0'));   // "00042"
        System.out.println(Strings.padEnd("API", 6, '!'));    // "API!!!"
        System.out.println(Strings.padStart("123456", 4, '0'));// "123456" (超长时保持原样)

        // 3. 字符串重复
        System.out.println(Strings.repeat("=-", 5));          // "=-=-=-=-=-"

        // 4. 公共前缀与后缀提取
        String path1 = "/usr/local/bin/java";
        String path2 = "/usr/local/etc/nginx";
        System.out.println(Strings.commonPrefix(path1, path2)); // "/usr/local/"

        String file1 = "UserServiceTest.java";
        String file2 = "OrderServiceTest.java";
        System.out.println(Strings.commonSuffix(file1, file2)); // "ServiceTest.java"

        // 5. 宽容格式化 (常用于日志打印,即使参数数量对不上也不会报错)
        String formatted = Strings.lenientFormat("User %s logged in at %s", "Alice"); 
        System.out.println(formatted); // "User Alice logged in at %s [1 unverified arg]"
    }
}
java 复制代码
true
true
false

null
00042
API!!!
123456
=-=-=-=-=-
/usr/local/
erServiceTest.java
User Alice logged in at %s

12、其它工具

12.1、Converter(双向类型转换器)

  • 用于定义双向的类型转换逻辑,继承自 Function,不仅支持前向转换(convert(A)),还支持通过 .reverse() 自动获取反向转换器。
  • 常见内置实现如 Ints.stringConverter(),可实现字符串与 Integer 之间的双向安全互转。

12.2、Ascii(ASCII 字符处理)

  • 专门针对 ASCII 字符及字符串的高性能大小写转换与判定。
  • 它不受 JVM 本地化环境(Locale)的影响,执行效率显著高于 JDK 标准的 String.toLowerCase() 或 Character 方法。
  • 核心 API:Ascii.toLowerCase(str)、Ascii.toUpperCase(str)、Ascii.isLowerCase©。

12.3、Enums(枚举工具类)

  • 用于安全地操作枚举类型,避免直接使用 Enum.valueOf() 时因找不到枚举常量而抛出 IllegalArgumentException。
  • 核心 API:Enums.getIfPresent(MyEnum.class, "VALUE") 会返回一个 Optional,若枚举值不存在则优雅返回空,极适合解析外部传入的配置或前端参数。

12.4、Functions 工具扩展

  • Functions.identity():返回输入对象本身的恒等函数。
  • Functions.forMap(Map<K, V> map):将一个普通的 Map 包装为 Function 接口,常用于集合转换映射。
  • Functions.compose(f, g):组合两个函数,实现链式调用。

12.5、Predicates 条件谓词扩展

  • Predicates.instanceOf(Class<?> type):判断对象是否为指定类型的实例。
  • Predicates.in(Collection<?> target):判断元素是否存在于指定集合中(类似于 SQL 的 IN 操作)。
  • Predicates.notNull():快速过滤所有非空元素的断言。

12.6、Suppliers 供应商扩展

  • Suppliers.memoize(Supplier delegate):将一个耗时的 Supplier 包装为线程安全的单次缓存(第一次调用时计算并保存结果,后续直接复用)。
  • Suppliers.memoizeWithExpiration(Supplier delegate, long duration, TimeUnit unit):支持带过期时间的缓存 Supplier,在多线程环境下到期后会自动重新触发计算。

13、常见替代方案与演进关系 (Java 8+ vs Guava)

随着 JDK 的升级,部分 Guava 基础工具已有原生实现,选型对照如下:

Guava 工具 JDK 推荐替代方案 建议
Objects.equal(a, b) java.util.Objects.equals(a, b) 优先使用 JDK
Objects.hashCode(a, b) java.util.Objects.hash(a, b) 优先使用 JDK
com.google.common.base.Optional java.util.Optional 优先使用 JDK(注意两者类不兼容)
Ordering java.util.Comparator (如 Comparator.comparing(...).thenComparing(...)) 简单排序用 JDK,复杂极值查找(leastOf/greatestOf)继续用 Guava
Preconditions java.util.Objects.requireNonNull 非空校验可用 JDK,复杂条件与状态校验继续用 Guava
相关推荐
用户8356290780511 小时前
使用 Python 管理 PDF 属性和元数据
后端·python
猎嘤一号2 小时前
【2026 最新】Windows 11 右键菜单还原为 Windows 10 经典样式:一条命令、原理、回退与新版说明
windows·python
用户8356290780512 小时前
使用 Python 在 Word 文档中创建自定义图表
后端·python
qq_369224332 小时前
ddraw.dll丢失是什么原因引起的?老游戏运行报错,多种可行修复手段汇总分享
windows·游戏·dll·dll修复·dll丢失·dll错误
BR_D2 小时前
Windows 下 nvm 安装与完整使用教程
windows·nodejs·nvm
kyrie_sakura3 小时前
python学习笔记11 -- 进程和线程
笔记·python·学习
Jialu.3 小时前
中文 NLP 模型部署实战:FastAPI 接口 + Streamlit 看板
人工智能·python·自然语言处理·fastapi
还是大剑师兰特4 小时前
Maven‑3.9.16 Windows 下载+安装+环境变量完整教程
java·windows·maven