黑马程序员_集合框架

------- android培训java培训、java学习型技术博客、期待与您交流! ----------

集合框架

数组和集合
数组虽然也可以存储对象,但长度是固定的;集合长度可变的。

数组中可以存储基本数据类型,集合只能存储对象。可以存储不同类型的对象。

1、集合框架

集合框架图


图 1-1


java.util包
Collection
├List
│├LinkedList
│├ArrayList
│└Vector
│ 
└Set
   ├HashSet
   └TreeSet
Map
├Hashtable
├HashMap
└TreeMap

1.1 Collection接口

Collection是最基本的集合接口,一个Collection代表一组Object,即Collection的元素(Elements)。
Collection定义了集合框架的共性方法。
1.添加
add(e);
addAll(collection);
2.删除
remove(e);
removeAll(collection);
clear();
3.判断
contains(e);
isEmpty();
4.获取
iterator();
size();
5.获取交集
retainAll();
6.集合变数组
toArray();
7.迭代
iterator();
集合可以采用高级for循环和迭代取出集合中的每一个元素。
Iterator it = collection.iterator(); // 获得一个迭代子
while(it.hasNext()) {
   Object obj = it.next(); // 得到下一个元素
}
for(Iterator it=collection.iterator;it.hasNext();)
{
it.next();
}
for(Object o : colleciton)
{
}

1.2 List&Set

Collection接口派生的两个接口List和Set
Collection
|--List:元素是有序的,元素可以重复。因为该集合体系有索引。
|--ArrayList:底层的数据结构使用的是数组结构。特点:查询速度很快。但是增删稍慢。线程不同步。
|--LinkedList:底层使用的链表数据结构。特点:增删速度很快,查询稍慢。线程不同步。
|--Vector:底层是数组数据结构。线程同步。被ArrayList替代了。因为效率低。

|--Set:元素是无序,元素不可以重复。

1.2.1 List

List中特有的方法。凡是可以操作角标的方法都是该体系特有的方法。
1.增
add(index,element);
addAll(index,Collection);
2.删
remove(index);
3.改
set(index,element);
4.查
get(index);
subList(from,to);
listIterator();特有迭代
indexOf(obj);获取指定元素的位置
并发修改异常:ConcurrentModdificationException异常
Iterator方法是有限的,只能对元素进行判断,取出,删除的操作,如果想要其他操作,可以使用ListIterator。
List集合特有的迭代器:列表迭代器ListIterator,可以并发修改

ArrayList:底层的数据结构是数组结构,查询快,增删稍慢。ArrayList实现了可变大小的数组。它允许所有元素,包括null。ArrayList没有同步。每个ArrayList实例都有一个容量(Capacity),即用于存储元素的数组的大小。这个容量可随着不断添加新元素而自动增加,默认大小是10。如果元素的数目超出了内部数组目前的长度,会自动扩展容器的长度,增长是原来的50%。
private transient Object[] elementData;
public ArrayList(int initialCapacity) {
    super();
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
    this.elementData = new Object[initialCapacity];
}
	
// 空构造函数,默认容量大小为10
public ArrayList() {
    this(10);
}


public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    size = elementData.length;
    // c.toArray might (incorrectly) not return Object[] (see 6260652)
    if (elementData.getClass() != Object[].class)
        elementData = Arrays.copyOf(elementData, size, Object[].class);
}
练习:
import java.util.*;

/*
去除ArrayList集合中的重复元素。

*/

class ArrayListTest 
{
	public static void sop(Object obj)
	{
		System.out.println(obj);
	}
	public static void main(String[] args) 
	{
		ArrayList al = new ArrayList();

		al.add("java01");
		al.add("java02");
		al.add("java01");
		al.add("java02");
		al.add("java01");
//		al.add("java03");
		/*
		在迭代时循环中next调用一次,就要hasNext判断一次。
		Iterator it = al.iterator();

		while(it.hasNext())
		{
			sop(it.next()+"...."+it.next());
		}
		*/

		/**/
		sop(al);
		
		al = singleElement(al);

		sop(al);
		

	}
	public static ArrayList singleElement(ArrayList al)
	{
		//定义一个临时容器。
		ArrayList newAl = new ArrayList();

		Iterator it = al.iterator();

		while(it.hasNext())
		{
			Object obj = it.next();

			if(!newAl.contains(obj))
				newAl.add(obj);

		}

		return newAl;
	}
}

/*
对字符串排序。
"zxcvbasdfg"
思路:
1,字符串变数组。
2,数组排序。
3,数组变字符串。

*/
import java.util.*;
class  Test
{
	public static void main(String[] args) 
	{
		String str = "zxcvbasdfg";

//		char[] arr = str.toCharArray();
//		Arrays.sort(arr);
//		System.out.println(new String(arr));
//		str = sortString(str);
//		System.out.println(str);
	}

	public static String sortString(String str)
	{
		char[] arr = stringToArray(str);

		sort(arr);

		return arrayToString(arr);
	}

	private static char[] stringToArray(String str)
	{
		return str.toCharArray();
	}

	private static void sort(char[] arr)
	{
		for(int x=0; x<arr.length-1; x++)
		{
			for(int y=x+1; y<arr.length; y++)
			{
				if(arr[x]>arr[y])
				{
					swap(arr,x,y);
				}
			}
		}
	}
	private static void swap(char[] arr,int x,int y)
	{
		char temp = arr[x];
		arr[x] = arr[y];
		arr[y] = temp;
	}

	private static String arrayToString(char[] arr)
	{
		return new String(arr);
	}
}

LinkedList:底层的数据结构是链表结构,查询慢,增删快。LinkedList实现了List接口,允许null元素。此外LinkedList提供额外的get,remove,insert方法在 LinkedList的首部或尾部。这些操作使LinkedList可被用作堆栈(stack),队列(queue)或双向队列(deque)。注意LinkedList没有同步方法。如果多个线程同时访问一个List,则必须自己实现访问同步。一种解决方法是在创建List时构造一个同步的List:

List list = Collections.synchronizedList(new LinkedList(...));

特有方法:
addFirst();
addLast();
getFirst();
getLast();
获取元素,但是不删除元素。如果集合中没有元素,会出现NoSuchElementException
removeFirst();
removeLast();
获取元素,但是元素被删除。如果集合中没有元素,会出现NoSuchElementException
JDK1.6出现替代方法。
offerFirst();

offerLast();

peekFirst();
peekLast();
获取元素,但是不删除元素。如果集合中没有元素,会返回null.
pollFirst();
pollLast();
获取元素,但是元素被删除。如果集合中没有元素,会返回null.

模拟功能练习:

先进先出(队列)功能:
import java.util.LinkedList;

public class DuiLie {

	private LinkedList l;
	DuiLie(){
		l = new LinkedList();
	}
	public void myAdd(Object obj){
		l.addFirst(obj);
	}
	public Object myGet(){
		return l.removeLast();
	}
	public boolean isNull(){
		return l.isEmpty();
	}
}

(堆栈)后进先出:
public class DuiZhan {

	private LinkedList l;
	DuiZhan(){
		l = new LinkedList();
	}
	public void myAdd(Object obj){
		l.addFirst(obj);
	}
	public Object myGet(){
		return l.removeFirst();
	}
	public boolean isNull(){
		return l.isEmpty();
	}
}

Vector:底层是数组结构,是线程同步的。默认是10,Vector缺省情况下自动增长原来一倍的数组长度。
枚举就是Vector特有的取出方式。
枚举:Enumeration en = vector.elements();

定义类对象相同条件需要在该类中复写equals方法。
List集合判断元素是否相同,根据equals方法。

1.2.2 Set

Set:元素无序,存入和取出的顺序不一样,元素不可以重复。
HashSet:底层数据结构式哈希表,按照哈希值存入。
如果哈希值一样(hashCode),再比较是否是同一个对象(equals)
保证元素唯一性依赖hashCode和equals(Object obj)。如果元素的HashCode值相同,才会判断equals是否为true。如果元素的hashcode值不同,不会调用equals。
对于判断元素是否存在和删除等操作,都依赖hashCode和equals方法。

TreeSet:底层是二叉树,保证元素唯一性的依据,compareTo方法或者compare方法;
TreeSet排序的第种方式:让元素自身具备比较性。元素需要实现Comparable接口,覆盖compareTo方法。也种方式也成为元素的自然顺序,或者叫做默认顺序。
TreeSet的第种排序方式。当元素自身不具备比较性时,或者具备的比较性不是所需要的。这时就需要让集合自身具备比较性。在集合初始化时,就有了比较方式。
有一定顺序,可以对Set集合进行排序。

存入对象需要实现Comparable接口,使元素具有比较性。
String实现了Comparable接口
Integer实现了Comparable接口
如果元素不具备比较性呢或者想改变元素的比较性。
这时需要让集合自身具备比较性。
在集合初始化时,就有了比较性。
TreeSet(Comparator<T> c)实现Comparator接口的compare();
或者Collections类的sort方法也可以实现排序;或者reverse方法反转集合中元素的顺序。reverseOrder方法返回一个比较器,它强行逆转指定比较器的顺序。


练习:

/*
练习:按照字符串长度排序。

字符串本身具备比较性。但是它的比较方式不是所需要的。

这时就只能使用比较器。

*/

import java.util.*;
class  TreeSetTest
{
	public static void main(String[] args) 
	{
		TreeSet ts = new TreeSet(new StrLenComparator());

		ts.add("abcd");
		ts.add("cc");
		ts.add("cba");
		ts.add("aaa");
		ts.add("z");
		ts.add("hahaha");

		Iterator it = ts.iterator();

		while(it.hasNext())
		{
			System.out.println(it.next());
		}
	}
}

class StrLenComparator implements Comparator
{
	public int compare(Object o1,Object o2)
	{
		String s1 = (String)o1;
		String s2 = (String)o2;

		/*
		if(s1.length()>s2.length())
			return 1;
		if(s1.length()==s2.length())
			return 0;
			*/

			

		int num = new Integer(s1.length()).compareTo(new Integer(s2.length()));
		if(num==0)
			return s1.compareTo(s2);

		return num;
	}
}

/*
"90 -7 0 18 2 45 4"

将字符串中的数值进行排序。使用TreeSet完成。

思路
1,将字符串切割。
2,可以将这些对象存入TreeSet集合。因为TreeSet自身具备排序功能。


*/
import java.util.*;
class TreeSetTest2 
{
	public static void main(String[] args) 
	{

		ArrayList al = new ArrayList();

		String str = "90 -7 0 18 2 45 4";

		String[] arr = str.split(" ");

		TreeSet ts = new TreeSet();

		for(int x=0; x<arr.length; x++)
		{
			//ts.add(new Integer(arr[x]));
			ts.add(Integer.parseInt(arr[x]));//
		}

		System.out.println(ts);
	}
}

总结
如果涉及到堆栈、队列等操作,应该考虑用List;
对于需要快速插入,删除元素,应该使用LinkedList;
如果需要快速随机访问元素,应该使用ArrayList。

如果程序在单线程环境中,或者访问仅仅在一个线程中进行,考虑非同步的类,其效率较高,
如果多个线程可能同时操作一个类,应该使用同步的类。


要特别注意对哈希表的操作,作为key的对象要正确复写equals和hashCode方法。
尽量返回接口而非实际的类型,如返回List而非ArrayList,这样如果以后需要将ArrayList换成LinkedList时,客户端代码不用改变,这就是针对抽象编程。

2、泛型

泛型:用于解决安全问题。
通过<>来定义要操作的引用数据类型。
将运行时期的问题转移到了编译时期。
泛型类:
class Demo<T>{}
泛型方法:
public <T> void set(T t){}
泛型类和泛型方法不冲突
静态方法不可以访问类上定义的泛型,
public static <T> void set(T t){}
泛型定义在接口上:
interface Inter<T>{}
泛型限定:
<?>
子类
<? extends E> 上限
<? super E> 下限

3、Map

Map没有继承Collection接口,Map提供key到value的映射。一个Map中不能包含相同的key,
每个key只能映射一个 value。Map接口提供3种集合的视图,Map的内容可以被当作一组key集合,
一组value集合,或者一组key-value映射。
Map<K,V>集合
  |--Hashtable,不允许存入null键和null值。线程同步。jdk1.0,效率低;
  |--HashMap
  |--TreeMap
Hashtable:
Hashtable继承于Dictionary字典,实现Map接口,完成一个key-value映射的哈希表。任何非空(non-null)的对象都可作为key或者value。
添加数据使用put(key, value),取出数据使用get(key),这两个基本操作的时间开销为常数。
HashMap:
HashMap和Hashtable类似,不同之处在于HashMap是非同步的,并且允许null,即null value和null key。
但是将HashMap视为Collection时(values()方法可返回Collection),其迭代子操作时间开销和HashMap的容量成比例。因此,如果迭代操作的性能相当重要的话,不要将HashMap的初始化容量设得过高,或者load factor过低。
HashMap的构造实现:
static final int DEFAULT_INITIAL_CAPACITY = 16;		// 默认 initialCapacity = 16(2指数的整倍数)
	static final int MAXIMUM_CAPACITY = 1 << 30;		// 最大容量(向左位移30位而不是31位,是因为int最高位为符号位)
	static final float DEFAULT_LOAD_FACTOR = 0.75f;		// 默认 loadFactor = 0.75f
	
	transient Entry[] table;
	int threshold;
	final float loadFactor;
	
	public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)		// 超过最大容量时,则重置为最大容量
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " + loadFactor);

        // Find a power of 2 >= initialCapacity
        int capacity = 1;
        while (capacity < initialCapacity)
            capacity <<= 1;		// 容量大小以2的指数级增长

        this.loadFactor = loadFactor;
        threshold = (int)(capacity * loadFactor);
        table = new Entry[capacity];
        init();
    }
	
	void init() {
    }
	
	public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }
	
	public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
        table = new Entry[DEFAULT_INITIAL_CAPACITY];
        init();
    }
	
	public HashMap(Map<? extends K, ? extends V> m) {
        this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1, DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
        putAllForCreate(m);
    }

TreeMap:
对键进行排序,类似于TreeSet.

Set底层就是使用了Map集合

3.1 map集合的两种取出方式

1,Set<k> keySet:将map中所有的键存入到Set集合。因为set具备迭代器。
所有可以迭代方式取出所有的键,在根据get方法。获取每一个键对应的值。
Map集合的取出原理:将map集合转成set集合。在通过迭代器取出。
2,Set<Map.Entry<k,v>> entrySet:将map集合中的映射关系存入到了set集合中,
而这个关系的数据类型就是:Map.Entry

Entry其实就是Map中的一个static内部接口。
为什么要定义在内部呢?
因为只有有了Map集合,有了键值对,才会有键值的映射关系。
关系属于Map集合中的一个内部事物。
而且该事物在直接访问Map集合中的元素。
Map.Entry:映射关系。(内部类实现)
练习:
//将Map集合中的映射关系取出。存入到Set集合中。
		Set<Map.Entry<String,String>> entrySet = map.entrySet();

		Iterator<Map.Entry<String,String>> it = entrySet.iterator();

		while(it.hasNext())
		{
			Map.Entry<String,String> me = it.next();
			String key = me.getKey();
			String value = me.getValue();

			System.out.println(key+":"+value);

		}

		/*
		//先获取map集合的所有键的Set集合,keySet();
		Set<String> keySet = map.keySet();

		//有了Set集合。就可以获取其迭代器。
		Iterator<String> it = keySet.iterator();

		while(it.hasNext())
		{
			String key = it.next();
			//有了键可以通过map集合的get方法获取其对应的值。
			String value  = map.get(key);
			System.out.println("key:"+key+",value:"+value);
		}

		*/

map扩展知识:
循环嵌套。如一个学校有多个教室。每一个教室都有名称。
练习:
/*
每一个学生都有对应的归属地。
学生Student,地址String。
学生属性:姓名,年龄。
注意:姓名和年龄相同的视为同一个学生。
保证学生的唯一性。



1,描述学生。

2,定义map容器。将学生作为键,地址作为值。存入。

3,获取map集合中的元素。

*/

import java.util.*;
class Student implements Comparable<Student>
{
	private String name;
	private int age;
	Student(String name,int age)
	{
		this.name = name;
		this.age = age;
	}
	
	public int compareTo(Student s)
	{
		int num = new Integer(this.age).compareTo(new Integer(s.age));

		if(num==0)
			return this.name.compareTo(s.name);
		return num;
	}

	public int hashCode()
	{
		return name.hashCode()+age*34;
	}
	public boolean equals(Object obj)
	{
		if(!(obj instanceof Student))
			throw new ClassCastException("类型不匹配");

		Student s = (Student)obj;

		return this.name.equals(s.name) && this.age==s.age;
		

	}
	public String getName()
	{
		return name;
	}
	public int getAge()
	{
		return age;
	}
	public String toString()
	{
		return name+":"+age;
	}
}



class  MapTest
{
	public static void main(String[] args) 
	{
		HashMap<Student,String> hm = new HashMap<Student,String>();

		hm.put(new Student("lisi1",21),"beijing");
		hm.put(new Student("lisi1",21),"tianjin");
		hm.put(new Student("lisi2",22),"shanghai");
		hm.put(new Student("lisi3",23),"nanjing");
		hm.put(new Student("lisi4",24),"wuhan");

		//第一种取出方式 keySet

		Set<Student> keySet = hm.keySet();

		Iterator<Student> it = keySet.iterator();

		while(it.hasNext())
		{
			Student stu = it.next();
			String addr = hm.get(stu);
			System.out.println(stu+".."+addr);
		}


		//第二种取出方式 entrySet
		Set<Map.Entry<Student,String>> entrySet = hm.entrySet();

		Iterator<Map.Entry<Student,String>> iter = entrySet.iterator();
		
		while(iter.hasNext())
		{
			Map.Entry<Student,String> me = iter.next();
			Student stu = me.getKey();
			String addr = me.getValue();
			System.out.println(stu+"........."+addr);
		}
	}
}
-----------------------------------------------------------
/*
需求:对学生对象的年龄进行升序排序。

因为数据是以键值对形式存在的。
所以要使用可以排序的Map集合。TreeMap。
*/
import java.util.*;

class StuNameComparator implements Comparator<Student>
{
	public int compare(Student s1,Student s2)
	{
		int num = s1.getName().compareTo(s2.getName());
		if(num==0)
			return new Integer(s1.getAge()).compareTo(new Integer(s2.getAge()));

		return num;
	}
}


class  MapTest2
{
	public static void main(String[] args) 
	{
		TreeMap<Student,String> tm = new TreeMap<Student,String>(new StuNameComparator());

		tm.put(new Student("blisi3",23),"nanjing");
		tm.put(new Student("lisi1",21),"beijing");
		tm.put(new Student("alisi4",24),"wuhan");
		tm.put(new Student("lisi1",21),"tianjin");
		tm.put(new Student("lisi2",22),"shanghai");

		
		Set<Map.Entry<Student,String>> entrySet = tm.entrySet();

		Iterator<Map.Entry<Student,String>> it = entrySet.iterator();

		while(it.hasNext())
		{
			Map.Entry<Student,String> me = it.next();

			Student stu = me.getKey();
			String addr = me.getValue();
			System.out.println(stu+":::"+addr);
		}
	}
}
--------------------------------------------------------------
/*
练习:
"sdfgzxcvasdfxcvdf"获取该字符串中的字母出现的次数。

希望打印结果:a(1)c(2).....

通过结果发现,每一个字母都有对应的次数。
说明字母和次数之间都有映射关系。

注意了,当发现有映射关系时,可以选择map集合。
因为map集合中存放就是映射关系。


什么使用map集合呢?
当数据之间存在这映射关系时,就要先想map集合。

思路:
1,将字符串转换成字符数组。因为要对每一个字母进行操作。

2,定义一个map集合,因为打印结果的字母有顺序,所以使用treemap集合。

3,遍历字符数组。
	将每一个字母作为键去查map集合。
	如果返回null,将该字母和1存入到map集合中。
	如果返回不是null,说明该字母在map集合已经存在并有对应次数。
	那么就获取该次数并进行自增。,然后将该字母和自增后的次数存入到map集合中。覆盖调用原理键所对应的值。

4,将map集合中的数据变成指定的字符串形式返回。



*/
import java.util.*;
class  MapTest3
{
	public static void main(String[] args) 
	{
		String s= charCount("ak+abAf1c,dCkaAbc-defa");
		System.out.println(s);
	}
	
	public static String charCount(String str)
	{
		char[] chs = str.toCharArray();

		TreeMap<Character,Integer> tm = new TreeMap<Character,Integer>();

		
		int count = 0;
		for(int x=0; x<chs.length; x++)
		{
			

			if(!(chs[x]>='a' && chs[x]<='z' || chs[x]>='A' && chs[x]<='Z'))
				continue;

			Integer value = tm.get(chs[x]);

			
			if(value!=null)
				count = value;
			count++;
			tm.put(chs[x],count);//直接往集合中存储字符和数字,为什么可以,因为自动装箱。

			count = 0;
			/*
			if(value==null)
			{
				tm.put(chs[x],1);
			}
			else
			{
				value = value + 1;
				tm.put(chs[x],value);
			}
			*/


		}

		//System.out.println(tm);

		StringBuilder sb = new StringBuilder();

		Set<Map.Entry<Character,Integer>> entrySet = tm.entrySet();
		Iterator<Map.Entry<Character,Integer>>  it = entrySet.iterator();

		while(it.hasNext())
		{
			Map.Entry<Character,Integer> me = it.next();
			Character ch = me.getKey();
			Integer value = me.getValue();
			sb.append(ch+"("+value+")");
		}



		return sb.toString();
	}
}

4、工具类

Collections:专门用于对集合进行操作
Arrays:用于操作数据的操作类

binarySearch:如果不在集合中的元素则返回插入点负数减一。
reverseOrder:
可以将非同步的变成同步的。
shuffle:随机排放
asList:将数组变成List集合。
注意:将数组变成集合,不可以使用集合的增删方法。
如果增删了,会发生异常。

如果数组中的元素都是对象,那么变成集合时,数组中的元素 就直接转成集合中的元素。
如果数组中的元素都是基本数据类型,那么会将该数组作为集合中的元素存在。

集合变数组:
Collecions.toArray():
1、定义多长呢:
当指定类型的数组长度小于集合的长度,长度为集合的size。
大于集合数组,多余的会补null。
2、为什么要将集合变数组?
为了限定对元素的操作。不需要进行增删了。
数组变集合:Arrays  asList:将数组变成List集合。
好处:可以使用集合的思想和方法来操作数组中的元素。
注意:将数组变成集合,不可以使用集合的增删方法。因为数组的长度是固定的。
如果使用了会发生UnsupportedOperationException。
如果数组中的元素都是对象,那么变成集合时,数组中的元素就直接转成集合中的元素
如果数组中的元素都是基本数据类型,那么会将该数组作为集合中的元素存在。


增强for循环.
格式:
for(数据类型 变量名:数组或集合)
{
执行语句;
}
传统for和高级for区别:
高级for简化了对数组,集合的遍历。
传统for可以根据角标操作元素。


高级for遍历Map
for(Map.Entry<> entry : map)
{
}
可变参数;
一定要定义在参数列表的最后面。
格式:
返回值类型 函数名(参数类型... 形式参数)
{
执行语句;
}
静态导入:
import static java.util.Arrays.*;
导入的是Arrays这个类中所有的静态成员。
可以省略类型,直接使用方法名来调用。

------- android培训java培训、 java学习型技术博客、期待与您交流! ----------
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值