java单列集合list和set

集合

数组的不足之处:
1.长度开始必须指定,而且一旦指定,不可修改
2.保存必须为同一类型的元素
3.使用数组进行增加元素比较麻烦
集合
1 可以动态保存任意多个对象,使用比较方便。
2 提供一系列方便的操作对象的方法:add remove set get等
3 使用集合添加 删除元素操作简单

集合框架

单列集合框架

在这里插入图片描述

双列集合

在这里插入图片描述

collection 接口

public interface Collection<E> extends Iterable<E>

特点:
1.collection实现子类可以存放多个元素,每个元素可以是Object。
2.有些collection的实现类 可以存放重复的元素,有些不可以。
3.有些collection的实现类 有些是有序的(list)有些不是有序的(set)
4.collection接口没事直接的实现子类,是通过他的子接口 List 和Set 来实现的
常用方法:
ArrayList为例

 List list = new ArrayList();
        list.add("hello");//list(new String("hello))
        list.add(10);//list(new Integer(10))
        list.add(false);
        list.remove(0);
        list.remove("hello");
        list.contains("hello"); //查找元素是否存在
        list.size(); //元素个数
        list.isEmpty(); //元素是否为空
        list.clear();//清空元素

        ArrayList list1 = new ArrayList();
        list1.add("一身转战三千里");
        list1.add("一剑曾当百万师");
        list.addAll(list1); //添加多个元素
        list.containsAll(list1); //判断多个元素存在
        list.removeAll(list1);//删除多个元素
        list.indexOf("hello");//元素在集合中第一次出现的位置
        list.lastIndexOf("hello"); //元素在集合中最后一次出现的位置
        list.set(1,"he");//指定index的元素替换
        list.subList(0,2);//返回子集合 0 <= index <2
collection接口遍历元素的方式1–使用Iterator

1.Iterator对象称为迭代器,主要用于遍历Collection集合中的元素
2.所有实现Collection接口的集合类都有iterator()方法,用于返回一个实现了Iterator接口的对象,即返回一个迭代器
3.Iterator仅用于遍历集合,本身不存放对象。

Iterator的执行原理

调用iterator.next()必须调用iterator.hasNext()

 		Iterator iterator = list.iterator();
 		//------->IDEA快捷键 itit回车
        while(iterator.hasNext()){
            System.out.println(iterator.next()); //指针下移 将下移以后集合位置上的元素返回
        }
	//while 退出后 迭代器指向最后一个元素
	//需要再次遍历则需要重置迭代器  iterator = list.iterator();
collection接口遍历元素的方式2–使用for循环增强

简化版的iterator,本质一样,只能用来遍历集合或者数组。
基本语法:
for(元素类型 元素名:集合或者数组名){
访问元素
}

        for (Object o : list) {
            System.out.println(o);
        }

List接口

List是Collection接口的子接口
1.List集合类的元素是有序的(添加顺序和去除的顺序是一致的),且可以重复
2.List集合中的每个元素都有其对应的顺序索引
3.List容器中的元素都对应一个整数型的序列号记载其在容器中的位置,可以根据序列号存取容器中的元素。
4.常用List的接口实现类,ArrayList 、 LinkedList 、Vector

List随机练习,Book按照价格排序遍历

 @SuppressWarnings("all")
    public static void main(String[] args) {
        List list = new ArrayList<Book>();
        list.add(new Book("罪与罚",30.3,"陀思妥耶夫斯基"));
        list.add(new Book("复活",40.4,"列夫托尔斯泰"));
        list.add(new Book("圆圈正义",20.7,"法外狂徒"));
        list.add(new Book("全国富婆通讯录",10000.0,"海贼王"));
//        list.sort(new Comparator() {
//            @Override
//            public int compare(Object o1, Object o2) {
//                Book o11 = (Book) o1;
//                Book o21 = (Book) o2;
//                return (int) (o11.getPrice()-o21.getPrice());
//            }
//        });
//        Iterator iterator = list.iterator();
//        while (iterator.hasNext()) {
//            Object next =  iterator.next();
//            System.out.println(next);
//        }
        System.out.println("==================================");
        sort02(list);
        for (Object o : list) {
            System.out.println(o);
        }
    }
    //冒个泡
    @SuppressWarnings("all")
    public static void sort02(List list){
        for (int i = 0; i < list.size()-1; i++) {
            for (int j = 0; j < list.size()-1-i; j++){
                Book book1 =(Book) list.get(j);
                Book book2 =(Book) list.get(j+1);
                if(book1.getPrice()> book2.getPrice()){
                    list.set(j,book2);
                    list.set(j+1,book1);
                }
            }
        }
    }
}

class Book{
    private String name;
    private Double Price;
    private String Author;

    public Book(String name, Double price, String author) {
        this.name = name;
        Price = price;
        Author = author;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Double getPrice() {
        return Price;
    }

    public void setPrice(Double price) {
        Price = price;
    }

    public String getAuthor() {
        return Author;
    }

    public void setAuthor(String author) {
        Author = author;
    }

    @Override
    public String toString() {
        return "Book{" +
                "name='" + name + '\'' +
                ", Price=" + Price +
                ", Author='" + Author + '\'' +
                '}';
    }
}
ArrayList的注意事项

1.可以放入多个null
2.是由数组来实现数据存储的
3.ArrayList基本等同于Vector,除了ArrayList是线程不安全的(执行效率高),多线程不建议使用ArrayList

ArrayList底层结构和源码分析

	1.ArrayList中维护了一个Object类型的elementData
	transient Object[] elementData; // 表示该属性不会被序列化
	public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    2.当创建ArrayList对象时,如果使用的是无参构造器,则 初始 elementData容量为0,第一次添加,则扩容elementData为10,如果需要再次扩容,则扩容为elementData的1.53.如果使用的指定大小的构造器,则初始 elementData容量为指定大小,如果需要再次扩容,则为elementData的1.5倍

分析:

 1. 使用无参构造器  new ArrayList();
 Object[] elementData ={};
 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
 public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
2.list.add
public boolean add(E e) {
        ensureCapacityInternal(size + 1);  //先确定是否要扩容
        elementData[size++] = e; //然后执行赋值
        return true;
    }
private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { 
            return Math.max(DEFAULT_CAPACITY, minCapacity); //第一扩容 DEFAULT_CAPACITY=10
        }
        return minCapacity;//第二次进来
    }

private void ensureExplicitCapacity(int minCapacity) { // minCapacity =10
        modCount++; //记录当前集合被修改的次数
        // overflow-conscious code
        if (minCapacity - elementData.length > 0) //当前elementData大小不够就grow扩容 第一次10-0>0 所以扩容 第二次 2-10 < 0 不扩容
            grow(minCapacity); // 底层扩容
    }
 private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length; //第一次为0
        int newCapacity = oldCapacity + (oldCapacity >> 1); //1.5倍扩容 第一次这里为0 
        if (newCapacity - minCapacity < 0) //0-10 < 0
            newCapacity = minCapacity; //newCapacity =10
        if (newCapacity - MAX_ARRAY_SIZE > 0) //
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity); //elementData 有10个大小 里面全为null
    }

如果debug看list数据不全的话 去掉下面的勾选
在这里插入图片描述

vector

1.Vector底层也是一个对象数组 protected Object[] elementData;
2.vector 线程同步

无参构造,默认是10,满后按照2倍扩容 指定大小直接2倍扩容

public Vector() {
        this(10);
    }
public synchronized boolean add(E e) {
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = e;
        return true;
    }
private void ensureCapacityHelper(int minCapacity) {
        // overflow-conscious code
        if (minCapacity - elementData.length > 0) //1-10
            grow(minCapacity);
    }
 private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + ((capacityIncrement > 0) ? //capacityIncrement =0
                                         capacityIncrement : oldCapacity); //2倍
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

LinkedList

1.实现了双向链表和双端队列的特点
2.可以添加任何元素,元素可以重复 可以是null
3.线程不安全

示意图:
LinkedList的元素添加和删除不是通过数组完成的,添加删除效率高
在这里插入图片描述

模拟简单的双向链表
public class homework01 {
    public static void main(String[] args) {
        Node node1 = new Node("悟空");
        Node node2 = new Node("八戒");
        Node node3 = new Node("沙僧");
        //node1-->node2-->node3
        node1.next = node2;
        node2.next = node3;
        //node3-->node2-->node1
        node3.pre = node2;
        node2.pre = node1;

        Node first = node1; //头结点
        Node last = node3; //尾结点

        //队伍添加一个师傅
        Node node = new Node("唐僧");
        first = node;
        node.next = node1;
        node1.pre = node;

        //队伍删除一头猪
        node1.next = node3;
        node3.pre = node1;


        //遍历 从头到尾
        while (true){
            if(first == null){
                break;
            }else {
                System.out.println(first);
                first = first.next;
            }
        }
        //遍历 从尾到头
        while (true){
            if(last == null){
                break;
            }else {
                System.out.println(last);
                last = last.pre;
            }
        }
    }
}

//双向链表的一个对象
class Node{
    public Object item; //存放数据
    public Node next; //下一个
    public Node pre; //前一个

    public Node(Object item) {
        this.item = item;
    }

    @Override
    public String toString() {
        return "item=" + item ;

    }
}

LinkedList源码分析

public LinkedList() {
    }
此时linkedlist属性 first = null  last = null 

public boolean add(E e) {
        linkLast(e);
        return true;
    }
void linkLast(E e) {
        final Node<E> l = last; // l = last = null
        final Node<E> newNode = new Node<>(l, e1, null); 
        last = newNode; //last->newNode 
        if (l == null)
            first = newNode; //first-->newNode
        else
            l.next = newNode;
        size++;
        modCount++;
    } //first-->Node1(null, e1, null)<--last
//第二次进来
void linkLast(E e) {
        final Node<E> l = last; //l-->node1
        final Node<E> newNode = new Node<>(l, e2, null); //nede2(node1,e2,null)
        last = newNode;//last->node2
        if (l == null)  //l= node1
            first = newNode;
        else
            l.next = newNode; //node1(null,e1,node2)
        size++;
        modCount++;
    }
//first(null,e1,node2)->(node1,e2,null) ->last


LinkedList.remove(); //删除第一个
public E removeFirst() {
        final Node<E> f = first; //
        if (f == null)
            throw new NoSuchElementException();
        return unlinkFirst(f);
    }
  private E unlinkFirst(Node<E> f) {
        // assert f == first && f != null;
        final E element = f.item; //first.item
        final Node<E> next = f.next; //指向第二个节点
        f.item = null; //第一个节点 item =null
        f.next = null; // 第一个节点 next = null 一二断了一半
        first = next; //first 指向第二节点
        if (next == null)
            last = null; //只有一个节点 空链表
        else //有2+个
            next.prev = null; //第二个节点的prev = null 一二节点断了另一半
        size--;
        modCount++;
        return element;
    }

    /**
     * Unlinks non-null last node l.
     */
    private E unlinkLast(Node<E> l) { //last
        // assert l == last && l != null;
        final E element = l.item; //last.item
        final Node<E> prev = l.prev; //prev 倒数第二个
        l.item = null;
        l.prev = null; // help GC //最后一个的prev指向倒数第二个断了
        last = prev; //last指向倒数第二个
        if (prev == null)
            first = null;
        else
            prev.next = null;//倒数第二个next指向最后一个断了
        size--;
        modCount++;
        return element;
    }
Arraylist 与LinkedList比较

ArrayList 底层 为可变数组,增删用的是数组的扩容效率低,改查的效率高
LinkedList 双向链表 增删效率高,改查的效率低。

两者之间的选择
1.改查较多 用ArrayList
2.增删较多 用Linkedlist
3.一般程序大多数是查询,大部分情况下用Array list
4.混合使用效率高
5.都是线程不安全的

set接口

set接口基本介绍

1 无序(添加顺序和取出顺序不一致,但是添加完的顺序是不会变的),没有索引
2 不允许重复元素 所以最多包含一个null
3 常用的 HashSet TreeSet

遍历

增强for 迭代器

HashSet

能存放一个null 不能存放重复元素 ,不保证存放和去除顺序一致

    private transient HashMap<E,Object> map;
    public HashSet() {
        map = new HashMap<>(); //底层是个hashMap
    }
HashSet使用示例

new class(); new String()有区别

 Set set = new HashSet();
        System.out.println(set.add("唐僧")); //T
        System.out.println(set.add("孙悟空")); //T
        System.out.println(set.add("唐僧")); //F

        set = new HashSet();
        set.add("hello");
        set.add("hello");
        System.out.println(set);//[hello]
        set.add(new Person("唐僧"));
        set.add(new Person("唐僧")); //person重写equals方法则可以只添加一个
        System.out.println(set); //[homework.Person@4554617c, hello, homework.Person@1b6d3586]
		/*
			class  Person{
			    public String name;
			
			    public Person(String name) {
			        this.name = name;
			    }
			
			    @Override
			    public boolean equals(Object o) {
			        if (this == o) return true;
			        if (o == null || getClass() != o.getClass()) return false;
			        Person person = (Person) o;
			        return Objects.equals(name, person.name);
			    }
			
			    @Override
			    public int hashCode() {
			        return Objects.hash(name);
			    }
			}
		*/

        set.add(new String("world"));
        set.add(new String("world"));
        System.out.println(set); // [world, homework.Person@4554617c, hello, homework.Person@1b6d3586]
		
		//hashSet添加元素到同一链表上
		//重写类的hashcode()方法 return 固定值
HashSet底层

HashSet的底层是HashMap,HashMap的底层是(数组+链表+红黑树)

数组链表模拟
public static void main(String[] args) {
        Node[] table = new Node[16];
        Node john = new Node("john", null);
        table[2] = john;
        Node jack = new Node("jack", null);
        john.next = jack; //jack挂载到john
        Node rose = new Node("rose", null);
        jack.next = rose;
        Node lucy = new Node("lucy", null);
        table[3] = lucy;
    }
class Node{
    Object item;
    Node next;

    public Node(Object item, Node next) {
        this.item = item;
        this.next = next;
    }
}

在这里插入图片描述

HashSet底层添加元素

1.HashSet底层是HashMap
2.添加一个元素时,先得到hash值 --会转成—索引值
3.找到存储数据表table,看看索引位置是否有存放元素
4.没有直接写入
5 如果有,调用equals比较,如果相同,就放弃添加,如果不同,则添加到最后
6 在java8中,一条链表的元素个数超过8个 并且table>=64个 就会进化成红黑树
(链表元素超过8,table没到64会数组扩容)

HashSet 源码
		Set set = new HashSet();
        set.add("唐僧"); //T
        set.add("孙悟空"); //T
        set.add("唐僧"); //F
        
      public boolean add(E e) { //e ="唐僧"
        return map.put(e, PRESENT)==null; //private static final Object PRESENT = new Object();
    }
	public V put(K key, V value) { // value = PRESENT
	        return putVal(hash(key), key, value, false, true);
    }
	 static final int hash(Object key) {
	        int h;
	        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
	    }

	//重点
	final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
	                   boolean evict) {
	        Node<K,V>[] tab; Node<K,V> p; int n, i; //辅助变量
	        //transient Node<K,V>[] table; = null
	        if ((tab = table) == null || (n = tab.length) == 0)
	            n = (tab = resize()).length; //执行完resize() tab就16
	        if ((p = tab[i = (n - 1) & hash]) == null) //根据key得到hash值去计算存放table的索引位置,这个位置的对象赋给P
	            tab[i] = newNode(hash, key, value, null); //为空 没存放过元素直接newNode
	        else {
	            Node<K,V> e; K k;
	            if (p.hash == hash && //当前索引位置的链表的第一个元素hash值和准备添加的key的hash值一样
	                ((k = p.key) == key || (key != null && key.equals(k)))) //准备加入的key和p指向的node节点的key(此时都是“唐僧”) 或者p指向节点node的key的equals和加入的key比较 
	               //此时“唐僧”可能(k = p.key) == key 相同了
	               //或者 new class出来 == 不符合,但是如果重写class的equals方法可以保证key.equals(k)
	                e = p;
	            else if (p instanceof TreeNode) //加入不满足 然后判断p是不是红黑树 是则putTreeVal添加
	                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
	            else { //当前位置存放的是链表
	                for (int binCount = 0; ; ++binCount) { //死循环比较
	                    if ((e = p.next) == null) { //依次和链表的没一个元素比较后,都不相同,则加到链表最后
	                        p.next = newNode(hash, key, value, null);
	                        //元素添加到链表的最后 判断是否达到8个
	                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st   TREEIFY_THRESHOLD =8
	                            treeifyBin(tab, hash); //当前的链表数化 table.length<64 不会马上数化 会扩容
	                        break;
	                    }
	                    if (e.hash == hash &&
	                        ((k = e.key) == key || (key != null && key.equals(k))))
	                        break; //比较链表中有相同退出
	                    p = e; //p->p.next 向后移
	                }
	            }
	            if (e != null) { // existing mapping for key
	                V oldValue = e.value;
	                if (!onlyIfAbsent || oldValue == null)
	                    e.value = value;
	                afterNodeAccess(e);
	                return oldValue; //存在相同的就返回oldValue
	            }
	        }
	        ++modCount;
	        if (++size > threshold) //>12 扩容 只要是添加元素就算size+1  不管是不是添加链表
	            resize();
	        afterNodeInsertion(evict); //hashMap子类实现的方法
	        return null; //成功添加返回null
	    }

//========================
final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table; //第一次进来 table = null
        int oldCap = (oldTab == null) ? 0 : oldTab.length; //oldCap = 0
        int oldThr = threshold; //oldThr =0
        int newCap, newThr = 0;
        if (oldCap > 0) {
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold
        }
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults
            newCap = DEFAULT_INITIAL_CAPACITY; //newCap =16
            //newThr  = (int)0.75*16 临界值 16个空间用了12个就开始扩容了 
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
           
        }
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        threshold = newThr;
        @SuppressWarnings({"rawtypes","unchecked"})
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab; //table开辟16个空间
        if (oldTab != null) {
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    if (e.next == null)
                        newTab[e.hash & (newCap - 1)] = e;
                    else if (e instanceof TreeNode)
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { // preserve order
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
                            next = e.next;
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }

hashset示例
  public static void main(String[] args) {
        HashSet hashSet = new HashSet();
        for (int i = 0; i < 12; i++) { //第一次table=16,链表元素第9个 table=32 10 table=64 11 树化
            hashSet.add(new A(i));
        }
    }

class A{
    private int n;

    public A(int n) {
        this.n = n;
    }

    @Override
    public int hashCode() {
        return 1;
    }
}

LinkedHashSet

1.LinkedHashSet 是HashSet的子类
2.LinkedHashSet 底层是一个LinkedHashMap 底层维护的是数组+双向链表
3.根据hashcode值决定元素存储的位置,同时使用链表维护元素的次序,使得元素看起来是以插入顺序保存的
4.不能添加相同元素

说明
1.LinkedHashSet中维护了一个hash表和双向链表(有head 和tail)
2.每一个节点都有before和after属性,这样可以形成双向链表
3.添加一个元素时,先求hash值,再求索引确定元素在table的位置,然后添加的元素加入到双向链表中,添加原理和HashSet一样
//添加元素示例
tail.next = newElement;
newElement.prev = tail;
tail = newElement
4.遍历 由于双向链表 保证插入的顺序和遍历顺序一致

内部结构示意图

		LinkedHashSet hashSet = new LinkedHashSet();
        hashSet.add(1);
        hashSet.add(2);
        hashSet.add(3);

在这里插入图片描述
在这里插入图片描述
Entry 是Node的子类 LinkedHashMap中
在这里插入图片描述

TreeSet

底层还是TreeMap

	    Set treeSet = new TreeSet(new Comparator() { //TreeMap comparator
            @Override
            public int compare(Object o1, Object o2) {
                return ((String)o1).compareTo((String) o2);
            }
        });
        treeSet.add("hello");
        treeSet.add("world");
	 public V put(K key, V value) {
        Entry<K,V> t = root;
        if (t == null) {
            compare(key, key); // type (and possibly null) check

            root = new Entry<>(key, value, null);
            size = 1;
            modCount++;
            return null;
        }
        int cmp;
        Entry<K,V> parent;
        // split comparator and comparable paths
        Comparator<? super K> cpr = comparator; //自定义的comparator
        if (cpr != null) {
            do {
                parent = t;
                cmp = cpr.compare(key, t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else 
                    return t.setValue(value); //comparator return 0 不会添加
            } while (t != null);
        }
        else {
            if (key == null)
                throw new NullPointerException();
            @SuppressWarnings("unchecked")
                Comparable<? super K> k = (Comparable<? super K>) key;
            do {
                parent = t;
                cmp = k.compareTo(t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
        Entry<K,V> e = new Entry<>(key, value, parent);
        if (cmp < 0)
            parent.left = e;
        else
            parent.right = e;
        fixAfterInsertion(e);
        size++;
        modCount++;
        return null;
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值