Guava学习(七):新的集合类型

主要介绍目录中这七种,下面分别介绍^_$。

Multiset

Multiset:它可以多次添加相等的元素,Multiset继承自JDK中的Collection接口,而不是Set接口,所以包含重复元素并没有违反原有的接口契约。

使用场景:一些需要计算元素个数的情况等。

可以用两种方式看待Multiset:

没有元素顺序限制的ArrayList
Map<E, Integer>,键为元素,值为计数

Guava的Multiset API也结合考虑了这两种方式:
当把Multiset看成普通的Collection时,它表现得就像无序的ArrayList:

add(E)添加单个给定元素
iterator()返回一个迭代器,包含Multiset的所有元素(包括重复的元素)
size()返回所有元素的总个数(包括重复的元素)

当把Multiset看作Map<E, Integer>时,它也提供了符合性能期望的查询操作:

count(Object)返回给定元素的计数。HashMultiset.count的复杂度为O(1),TreeMultiset.count的复杂度为O(log n)。
entrySet()返回Set<Multiset.Entry>,和Map的entrySet类似。
elementSet()返回所有不重复元素的Set,和Map的keySet()类似。
所有Multiset实现的内存消耗随着不重复元素的个数线性增长。

主要方法如下:在这里插入图片描述
请注意,Multiset不是Map<E, Integer>,虽然Map可能是某些Multiset实现的一部分。准确来说Multiset是一种Collection类型,并履行了Collection接口相关的契约。
Multiset可以看作是List和Map的结合,但都不是这两者,只是表现上类似这两者的结合,同时提供了一些计算元素个数等的方法。

主要实现:
在这里插入图片描述
使用示例:

 /**
     * Multiset适合用来创建可以包含多个元素及需要计算元素个数的这种场景的集合
     */
    private static void testMultiset() {
        //创建
        Multiset<String> multiset = HashMultiset.create();
        multiset.add("1");
        multiset.add("6");
        multiset.add("3");
        multiset.add("4");
        multiset.add("5");
        multiset.add("2");
        multiset.add("2");
        multiset.addAll(Lists.newArrayList(null, null));
        System.out.println(multiset);
        //size:返回包含的总个数
        System.out.println("size:" + multiset.size());
        //count:计算集合中出现某元素的个数
        System.out.println(multiset.count("2"));
        //打出有几个2
        multiset.stream().filter(s -> "2".equals(s)).forEach(System.out::println);
        Iterator<String> iterator = multiset.iterator();
        System.out.println("iterator:");
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
        //添加指定个数的某元素
        multiset.setCount("9", 3);
        multiset.setCount("2", 3);
        //设置某元素的个数,当某元素的旧个数等于指定的旧个数时设置才生效
        multiset.setCount("1", 2, 3); //不生效,因为原先"1"的个数不是2个
        multiset.setCount("3", 1, 3); //生效,原先"3"的个数是1个,设置后变成3个
        System.out.println(multiset);
        //包含方法 contains(是否包含单个元素)和containsAll(是否包含指定集合的所有元素)
        System.out.println(multiset.contains("abc"));
        System.out.println(multiset.containsAll(Lists.newArrayList("1", "a")));
        System.out.println(multiset.containsAll(Lists.newArrayList("1", "2")));

        //删除 remove(默认移除一个,也可以指定移除多少个),removeAll(移除集合中所有元素全部个数),还有一种移除方式是使用retainAll(下面有例子)
        multiset.remove("9");
        multiset.remove("2", 2);
        multiset.remove("a");
        multiset.removeAll(Sets.newHashSet("3", "5"));
        System.out.println(multiset);

        //elementSet:返回一个set,去掉重复元素
        Set<String> elementSet = multiset.elementSet();
        System.out.println(elementSet);

        //有类似Map的entrySet方法,可以获得元素及其个数
        for (Multiset.Entry<String> entry : multiset.entrySet()) {
            MoreObjects.ToStringHelper str = MoreObjects.toStringHelper("Multiset.Entry<String>")
                    .add("element", entry.getElement())
                    .add("count", entry.getCount());
            System.out.println(str);
        }

        //retainAll:保留集合中指定的元素,相当于其他的被删除掉了
        boolean b = multiset.retainAll(Lists.newArrayList("1", "3", "9"));
        System.out.println(b);
        System.out.println(multiset);

        //SortedMultiset,也可以用其他方法创建,TreeMultiset是SortedMultiset的子类
        SortedMultiset<Integer> sortedMultiset = TreeMultiset.create();
        sortedMultiset.addAll(Lists.newArrayList(1, 1, 3, 5, 6, 2, 6, 7, 2, 8, 11, 0, 0, 22, 21, 20, 15));
        //取区间子集
        SortedMultiset<Integer> sortedMultiset1 = sortedMultiset.subMultiset(5, BoundType.CLOSED, 21, BoundType.OPEN);
        System.out.println(sortedMultiset1);
        //从某个元素往前子集
        System.out.println(sortedMultiset.headMultiset(5, BoundType.CLOSED));
        //从某个元素往后的子集
        System.out.println(sortedMultiset.tailMultiset(5, BoundType.OPEN));
        //倒序
        SortedMultiset<Integer> descendingMultiset = sortedMultiset.descendingMultiset();
        System.out.println(descendingMultiset);
        System.out.println(descendingMultiset.headMultiset(5, BoundType.CLOSED));
        System.out.println(descendingMultiset.tailMultiset(5, BoundType.OPEN));
    }

输出结果:

[null x 2, 1, 2 x 2, 3, 4, 5, 6]
size:9
2
2
2
iterator:
null
null
1
2
2
3
4
5
6
[null x 2, 1, 2 x 3, 3 x 3, 4, 5, 6, 9 x 3]
false
false
true
[null x 2, 1, 2, 4, 6, 9 x 2]
[null, 1, 2, 4, 6, 9]
Multiset.Entry<String>{element=null, count=2}
Multiset.Entry<String>{element=1, count=1}
Multiset.Entry<String>{element=2, count=1}
Multiset.Entry<String>{element=4, count=1}
Multiset.Entry<String>{element=6, count=1}
Multiset.Entry<String>{element=9, count=2}
true
[1, 9 x 2]
[5, 6 x 2, 7, 8, 11, 15, 20]
[0 x 2, 1 x 2, 2 x 2, 3, 5]
[6 x 2, 7, 8, 11, 15, 20, 21, 22]
[22, 21, 20, 15, 11, 8, 7, 6 x 2, 5, 3, 2 x 2, 1 x 2, 0 x 2]
[22, 21, 20, 15, 11, 8, 7, 6 x 2, 5]
[3, 2 x 2, 1 x 2, 0 x 2]

Multimap

Multimap:把一个键映射到多个值,当你需要使用Map<K, List>或Map<K, Set>这种数据结构的使用,你可以使用Multimap来实现,它封装之后让我们更加容易使用。

可以用两种方式思考Multimap的概念:”键-单个值映射”的集合:

a -> 1 a -> 2 a ->4 b -> 3 c -> 5

或者”键-值集合映射”的映射:

a -> [1, 2, 4] b -> 3 c -> 5

一般来说,【Multimap接口应该用第一种方式看待】,但asMap()视图返回Map<K, Collection>,让你可以按另一种方式看待Multimap。重要的是,不会有任何键映射到空集合:一个键要么至少到一个值,要么根本就不在Multimap中。

在这里插入图片描述
各种实现:
在这里插入图片描述
使用例子:

    /**
     * Multimap可以很容易地把一个键映射到多个值。换句话说,Multimap是把键映射到任意多个值的一般方式
     * 特别注意:尽管Multimap的实现用到了Map,但Multimap<K, V>不是Map<K, Collection<V>>
     */
    private static void testMultimap() {
        ListMultimap<String, String> listMultimap = ArrayListMultimap.create();
        //添加两个方法put和putAll
        listMultimap.putAll("a", Lists.newArrayList("a1", "a2", "a3"));
        listMultimap.put("b", "b1");
        listMultimap.put("b", "b2");
        listMultimap.putAll("c", Lists.newArrayList("c1", "c1", "c3", "c5", "c5", "c5"));
        System.out.println(listMultimap);

        //获取值
        System.out.println(listMultimap.get("a"));
        System.out.println(listMultimap.get("c"));
        //获取不存在的键对应的值会返回空集合
        System.out.println(listMultimap.get("d"));

        //返回每个映射的个数,应为11而不是3
        System.out.println(listMultimap.size());
        System.out.println(listMultimap.keys().size());
        //获取不重复键的个数
        System.out.println(listMultimap.keySet().size());
        for (Map.Entry<String, String> entry : listMultimap.entries()) {
            String str = MoreObjects.toStringHelper("Map.Entry<String, String>")
                    .add("key", entry.getKey())
                    .add("value", entry.getValue()).toString();
            System.out.println(str);
        }
        //返回键的Multiset
        System.out.println("listMultimap.keys():" + listMultimap.keys());
        //返回键的Set
        System.out.println("listMultimap.keySet():" + listMultimap.keySet());
        //返回所有值的集合
        System.out.println("listMultimap.values():" + listMultimap.values());
        System.out.println(listMultimap.containsKey("a"));
        System.out.println(listMultimap.containsKey("d"));
        System.out.println(listMultimap.containsValue("a1"));
        System.out.println(listMultimap.containsValue("d1"));
        System.out.println(listMultimap.containsEntry("a", "a1"));
        System.out.println(listMultimap.containsEntry("a", "a5"));

        //转换成真正的Map<String, Collection<String>> ,这相当于是一个视图
        Map<String, Collection<String>> collectionMap = listMultimap.asMap();
        System.out.println("collectionMap.keySet():" + collectionMap.keySet());
        System.out.println("collectionMap.values():" + collectionMap.values());
        System.out.println(collectionMap);
        System.out.println(collectionMap.size());
        System.out.println(collectionMap.get("a"));
        //获取不存在的键对应的值会返回null
        System.out.println(collectionMap.get("d"));
        //put操作不能做,但是可以做remove,改变会映射到原来集合
//        collectionMap.put("d", Lists.newArrayList("d5", "d3", "d7"));
        //删除存在的键 返回被删除的对象
        System.out.println(collectionMap.remove("c"));
        //删除不存在的键 返回null
        System.out.println(collectionMap.remove("d"));
        System.out.println(collectionMap);
        System.out.println(listMultimap);

        //替换某个键的值,存在则替换,【不存在则添加】
        listMultimap.replaceValues("a", Lists.newArrayList("aa", "aaa"));
        listMultimap.replaceValues("d", Lists.newArrayList("dd"));
        System.out.println(listMultimap);
        //视图也会跟着变
        System.out.println(collectionMap);
        //移除
        listMultimap.removeAll("a");
        listMultimap.remove("b", "b1");
        listMultimap.put(null, null);
        System.out.println(listMultimap);
    }

输出结果:

{a=[a1, a2, a3], b=[b1, b2], c=[c1, c1, c3, c5, c5, c5]}
[a1, a2, a3]
[c1, c1, c3, c5, c5, c5]
[]
11
11
3
Map.Entry<String, String>{key=a, value=a1}
Map.Entry<String, String>{key=a, value=a2}
Map.Entry<String, String>{key=a, value=a3}
Map.Entry<String, String>{key=b, value=b1}
Map.Entry<String, String>{key=b, value=b2}
Map.Entry<String, String>{key=c, value=c1}
Map.Entry<String, String>{key=c, value=c1}
Map.Entry<String, String>{key=c, value=c3}
Map.Entry<String, String>{key=c, value=c5}
Map.Entry<String, String>{key=c, value=c5}
Map.Entry<String, String>{key=c, value=c5}
listMultimap.keys():[a x 3, b x 2, c x 6]
listMultimap.keySet():[a, b, c]
listMultimap.values():[a1, a2, a3, b1, b2, c1, c1, c3, c5, c5, c5]
true
false
true
false
true
false
collectionMap.keySet():[a, b, c]
collectionMap.values():[[a1, a2, a3], [b1, b2], [c1, c1, c3, c5, c5, c5]]
{a=[a1, a2, a3], b=[b1, b2], c=[c1, c1, c3, c5, c5, c5]}
3
[a1, a2, a3]
null
[c1, c1, c3, c5, c5, c5]
null
{a=[a1, a2, a3], b=[b1, b2]}
{a=[a1, a2, a3], b=[b1, b2]}
{a=[aa, aaa], b=[b1, b2], d=[dd]}
{a=[aa, aaa], b=[b1, b2], d=[dd]}
{null=[null], b=[b2], d=[dd]}

BiMap

BiMap:用来实现实现键值对的双向映射,这样就不用同时维护两个Map了
各种实现:
在这里插入图片描述
特别注意:
在BiMap中,如果你想把键映射到已经存在的值,会抛出IllegalArgumentException异常。如果对特定值,你想要强制替换它的键,请使用 BiMap.forcePut(key, value)。

使用例子:

	 /**
     * 实现键值对的双向映射
     */
    private static void testBimap() {
        BiMap<String, Integer> nameToAge = HashBiMap.create();
        nameToAge.put("a", 10);
        nameToAge.put("b", 20);
        nameToAge.putAll(ImmutableMap.of("c", 30, "d", 40));
        System.out.println(nameToAge);
        try {
            nameToAge.put("d", 10);
        } catch (IllegalArgumentException e) {
            System.out.println("不能把键映射到已经存在的值");
            e.printStackTrace();
        }
        //强制覆盖,这是后有值为10的键值对a-10会被覆盖掉,d-40也会变成d-10
        nameToAge.forcePut("d", 10);
        System.out.println(nameToAge);
        for (Map.Entry<String, Integer> entry : nameToAge.entrySet()) {
            String str = MoreObjects.toStringHelper("Map.Entry<String, Integer>")
                    .add("key", entry.getKey())
                    .add("value", entry.getValue())
                    .toString();
            System.out.println(str);
        }

        //反转
        BiMap<Integer, String> ageToName = nameToAge.inverse();
        System.out.println(ageToName);

    }

输出结果:

{b=20, a=10, d=40, c=30}
不能把键映射到已经存在的值
{b=20, d=10, c=30}
Map.Entry<String, Integer>{key=b, value=20}
Map.Entry<String, Integer>{key=d, value=10}
Map.Entry<String, Integer>{key=c, value=30}
java.lang.IllegalArgumentException: value already present: 10
	at com.google.common.collect.HashBiMap.put(HashBiMap.java:238)
	at com.google.common.collect.HashBiMap.put(HashBiMap.java:215)
	at com.bluedragon.guavalearning.newcollection.NewCollectionTest.testBimap(NewCollectionTest.java:202)
	at com.bluedragon.guavalearning.newcollection.NewCollectionTest.main(NewCollectionTest.java:24)
{20=b, 10=d, 30=c}

Table

Table:表格,就是一个两个键映射一个值的Map

Table有如下几种实现:
HashBasedTable:本质上用HashMap<R, HashMap<C, V>>实现;
TreeBasedTable:本质上用TreeMap<R, TreeMap<C,V>>实现;
ImmutableTable:本质上用ImmutableMap<R, ImmutableMap<C, V>>实现;注:ImmutableTable对稀疏或密集的数据集都有优化。
ArrayTable:要求在构造时就指定行和列的大小,本质上由一个二维数组实现,以提升访问速度和密集Table的内存利用率。ArrayTable与其他Table的工作原理有点不同,请参见Javadoc了解详情。

使用例子:

    /**
     * 使用两个键做索引,理解为表格,行和列可以确定唯一一个值
     */
    private static void testTable() {
        HashBasedTable<String, Integer, Object> hashBasedTable = HashBasedTable.create();
        hashBasedTable.put("a", 1, "a1");
        hashBasedTable.put("b", 2, "b2");
        hashBasedTable.put("c", 3, 3);
        hashBasedTable.put("d", 4, 4.0);
        hashBasedTable.put("d", 4, 5.0);
        System.out.println(hashBasedTable);
        //根据行和列获取值
        System.out.println(hashBasedTable.get("a", 1));
        System.out.println(hashBasedTable.get("a", 1.0));

        //获取某行
        System.out.println(hashBasedTable.row("b"));
        //获取行的所有键的集合
        System.out.println(hashBasedTable.rowKeySet());
        //获取行map
        Map<String, Map<Integer, Object>> rowMap = hashBasedTable.rowMap();
        System.out.println(rowMap);

        //获取某列
        System.out.println(hashBasedTable.column(3));
        //获取列的所有键的集合
        System.out.println(hashBasedTable.columnKeySet());
        //获取列map
        Map<Integer, Map<String, Object>> columnMap = hashBasedTable.columnMap();
        System.out.println(columnMap);

        Set<Table.Cell<String, Integer, Object>> cells = hashBasedTable.cellSet();
        for (Table.Cell<String, Integer, Object> cell : cells) {
            String str = MoreObjects.toStringHelper("Table.Cell<String, Integer, Object>")
                    .add("rowKey", cell.getRowKey())
                    .add("columnKey", cell.getColumnKey())
                    .add("value", cell.getValue())
                    .toString();
            System.out.println(str);
        }
    }

输出结果:

{a={1=a1}, b={2=b2}, c={3=3}, d={4=5.0}}
a1
null
{2=b2}
[a, b, c, d]
{a={1=a1}, b={2=b2}, c={3=3}, d={4=5.0}}
{c=3}
[1, 2, 3, 4]
{1={a=a1}, 2={b=b2}, 3={c=3}, 4={d=5.0}}
Table.Cell<String, Integer, Object>{rowKey=a, columnKey=1, value=a1}
Table.Cell<String, Integer, Object>{rowKey=b, columnKey=2, value=b2}
Table.Cell<String, Integer, Object>{rowKey=c, columnKey=3, value=3}
Table.Cell<String, Integer, Object>{rowKey=d, columnKey=4, value=5.0}

ClassToInstanceMap

ClassToInstanceMap:用类型来映射值的Map,它的键是类型,而值是符合键所指类型的对象(本身或子类或实现它的类(接口而言))。

为了扩展Map接口,ClassToInstanceMap额外声明了两个方法:T getInstance(Class) 和T putInstance(Class, T),从而避免强制类型转换,同时保证了类型安全。

使用例子:

    /**
     * 我们之所以使用ClassToInstanceMap而不是使用Map<Class, Object>,就是因为ClassToInstanceMap使用了MapConstraint,
     * 它保证了我们放入的Class和Object的类型是对应的, 而不会出现 put(Integer.class, "string")这样的情况.
     */
    private static void testClassToInstanceMap() {
        ClassToInstanceMap classToInstanceMap = MutableClassToInstanceMap.create();
        //必须存放对应类型,否则抛出异常
        classToInstanceMap.putInstance(Integer.class, 1);
        classToInstanceMap.putInstance(Double.class, 2.0);
        classToInstanceMap.putInstance(String.class, "3");
        classToInstanceMap.putInstance(String.class, "123");
        classToInstanceMap.put(Float.class, 5f);
        classToInstanceMap.putInstance(List.class, Lists.newArrayList("1", "2", "3"));
        System.out.println(classToInstanceMap);
        System.out.println(classToInstanceMap.get(Integer.class));
        System.out.println(classToInstanceMap.getInstance(Integer.class));
        System.out.println(classToInstanceMap.getInstance(List.class));


        ClassToInstanceMap<String> classToInstanceMap2 = MutableClassToInstanceMap.create();
//        classToInstanceMap2.putInstance(Integer.class, 1); //编译报错
        //只能放指定类型及其子类
        classToInstanceMap2.putInstance(String.class, "22");
        System.out.println(classToInstanceMap2);
    }

输出结果:

{class java.lang.String=123, class java.lang.Integer=1, class java.lang.Double=2.0, class java.lang.Float=5.0, interface java.util.List=[1, 2, 3]}
1
1
[1, 2, 3]
{class java.lang.String=22}

RangeSet

RangeSet:RangeSet描述了一组不相连的、非空的区间。当把一个区间添加到可变的RangeSet时,所有相连的区间会被合并,空区间会被忽略

使用例子:

    /**
     * 区间集合
     */
    private static void testRangeSet() {
        RangeSet<Integer> rangeSet = TreeRangeSet.create();
        rangeSet.add(Range.closed(1, 10));
        rangeSet.add(Range.closedOpen(15, 20));
        rangeSet.add(Range.open(25, 30));
        rangeSet.add(Range.openClosed(35, 50));
        System.out.println(rangeSet);
        //判断是否包含元素
        System.out.println(rangeSet.contains(15));
        System.out.println(rangeSet.contains(20));
        //返回包含元素的区间,没有则返回null
        System.out.println(rangeSet.rangeContaining(15));
        System.out.println(rangeSet.rangeContaining(20));
        //判断是否被其中的某个区间包含
        System.out.println(rangeSet.encloses(Range.closed(17, 19)));
        System.out.println(rangeSet.encloses(Range.closed(17, 20)));
        System.out.println(rangeSet.encloses(Range.closedOpen(17, 20)));
        System.out.println(rangeSet.encloses(Range.closed(17, 21)));
        //返回包括RangeSet中所有区间的最小区间。
        System.out.println(rangeSet.span());
        //返回所有区间set
        Set<Range<Integer>> ranges = rangeSet.asRanges();
        ranges.forEach(System.out::println);
        //如果加进去正好补全了中间某个区段,则这两个区间段合并成一个
        rangeSet.add(Range.closed(20, 27));
        System.out.println(rangeSet);
        //同理,移除的区间段如果在某个更大区间中,则会分割这个区间为两个
        rangeSet.remove(Range.open(22, 25));
        System.out.println(rangeSet);
        //取交集:返回被指定区间包含的所有区间
        System.out.println(rangeSet.subRangeSet(Range.closed(0, 23)));
        System.out.println(rangeSet.subRangeSet(Range.closed(0, 20)));
        //没有交集返回的就是空集
        System.out.println(rangeSet.subRangeSet(Range.closed(55, 66)));
        //取补集
        System.out.println(rangeSet.complement());


        //虽然也可以运行,但是正常都是用在数值上的吧
        RangeSet<String> stringRangeSet = TreeRangeSet.create();
        stringRangeSet.add(Range.open("a", "bb"));
        stringRangeSet.add(Range.closedOpen("1ab", "2cc"));
        System.out.println(stringRangeSet.complement());
        System.out.println(stringRangeSet.span());
//        System.out.println(stringRangeSet.subRangeSet(Range.closed("dsf", "1cc")));
    }

输出结果:

[[1‥10], [15‥20), (25‥30), (35‥50]]
true
false
[15‥20)
null
true
false
true
false
[1‥50]
[1‥10]
[15‥20)
(25‥30)
(35‥50]
[[1‥10], [15‥30), (35‥50]]
[[1‥10], [15‥22], [25‥30), (35‥50]]
[[1‥10], [15‥22]]
[[1‥10], [15‥20]]
[]
[(-∞‥1), (10‥15), (22‥25), [30‥35], (50‥+∞)]
[(-∞‥1ab), [2cc‥a], [bb‥+∞)]
[1ab‥bb)

RangeMap

RangeMap:RangeMap描述了”不相交的、非空的区间”到特定值的映射。和RangeSet不同,RangeMap不会合并相邻的映射,即便相邻的区间映射到相同的值

使用例子:

   private static void testRangeMap() {
        RangeMap<Integer, String> rangeMap = TreeRangeMap.create();
        rangeMap.put(Range.closedOpen(1, 10), "1-10");
        rangeMap.put(Range.closed(5, 12), "5-12");
        rangeMap.put(Range.closed(20, 23), "cc");
        rangeMap.put(Range.openClosed(33, 55), "dd");
        rangeMap.put(Range.openClosed(22, 35), "ff");
        System.out.println(rangeMap);
        System.out.println(rangeMap.get(5));
        System.out.println(rangeMap.get(6));
        Map.Entry<Range<Integer>, String> entry = rangeMap.getEntry(5);
        System.out.println(entry.getKey() + ":" + entry.getValue());
        //返回Map视图
        Map<Range<Integer>, String> rangeStringMap = rangeMap.asMapOfRanges();
        rangeStringMap.forEach((integerRange, s) -> {
            System.out.println(integerRange + "-->" + s);
        });
        //取交集
        System.out.println(rangeMap.subRangeMap(Range.open(11, 22)));
    }

输出结果:

[[1‥5)=1-10, [5‥12]=5-12, [20‥22]=cc, (22‥35]=ff, (35‥55]=dd]
5-12
5-12
[5‥12]:5-12
[1‥5)-->1-10
[5‥12]-->5-12
[20‥22]-->cc
(22‥35]-->ff
(35‥55]-->dd
{(11‥12]=5-12, [20‥22)=cc}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值