JAVA基础-Steam
1,Optional
Java 8 中的 Optional
1.1,获取 Optional 的三个方法
1. of(value)
返回一个 Optional, value 不可以为 null
2. empty()
返回一个空的 Optional
3. ofNullable(value)
返回一个 Optional, 如果 value为 null,就是 empty(),否则是 of
1.2,实例方法
1. ifPresent(Consumer<? super T> consumer)
optional 实例非 null 执行 consumer
2. isPresent()
判断 optional 是否有值
3. get()
获取 optional 值
4. orElse(value)
optional 值为 null 时,返回 value
5. orElseGet(Supplier<? extends T> other)
optional 值为 null 时,返回 Supplier 的返回值
6. orElseThrow(Supplier<? extends X> exceptionSupplier)
optional 值为 null 时,抛出指定异常
7. map(Function<? super T, ? extends U> mapper)
使用 Function 函数式接口返回一个值
8. filter(Predicate<? super T> predicate)
断言返回 false 时 optional 为 null
2,Stream
- 中间操作:返回一个新的 stream,可以有 0 个或多个。
- 终端操作:Stream流执行完终端操作之后,无法再执行其他动作,否则会报状态异常,提示该流已经被执行操作或者被关闭,想要再次执行操作必须重新创建Stream流
2.1, 创建 Stream 的几种情景
1,Stream.of()/Stream.iterator()/Stream.generate()
Stream<Integer> streamSelf = Stream.of(1, 2, 3, 4); Stream<Integer> streamIterate = Stream.iterate(0, integer -> { System.out.println(integer); return integer + 2; }).limit(5); Stream<Object> streamGenerate = Stream.generate((Supplier<Object>) () -> Math.random()).limit(5);
2,array.stream()
Stream<Integer> streamArray = Arrays.stream(new Integer[]{1, 2, 3, 4, 5});
3,collection.stream()
Stream<Integer> streamCollection = Arrays.asList(1, 2, 3, 4).stream();
4,Files.lines/files.list()
Stream<String> streamFile = Files.lines(new File("C:\\Users\\admin\\Desktop\\test1.html").toPath()); Stream<Path> streamList = Files.list(new File("C:\\Users\\admin\\Desktop").toPath());
2.2,Stream Api
1. 判断匹配元素 *Match()
根据提供的断言判断元素匹配情况,返回 boolean 值。
//所有元素都匹配 boolean allMatch(Predicate<? super T> predicate); //任意一个匹配 boolean anyMatch(Predicate<? super T> predicate); //都不匹配 boolean noneMatch(Predicate<? super T> predicate);
example:
@Test public void test13(){ System.out.println( list.stream().allMatch(new Predicate<Dog>() { @Override public boolean test(Dog dog) { return dog.getSteps() > 5; } })); System.out.println(list.stream().anyMatch(new Predicate<Dog>() { @Override public boolean test(Dog dog) { return dog.getSteps() > 10; } })); System.out.println(list.stream().noneMatch(new Predicate<Dog>() { @Override public boolean test(Dog dog) { return dog.getSteps() > 10; } })); }
2. 统计 count()
统计元素数目
long count();
3. 去重 distinct()
Stream<T> distinct();
4. 过滤 filter()
Stream<T> filter(Predicate<? super T> predicate);
example:
System.out.println( list.stream().filter(new Predicate<Dog>() { @Override public boolean test(Dog dog) { return dog.getSteps() > 20; } }).collect(Collectors.toList()) );
5. 获取一个元素 findAny()/findFirst()
findAny() 和 findFirst() 都是从流中获取一个元素,返回 Optional。与 findFirst 不同的是,findAny() 然会任意一个,如果是并行流则可能是随机一个。
//获取随机一个 Optional<T> findAny(); //获取第一个 Optional<T> findFirst();
6. 扁平流处理 flatMap()
与 map() 类似,但是一般适用于双重列表,处理成一层列表,故称扁平流
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
example:
@Test public void test15(){ List<List<Integer>> lists = Arrays.asList( Arrays.asList(1, 2, 3, 4), Arrays.asList(5, 6, 7, 8), Arrays.asList(9, 11, 13) ); System.out.println( lists.stream().flatMap(new Function<List<Integer>, Stream<?>>() { @Override public Stream<?> apply(List<Integer> list) { return list.stream(); } }).collect(Collectors.toList()) ); }
与之类似的还有 flatMapToInt,flatMapToLong, flatMapToDouble,以 flatMapToInt 为例:
//比 flatMap() 麻烦亿点 System.out.println( lists.stream().flatMapToInt(new Function<List<Integer>, IntStream>() { @Override public IntStream apply(List<Integer> innerList) { return innerList.stream().mapToInt(new ToIntFunction<Integer>() { @Override public int applyAsInt(Integer value) { return value; } }); } }).collect(new Supplier<List<Integer>>() { @Override public List<Integer> get() { return new ArrayList<>(); } }, new ObjIntConsumer<List<Integer>>() { @Override public void accept(List<Integer> integers, int value) { integers.add(value); } }, new BiConsumer<List<Integer>, List<Integer>>() { @Override public void accept(List<Integer> integers, List<Integer> integers2) { integers.addAll(integers2); } }));
7. 遍历元素 foreach()
对流中每个元素进行处理
void forEach(Consumer<? super T> action);
example:
@Test public void test16(){ list.stream().forEach(new Consumer<Dog>() { @Override public void accept(Dog dog) { dog.setSteps(10000); } }); System.out.println(list); }
有一个 forEachOrdered() 方法,可以保证在并行流中也是按照顺序处理每个元素。
8. 长度限制 limit()
截取流中部分元素
Stream<T> limit(long maxSize);
example:
System.out.println( list.stream().limit(3).collect(Collectors.toList()) );
9. 元素转换 map()
将流中元素进行映射处理
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
example:
@Test public void test19(){ System.out.println( list.stream().map(new Function<Dog, Cat>() { @Override public Cat apply(Dog dog) { return new Cat(dog.getSteps(), dog.getName()); } }).collect(Collectors.toList()) ); }
mapToDouble(),mapToLong(),mapToInt() 差不多,不再赘述。
10,最大最小 max()/min()
获取元素中最大最小,返回的是一个 Optional 对象
Optional<T> max(Comparator<? super T> comparator); Optional<T> min(Comparator<? super T> comparator);
example:
@Test public void max(){ System.out.println( list.stream().max(new Comparator<Dog>() { @Override public int compare(Dog o1, Dog o2) { return o1.getSteps() - o2.getSteps(); } }).get() ); }
11,观察元素 peek()
用于在流的处理过程中提供一种观察元素的机制,而不会改变流中的元素。类似与 foreach(),但是不会改变元素数据。
Stream<T> peek(Consumer<? super T> action);
12,归并 reduce()
三个参数,接收一个初始值,一个映射函数(用来修改元素),和一个二元运算符,参考 Collectors.reducing()
<U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner);
example:
@Test public void reduce(){ System.out.println( list.stream().reduce(new BinaryOperator<Dog>() { @Override public Dog apply(Dog dog, Dog dog2) { return dog.getSteps()>dog2.getSteps()?dog:dog2; } }).get() ); }
13. 跳过元素 skip
Stream<T> skip(long n);
14. 排序 sort()
Stream<T> sorted(); Stream<T> sorted(Comparator<? super T> comparator);
14. 转换数组 toArray()
两个方法重载
//转换成 Object 数组 Object[] toArray(); //转换成固定泛型数组 <A> A[] toArray(IntFunction<A[]> generator);
example:
@Test public void toArray(){ Object[] objects = list.stream().toArray(); Dog[] dogs = list.stream().toArray(new IntFunction<Dog[]>() { @Override public Dog[] apply(int value) { return new Dog[value]; } }); for(Dog dog: dogs){ System.out.println(dog); } }
2.3, Stream 类 collect()
收集器,将流转换为其他形式。有两个重载方法
/** * 接收三个参数 * 第一个参数提供一个 目标类型的示例,如 ArrayList,HashSet 等。 * 第二个参数提供一个消费者(累加器),第一个方法参数就是上面参数提供的实例,第二个参数表示一个元素,就是 stream 流中的每个元素,重复调用直到流中元素全部添加到目标实例重。 * 第三个参数也是一个消费者(整合),好像并发时候才用得到。 */ <R> R collect(Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner); /** * 参数是一个 Collectors 类型实例 */ <R, A> R collect(Collector<? super T, A, R> collector);
第一个举个例子了解一下就好:
@Test public void testCollector(){ System.out.println( list.stream().collect( new Supplier<List<Dog>>() { @Override public List<Dog> get() { List<Dog> list = new ArrayList<>(); System.out.println( list ); return list; } }, new BiConsumer<List<Dog>, Dog>() { @Override public void accept(List<Dog> dogs, Dog dog) { System.out.println("accumulator"); dogs.add(dog); } }, new BiConsumer<List<Dog>, List<Dog>>() { @Override public void accept(List<Dog> dogs, List<Dog> dogs2) { System.out.println("combiner"); dogs.addAll(dogs2); } } )); }
我们一般使用的是第二个,通过 Collectors 提供一个 Collector 实例,下面列出常用的几种。
1. 转换为 Collection
//转换为 List public static <T> Collector<T, ?, List<T>> toList() { return new CollectorImpl<>((Supplier<List<T>>) ArrayList::new, List::add, (left, right) -> { left.addAll(right); return left; }, CH_ID); } //转换为 Set public static <T> Collector<T, ?, Set<T>> toSet() { return new CollectorImpl<>((Supplier<Set<T>>) HashSet::new, Set::add, (left, right) -> { left.addAll(right); return left; }, CH_UNORDERED_ID); } //转换为 collection public static <T, C extends Collection<T>> Collector<T, ?, C> toCollection(Supplier<C> collectionFactory) { return new CollectorImpl<>(collectionFactory, Collection<T>::add, (r1, r2) -> { r1.addAll(r2); return r1; }, CH_ID); }
example:
list.stream().collect(Collectors.toList()); list.stream().collect(Collectors.toSet()); list.stream().collect(Collectors.toCollection(new Supplier<Collection<Dog>>() { @Override public Collection<Dog> get() { return new ArrayList<Dog>(); } }));
2. 转换为对应 Map
转换为 map,一共三个重载方法,同时有三个 toConcurrentMap(),转换为 ConcurrentMap,参数与 toMap 对应。
/** * 三个重载 构造方法 * Function<? super T, ? extends K> keyMapper, key 的映射函数 * Function<? super T, ? extends U> valueMapper, value 的映射函数 * BinaryOperator<U> mergeFunction, 当key 冲突时,调用的合并方法 * Supplier<M> mapSupplier, Map 构造器,需要返回特定的 map 时使用 */ public static <T, K, U> Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper) { return toMap(keyMapper, valueMapper, throwingMerger(), HashMap::new); } public static <T, K, U> Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction) { return toMap(keyMapper, valueMapper, mergeFunction, HashMap::new); } public static <T, K, U, M extends Map<K, U>> Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction, Supplier<M> mapSupplier) { BiConsumer<M, T> accumulator = (map, element) -> map.merge(keyMapper.apply(element), valueMapper.apply(element), mergeFunction); return new CollectorImpl<>(mapSupplier, accumulator, mapMerger(mergeFunction), CH_ID); }
example:
list.stream().collect(Collectors.toMap( new Function<Dog, String>() { @Override public String apply(Dog dog) { return dog.getName(); } }, new Function<Dog, Dog>() { @Override public Dog apply(Dog dog) { return dog; } }, new BinaryOperator<Dog>() { @Override public Dog apply(Dog o1, Dog o2) { return o2; } }, new Supplier<Map<String, Dog>>() { @Override public Map<String, Dog> get() { return new TreeMap<String, Dog>(); } } ));
3,字符串拼接 joining
按照遇到的顺序拼接成一个字符串。这个只能对字符串的对象使用。
//直接拼接 public static Collector<CharSequence, ?, String> joining() { return new CollectorImpl<CharSequence, StringBuilder, String>( StringBuilder::new, StringBuilder::append, (r1, r2) -> { r1.append(r2); return r1; }, StringBuilder::toString, CH_NOID); } //元素间添加分隔符 public static Collector<CharSequence, ?, String> joining(CharSequence delimiter) { return joining(delimiter, "", ""); } //元素间添加分隔符,最后拼接前后缀 public static Collector<CharSequence, ?, String> joining(CharSequence delimiter, CharSequence prefix, CharSequence suffix) { return new CollectorImpl<>( () -> new StringJoiner(delimiter, prefix, suffix), StringJoiner::add, StringJoiner::merge, StringJoiner::toString, CH_NOID); }
example:
//123 System.out.println( Arrays.asList("1", "2", "3").stream().collect(Collectors.joining()) ); //1,2,3 System.out.println( Arrays.asList("1", "2", "3").stream().collect(Collectors.joining(",")) ); //#{1,2,3} System.out.println( Arrays.asList("1", "2", "3").stream().collect(Collectors.joining(",", "#{", "}")) );
4. 映射 mapping()
mapping() 方法,将流中元素映射为指定对象,与 stream().map() 有点类似。Collectors.mapping的作用是将流中的元素经过mapper函数映射后,再使用downstream收集器来收集这些映射结果。最终返回的结果是downstream收集器生成的结果。
/** * 两个参数: * 第一个参数:提供一个映射 mapper,将流中元素映射成指定对象 * 第二个参数:提供一个收集器,将映射后的元素返回一个指定收集器 */ public static <T, U, A, R> Collector<T, ?, R> mapping(Function<? super T, ? extends U> mapper, Collector<? super U, A, R> downstream) { BiConsumer<A, ? super U> downstreamAccumulator = downstream.accumulator(); return new CollectorImpl<>(downstream.supplier(), (r, t) -> downstreamAccumulator.accept(r, mapper.apply(t)), downstream.combiner(), downstream.finisher(), downstream.characteristics()); }
exapmle:
@Test public void test4(){ List<Cat> catList = list.stream().collect( Collectors.mapping( new Function<Dog, Cat>() { @Override public Cat apply(Dog dog) { return new Cat(dog.getSteps(), dog.getName()); } }, Collectors.toList() ) ); System.out.println(catList); }
5. 求平均数 averaging*
这样方法有三个, averagingDouble,averagingInt,averagingLong,以 averagingDouble 为例
public static <T> Collector<T, ?, Double> averagingDouble(ToDoubleFunction<? super T> mapper) { /* * In the arrays allocated for the collect operation, index 0 * holds the high-order bits of the running sum, index 1 holds * the low-order bits of the sum computed via compensated * summation, and index 2 holds the number of values seen. */ return new CollectorImpl<>( () -> new double[4], (a, t) -> { sumWithCompensation(a, mapper.applyAsDouble(t)); a[2]++; a[3]+= mapper.applyAsDouble(t);}, (a, b) -> { sumWithCompensation(a, b[0]); sumWithCompensation(a, b[1]); a[2] += b[2]; a[3] += b[3]; return a; }, a -> (a[2] == 0) ? 0.0d : (computeFinalSum(a) / a[2]), CH_NOID); }
example:
System.out.println( list.stream().collect( Collectors.averagingDouble(new ToDoubleFunction<Dog>() { @Override public double applyAsDouble(Dog value) { return value.getSteps(); } }) ) );
6. 最大最小( maxBy/minBy )
获取最大最小值,根据提供的比较器返回最大/最小元素
//最大值 public static <T> Collector<T, ?, Optional<T>> maxBy(Comparator<? super T> comparator) { return reducing(BinaryOperator.maxBy(comparator)); } //最小值 public static <T> Collector<T, ?, Optional<T>> minBy(Comparator<? super T> comparator) { return reducing(BinaryOperator.minBy(comparator)); }
example:
@Test public void test5(){ System.out.println( list.stream().collect(Collectors.maxBy(new Comparator<Dog>() { @Override public int compare(Dog o1, Dog o2) { return o1.getId() - o2.getId(); } }))); }
7. 求和 summing*
用于元素间求和,也是有 int, double, long 三种变形,以 int 为例:
public static <T> Collector<T, ?, Integer> summingInt(ToIntFunction<? super T> mapper) { return new CollectorImpl<>( () -> new int[1], (a, t) -> { a[0] += mapper.applyAsInt(t); }, (a, b) -> { a[0] += b[0]; return a; }, a -> a[0], CH_NOID); }
example:
@Test public void test7(){ Integer collect = list.stream().collect(Collectors.summingInt(new ToIntFunction<Dog>() { @Override public int applyAsInt(Dog value) { return value.getSteps(); } })); System.out.println(collect); }
8. 统计信息,summarizing*
用于计算流中元素的统计信息,例如计数、求和、最大值、最小值和平均值。有 summarizingDouble,summarizingInt,summarizingLong 三种变形
public static <T> Collector<T, ?, DoubleSummaryStatistics> summarizingDouble(ToDoubleFunction<? super T> mapper) { return new CollectorImpl<T, DoubleSummaryStatistics, DoubleSummaryStatistics>( DoubleSummaryStatistics::new, (r, t) -> r.accept(mapper.applyAsDouble(t)), (l, r) -> { l.combine(r); return l; }, CH_ID); }
example:
@Test public void test6(){ DoubleSummaryStatistics collect = list.stream().collect(Collectors.summarizingDouble(new ToDoubleFunction<Dog>() { @Override public double applyAsDouble(Dog value) { return value.getSteps(); } })); System.out.println( collect.getAverage() ); System.out.println( collect.getMax() ); System.out.println( collect.getCount() ); System.out.println( collect.getMin() ); System.out.println( collect.getSum() ); }
9. 归约 reducing
用于将流中的元素进行归约操作,也就是将一系列元素逐个处理并最终合并成一个结果。有三个方法重载如下:
//接收一个参数,参数规定了二元运算符,将每两个元素按照此规则进行归约,返回一个 Optional 。 public static <T> Collector<T, ?, Optional<T>> reducing(BinaryOperator<T> op) //接收一个初始值和一个上述的二元运算符。 public static <T> Collector<T, ?, T> reducing(T identity, BinaryOperator<T> op) //接收一个初始值,一个映射函数(用来修改元素),和一个二元运算符 public static <T, U> Collector<T, ?, U> reducing(U identity, Function<? super T, ? extends U> mapper, BinaryOperator<U> op)
example:
@Test public void test8(){ //一个参数 Optional<Dog> col = list.stream().collect(Collectors.reducing(new BinaryOperator<Dog>() { @Override public Dog apply(Dog dog, Dog dog2) { return dog.getSteps()>dog2.getSteps()?dog:dog2; } })); if( col.isPresent() ){ System.out.println( col.get() ); } //两个参数,带初始值 Dog yq = list.stream().collect(Collectors.reducing( new Dog(1, 100, "yq"), new BinaryOperator<Dog>() { @Override public Dog apply(Dog dog, Dog dog2) { return dog.getSteps()>dog2.getSteps()?dog:dog2; } } )); System.out.println( yq ); //三个参数,带初始值,元素映射规则 Dog yq1 = list.stream().collect(Collectors.reducing( new Dog(1, 100, "yq"), new Function<Dog, Dog>() { @Override public Dog apply(Dog s) { s.setSteps(s.getSteps() * 1000); return s; } }, new BinaryOperator<Dog>() { @Override public Dog apply(Dog dog, Dog dog2) { return dog.getSteps()>dog2.getSteps()?dog:dog2; } } )); System.out.println(yq1); }
10. 求总数 counting
public static <T> Collector<T, ?, Long> counting() { return reducing(0L, e -> 1L, Long::sum); }
example:
@Test public void test9(){ System.out.println( list.stream().collect(Collectors.counting()) ); }
11. 分组 groupingBy
将流中的元素按照某个属性分组,有三个方法重载
//一个参数,根据某个属性分组 public static <T, K> Collector<T, ?, Map<K, List<T>>> groupingBy(Function<? super T, ? extends K> classifier) { return groupingBy(classifier, toList()); } //两个参数,downstream 表示对分组后,每个组的操作 public static <T, K, A, D> Collector<T, ?, Map<K, D>> groupingBy(Function<? super T, ? extends K> classifier, Collector<? super T, A, D> downstream) { return groupingBy(classifier, HashMap::new, downstream); } //三个参数,mapFactory 表示分组运算结构的结果类型 public static <T, K, D, A, M extends Map<K, D>> Collector<T, ?, M> groupingBy(Function<? super T, ? extends K> classifier, Supplier<M> mapFactory, Collector<? super T, A, D> downstream) {
example:
@Test public void test11(){ //根据名字分组 System.out.println( list.stream().collect( Collectors.groupingBy( new Function<Dog, String>() { @Override public String apply(Dog dog) { return dog.getName(); } } ) )); //根据名字分组,并且每组再取 step 的最小值 System.out.println( list.stream().collect( Collectors.groupingBy( new Function<Dog, String>() { @Override public String apply(Dog dog) { return dog.getName(); } }, Collectors.reducing(new BinaryOperator<Dog>() { @Override public Dog apply(Dog dog, Dog dog2) { return dog.getSteps()>dog2.getSteps()?dog:dog2; } }) ) )); //根据名字分组,并且每组再取 step 的最小值,返回的是一个 HashMap 类型(获取每个分组最小的一个) System.out.println( list.stream().collect( Collectors.groupingBy( new Function<Dog, String>() { @Override public String apply(Dog dog) { return dog.getName(); } }, new Supplier<Map<String, Optional<Dog>>>() { @Override public Map<String, Optional<Dog>> get() { return new HashMap<>(); } }, Collectors.reducing(new BinaryOperator<Dog>() { @Override public Dog apply(Dog dog, Dog dog2) { return dog.getSteps()>dog2.getSteps()?dog:dog2; } }) ) )); }
12. 特殊分组 partitioningBy
用于根据指定的条件对流中的元素进行分区操作。分区操作将流中的元素分为满足条件和不满足条件两个部分,并分别收集到一个Map中。
有两个方法重载
//按照参数断言分成满足条件和不满足条件两个部分 public static <T> Collector<T, ?, Map<Boolean, List<T>>> partitioningBy(Predicate<? super T> predicate) { return partitioningBy(predicate, toList()); } //按照断言分组后,第二个参数的收集器进而对分组后的每个组进行操作。 public static <T, D, A> Collector<T, ?, Map<Boolean, D>> partitioningBy(Predicate<? super T> predicate, Collector<? super T, A, D> downstream) { }
example:
@Test public void test10(){ //根据断言分组 Map<Boolean, List<Dog>> collect = list.stream().collect(Collectors.partitioningBy(new Predicate<Dog>() { @Override public boolean test(Dog dog) { return dog.getSteps() % 2 == 0; } })); System.out.println(collect); //根据断言分组,再分别求和 System.out.println( list.stream().collect(Collectors.partitioningBy( new Predicate<Dog>() { @Override public boolean test(Dog dog) { return dog.getSteps() % 2 == 0; } }, Collectors.counting() ))); }
13. 收集完成后应用一个最终转换操作 collectingAndThen
方法签名如下:
public static<T,A,R,RR> Collector<T,A,RR> collectingAndThen(Collector<T,A,R> downstream, Function<R,RR> finisher) { }
该方法接受两个参数:downstream 和 finisher。
- downstream是一个内部收集器,用于执行实际的收集操作,它将元素类型T,累加器类型A,以及结果类型R作为泛型参数。
- finisher是一个Function,它将从downstream收集器返回的结果类型R转换为类型RR的结果。
example:
@Test public void test12(){ System.out.println( list.stream().collect(Collectors.collectingAndThen( //先将 list 收集成 List<String> Collectors.mapping(new Function<Dog, String>() { @Override public String apply(Dog t) { return t.getName(); } }, Collectors.toList()), //再将转换后的 List<String> 转换成 Set<String>,最终返回的结果就是转换后的 set new Function<List<String>, Set<String>>() { @Override public Set<String> apply(List<String> r) { HashSet<String> set = new HashSet<>(); set.addAll(r); return set; } } )) ); }
2.4,并行流
Stream<Object> parallelStream = new ArrayList<>().parallelStream(); Stream<Object> parallel = new ArrayList<>().stream().parallel();
本文作者:Hi.PrimaryC
本文链接:https://www.cnblogs.com/cnff/p/17071346.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步