黑马程序员—java API:集合框架 & 其他

------<a href="http://www.itheima.com" target="blank">Java培训、Android培训、iOS培训、.Net培训</a>、期待与您交流! -------

集合:

 

(1)java是一种面向对象语言,如果我们要针对多个对象进行操作,就必须对多个对象进行存储。而对多个元素进行存储,前面我们学习过数组,数组的弊端,长度固定。这样,数组将不能满足变化的要求。所以,java就提供了集合(容器)供我们使用。

 

(2)集合的特点:

1、长度可以发生改变

2、只能存储对象

3、可以存储多种类型对象(一般存储的还是同一种)

 

(3)集合和数组的区别:

数组:

数组长度固定;

数组可以存储基本数据类型,也可以存储引用类型;

数组存储的数据类型是一样的。

 

集合:

集合长度可变;

集合只能存储引用类型;

集合可以存储不同类型的对象。

 

(4)集合体系的由来

集合是存储多个元素的容器,但是,由于数据结构不同,java就提供了多种集合类。

而这多种集合类有共性的功能,所以,通过不断的向上抽取,最终形成了集合体系

结构。

 

数据结构:数据存储的方式。

 

Collection

|--List(元素有序--指的是存储顺序和取出顺序是否一致),可重复。

|--ArrayList

|--Vector

|--LinkedList

|--Set(元素无序,唯一)

|--HashSet

|--TreeSet

 

List特点:

1. 有序(存储和取出的顺序一致)

2. 可以重复

3. 可以通过索引值操作对应位置的元素

 

List的特有功能:

1、添加功能

void add(int index,Object obj): 在指定位置添加元素。

2、删除功能

Object remove(int index): 根据指定索引删除元素,并把删除的元素返回。

3、修改功能

Object set(int index,Object obj): 在指定索引位置的元素修改为指定的值,返回修改前的值。

 

4、获取功能

Object get(int index): 返回指定元素在集合中第一次出现的索引。

int indexOf(Object obj): 获取指定位置的元素。

ListIterator listIterator():列表迭代器。

 

        5、截取功能:

           List subList(int fromIndex,int toIndex):j截取集合。

 

(5)如何学习和使用一个继承体系呢?

学习顶层:因为顶层定义的是共性内容。

使用底层:因为底层才是具体的实现。

 

 

Collection的功能:

1、添加功能

boolean add(Object obj):向集合中添加一个元素。

boolean addAll(Collection c):向集合中添加一个集合的元素。

 

2、删除功能:

void clear():删除集合中所有的元素。

boolean remove(Object obj):删除集合中指定的元素。

boolean removeAll(Collection c):删除集合中指定的集合元素。

 

3、判断功能:

  boolean isEmpty():判断集合是否为空。

boolean contains(Object obj):判断集合是否包含指定的元素。

boolean containsAll(Collection c):判断集合是否包含指定的集合中的元素。

 

4、遍历功能:

Iterator iterator():迭代器。

hasNext():判断是否还有元素

next():获取下一个元素

 

5、长度功能:

int size():获得集合的元素个数。

 

6、交集功能:

boolean retainAll(Collection c):判断集合中是否有相同的元素。

 

7、转换功能:

Object[] toArray():把集合变成数组。

 

注意:

所有带All的方法:

boolean addAll(Collection c):向集合中添加一个集合的元素。

boolean removeAll(Collection c):删除集合中指定的集合元素。

boolean containsAll(Collection c):判断集合是否包含指定的集合中的元素。

boolean retainAll(Collection c):判断集合中是否有相同的元素。

 

8、Iterator iterator():就是用来获取集合中每一个元素。

成员方法:

Object next():获取元素,并自动移动到下一个位置等候获取。

NoSuchElementException:异常(没有这样的元素异常)。

 

 

代码示例:

import java.util.*;
 
/*
Collection定义了集合框架的共性功能。
1,添加
add(e);
addAll(collection);
 
2,删除
remove(e);
removeAll(collection);
clear();
 
3,判断。
contains(e);
isEmpty();
 
4,获取
iterator();
size();
 
5,获取交集。
retainAll();
 
6,集合变数组。
toArray();
 
 
 
1,add方法的参数类型是Object。以便于接收任意类型对象。
 
2,集合中存储的都是对象的引用(地址)
 
 
*/
class  CollectionDemo
{
public static void main(String[] args) 
{
method_get();
}
public static void method_get()
{
ArrayList al = new ArrayList();
 
//1,添加元素。
al.add("java01");//add(Object obj);
al.add("java02");
al.add("java03");
al.add("java04");
 
/*
Iterator it = al.iterator();//获取迭代器,用于取出集合中的元素。
 
while(it.hasNext())
{
sop(it.next());
}
*/
 
for(Iterator it = al.iterator(); it.hasNext() ; )
{
sop(it.next());
}
}
 
 
public static void method_2()
{
ArrayList al1 = new ArrayList();
 
al1.add("java01");
al1.add("java02");
al1.add("java03");
al1.add("java04");
ArrayList al2 = new ArrayList();
 
al2.add("java03");
al2.add("java04");
al2.add("java05");
al2.add("java06");
 
//al1.retainAll(al2);//去交集,al1中只会保留和al2中相同的元素。
al1.removeAll(al2);
 
sop("al1:"+al1);
sop("al2:"+al2);
 
 
 
 
}
 
public static void base_method()
{
//创建一个集合容器。使用Collection接口的子类。ArrayList
ArrayList al = new ArrayList();
 
//1,添加元素。
al.add("java01");//add(Object obj);
al.add("java02");
al.add("java03");
al.add("java04");
 
//打印原集合。
sop("原集合:"+al);
 
 
//3,删除元素。
//al.remove("java02");
//al.clear();//清空集合。
 
 
//4,判断元素。
sop("java03是否存在:"+al.contains("java03"));
sop("集合是否为空?"+al.isEmpty());
 
 
//2,获取个数。集合长度。
sop("size:"+al.size());
 
//打印改变后的集合。
sop(al);
 
}
 
public static void sop(Object obj)
{
System.out.println(obj);
}
}


 

 

 

迭代器的使用:

1)迭代器:其实是集合的一种遍历方式。

2)集合的使用步骤:

A,创建集合对象。

B,创建元素对象。

C,把元素对象添加到集合对象中。

D,遍历集合对象:

通过集合对象获取迭代器对象。

通过迭代器对象的hasNext()方法进行判断。

通过迭代器的next()进行获取。

 

3)迭代器为什么定义为一个接口,而不是一个类?

不同的集合,由于数据结构不一样,所以他们的存储方式也有不一样,进而他们的获取方式也是不一样的;也就是说迭代器获取的时候,获取的方式也是变化的,也就是不固定的,所以,迭代器定义为接口,而不是一个类。

    无论是何种集合,他们肯定都有获取的功能,而且,如果不知道什么时候,就 没有数据了,所以,一般他们也会有判断功能,我们就把判断和获取功能抽取出来, 定义在接口中,这样的话,将来无论是那种集合在遍历的时候,他们自己只要去实 现迭代器接口即可。

4)怎么实现呢?

迭代器是以内部类的方式实现的。

5)迭代器并发修改异常(ConcurrentModificationException)的产生:

当我们通过迭代器迭代元素的过程中,又通过集合去添加了元素。这种情况是不允许的。因为迭代器是依赖于集合存在的,如果集合发生改变,迭代器也相应发生改变,而我们目前看到的确实,迭代器没变,集合变了,所以,爆出了一个并发修改异常。

注意问题:通过迭代器遍历集合的时候,是不能通过集合去操作(添加,删除)的。

我们可以这么理解:

A全部通过迭代器操作:元素是添加到刚遍历的那个元素后面。通过迭代器迭代的时候,可以通过迭代器对集合进行操作。

B全部通过集合操作:元素是添加到最后的。通过集合普通For遍历的时候,可以通过集合去操作。

 

代码示例:

package ArrayList_1;
 
import java.util.ArrayList;
import java.util.Iterator;
 
public class ArrayListDemo {
public static void main(String[] args) {
//创建对象
ArrayList al = new ArrayList();
al.add("呵呵哒");
al.add("萌萌哒");
al.add("嘿嘿哒");
sop(al);
//         用迭代器迭代
//Iterator<String> it = al.iterator();
//while (it.hasNext()) {
//String str = (String) it.next();
//sop(str);
//}
for (Iterator iterator = al.iterator(); iterator.hasNext();) {
String str = (String) iterator.next();
sop(str);
}
}
public static void sop(Object obj){
System.out.println(obj);
}
}


 

 

集合--list & 泛型:

 

1.数据结构:组织数据的方式。

 

2.常见的数据结构:栈,队列,链表,数组,树,图,堆...

(数据结构 算法//UMI+设计模式)

 

栈:先进后出(反转)

 

队列:先进先出。

 

 

数组:存储多个同一种元素的容器。

    特点:元素都有编号,方便获取。

数组的优缺点:

 优点:查询快;

 缺点:增删慢。

 

链表:把一些结点通过链子连接起来的数据结构。结点:由地址(指针)域和数值域组成。

    链表的优缺点:

 优点:增删快;

缺点:查询慢。

 

 

//同步--安全;eg:去银行办业务。

//同步--安全--效率低。

List:

代码示例:

/*

Collection

|--List:元素是有序的,元素可以重复。因为该集合体系有索引。

|--ArrayList:底层的数据结构使用的是数组结构。特点:查询速度很快。但是增删稍慢。线程不同步。

|--LinkedList:底层使用的链表数据结构。特点:增删速度很快,查询稍慢。线程不同步。

|--Vector:底层是数组数据结构。线程同步。被ArrayList替代了。因为效率低。

 

 

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

 

 

List

特有方法。凡是可以操作角标的方法都是该体系特有的方法。

 

add(index,element);

addAll(index,Collection);

 

remove(index);

 

set(index,element);

get(index):

subList(from,to);

listIterator();

int indexOf(obj):获取指定元素的位置。

ListIterator listIterator();

 

 

 

 

 

List集合特有的迭代器。ListIteratorIterator的子接口。

 

在迭代时,不可以通过集合对象的方法操作集合中的元素。

因为会发生ConcurrentModificationException异常。

 

所以,在迭代器时,只能用迭代器的放过操作元素,可是Iterator方法是有限的,

只能对元素进行判断,取出,删除的操作,

如果想要其他的操作如添加,修改等,就需要使用其子接口,ListIterator

 

该接口只能通过List集合的listIterator方法获取。

 

*/

 

 

import java.util.*;
class ListDemo 
{
public static void sop(Object obj)
{
System.out.println(obj);
}
public static void method()
{
ArrayList al = new ArrayList();
 
//添加元素
al.add("java01");
al.add("java02");
al.add("java03");
sop("原集合是:"+al);
//在指定位置添加元素。
al.add(1,"java09");
 
//删除指定位置的元素。
//al.remove(2);
 
//修改元素。
//al.set(2,"java007");
 
//通过角标获取元素。
sop("get(1):"+al.get(1));
 
sop(al);
 
//获取所有元素。
for(int x=0; x<al.size(); x++)
{
System.out.println("al("+x+")="+al.get(x));
}
 
Iterator it = al.iterator();
 
while(it.hasNext())
{
sop("next:"+it.next());
}
 
 
//通过indexOf获取对象的位置。
sop("index="+al.indexOf("java02"));
 
List sub = al.subList(1,3);
 
sop("sub="+sub);
}
 
public static void main(String[] args) 
{
 
//演示列表迭代器。
ArrayList al = new ArrayList();
 
//添加元素
al.add("java01");
al.add("java02");
al.add("java03");
 
sop(al);
 
ListIterator li = al.listIterator();
 
//sop("hasPrevious():"+li.hasPrevious());
 
while(li.hasNext())
{
Object obj = li.next();
 
if(obj.equals("java02"))
//li.add("java009");
li.set("java006");
 
 
}
 
while(li.hasPrevious())
{
sop("pre::"+li.previous());
}
//sop("hasNext():"+li.hasNext());
//sop("hasPrevious():"+li.hasPrevious());
 
 
sop(al);
 
 
 
/*
//在迭代过程中,准备添加或者删除元素。
 
Iterator it = al.iterator();
 
while(it.hasNext())
{
Object obj = it.next();
 
if(obj.equals("java02"))
//al.add("java008");
it.remove();//将java02的引用从集合中删除了。
 
sop("obj="+obj);
 
 
}
sop(al);
*/
 
 
}
}


 

1.List

    --ArrayList;

特点:

 底层数据结构是数组,查询快,增删慢。

 线程不安全,效率高。

    --Vector;

特点:

  底层数据结构是数组,查询快,增删慢。

  线程安全,效率高。

    --LinkedList;

特点:

 底层数据结构是链表,查询快,增删慢。

  线程不安全,效率高。

 

2.ArrayList操作自定义对象

--存储自定义对象去重复:

 

 

3.Vctor的特有功能:

添加:

    public void addElement(Object obj)-->add(Object obj)

获取:

    public Object elementAt(int index)-->get(int index)

    public Enumeration elements()-->Iterator

长度:

    public int size()

 

代码示例:

 

import java.util.*;
 
/*
枚举就是Vector特有的取出方式。
发现枚举和迭代器很像。
其实枚举和迭代是一样的。
 
因为枚举的名称以及方法的名称都过长。
所以被迭代器取代了。
枚举郁郁而终了。
 
 
 
*/
class VectorDemo 
{
public static void main(String[] args) 
{
Vector v = new Vector();
 
v.add("java01");
v.add("java02");
v.add("java03");
v.add("java04");
 
Enumeration en = v.elements();
 
while(en.hasMoreElements())
{
System.out.println(en.nextElement());
}
}

}

 

 

4.LinkedList的特有功能:

添加功能:

 void addFirst(Object e)-在集合第一个位置上添加元素。

 void addLast(Object e)-在集合最后一个位置上添加元素。

获取功能:

 Object getFirst()-获取集合中第一个元素。

  Object getLast()-获取集合中最后一个元素。

删除功能:

 Object removedFirst()-删除第一个元素并返回。

 Object removedLast()-删除最后一个元素并返回。

 

代码 示例:

import java.util.*;
 
/*
LinkedList:特有方法:
addFirst();
addLast();
 
getFirst();
getLast();
获取元素,但不删除元素。如果集合中没有元素,会出现NoSuchElementException
 
removeFirst();
removeLast();
获取元素,但是元素被删除。如果集合中没有元素,会出现NoSuchElementException
 
 
在JDK1.6出现了替代方法。
 
offerFirst();
offerLast();
 
 
peekFirst();
peekLast();
获取元素,但不删除元素。如果集合中没有元素,会返回null。
 
pollFirst();
pollLast();
获取元素,但是元素被删除。如果集合中没有元素,会返回null。
 
 
 
 
*/
 
class LinkedListDemo 
{
public static void main(String[] args) 
{
LinkedList link = new LinkedList();
 
link.addLast("java01");
link.addLast("java02");
link.addLast("java03");
link.addLast("java04");
 
//sop(link);
//	sop(link.getFirst());
//	sop(link.getFirst());
//sop(link.getLast());
//sop(link.removeFirst());
//sop(link.removeFirst());
 
//sop("size="+link.size());
 
while(!link.isEmpty())
{
sop(link.removeLast());
}
 
}
 
public static void sop(Object obj)
{
System.out.println(obj);
}
}
/*
 
---
java01
java02
java03
java04
 
----
java04
java03
java02
java01
---
*/


 

泛型:

1.泛型的概述:任意的类型。是一种把明确数据类型的工作放在了创建对象或者调用方法时候进行的特殊的类型。

 

2.泛型的格式:<数据类型>

 

3.为什么会有泛型?--就是为了解决黄线和类型转换问题。

 

4.怎么解决?--就是模仿数组解决的,在定义集合的时候,告诉集合,你只能存储什么类型的元素。(通过泛型告诉你)

 

5.泛型的好处:

a:解决黄色警线问题;

b:把运行期间的类型转换异常提前到了编译期间;

c:优化程序设计。

 

6.for循环的增强使用:

格式:for(数组或者Collection集合元素类型变量名:数组或者Collection集合对象)

使用变量名即可。

 }

作用:简化数组和Collection集合的变量。

 

注意:增强for和迭代器我们一般只选一种;增强for是来替代迭代器的

 

7.泛型应用的高级使用:

泛型类:把泛型定义在类上。

    注意:泛型接收的是引用类型。

 

泛型方法:把泛型加在方法上。

 

泛型接口:

    如果一个人接口有泛型,在实现的时候:

A,在实现类的写的时候,我已经知道接口是什么类型。

 

 

代码 示例:

 

import java.util.*;
 
/*
泛型:JDK1.5版本以后出现新特性。用于解决安全问题,是一个类型安全机制。
 
好处
1.将运行时期出现问题ClassCastException,转移到了编译时期。
方便于程序员解决问题。让运行时问题减少,安全。
 
2,避免了强制转换麻烦。
 
 
泛型格式:通过<>来定义要操作的引用数据类型。
 
在使用java提供的对象时,什么时候写泛型呢?
 
通常在集合框架中很常见,
只要见到<>就要定义泛型。
 
其实<> 就是用来接收类型的。
 
当使用集合时,将集合中要存储的数据类型作为参数传递到<>中即可。
 
 
 
 
 
*/
 
class GenericDemo 
{
public static void main(String[] args) 
{
 
ArrayList<String> al = new ArrayList<String>();
 
al.add("abc01");
al.add("abc0991");
al.add("abc014");
 
//al.add(4);//al.add(new Integer(4));
 
Iterator<String> it = al.iterator();//把数据装到迭代器,也加泛型限定,不仅编译通过,编译出现的注意提示也会不见;
while(it.hasNext())
{
String s = it.next();
 
System.out.println(s+":"+s.length());
}
}
}


 

 

Set集合:

 

1)Set特点:元素无序,唯一的。

  *注意:这里的顺序是指存储和取出顺序。

  *Set中的功能和collection一样,直接使用即可;又因为他是接口,所以需要找一个实现类HashSet

 

 

2) HashSet怎么保证元素的唯一性?

是通过元素的两个方法,复写hashCodeequals来完成

如果元素的HashCode值相同,才会判断equals是否为true

如果元素的hashcode值不同,不会调用equals

 

 

3)HashSet:不保证元素迭代顺序;并且,不保证该顺序恒久不变。

HashSet底层数据结构是哈希表。

     它依赖两个方法:hashCode()equals()

顺序:

首先,判断hashCode()值是否相同。

相同:

继续走equals()方法,根据其返回值:

true:说明元素重复,不添加到集合。

false:说明元素不重复,添加到集合。

不同:直接添加到集合。

 

4)怎么重写hashCode()equals()方法呢?

hashCode():

把对象的所有成员变量值相加即可。

如果是基本类型,就加值。如果是引用类型,就加哈希值。

equals():

A:this==obj

B:!(obj instanceof Student)

C:所有成员变量的值比较。基本类型用==,引用类型用equals()

 

如果不会,自动生成。

 

 

代码示例:

import java.util.*;
/*
|--Set:元素是无序(存入和取出的顺序不一定一致),元素不可以重复。、
|--HashSet:底层数据结构是哈希表。是线程不安全的。不同步。
HashSet是如何保证元素唯一性的呢?
是通过元素的两个方法,hashCode和equals来完成。
如果元素的HashCode值相同,才会判断equals是否为true。
如果元素的hashcode值不同,不会调用equals。
 
注意,对于判断元素是否存在,以及删除等操作,依赖的方法是元素的hashcode和equals方法。
 
 
|--TreeSet:
 
Set集合的功能和Collection是一致的。
 
 
 
 
*/
 
 
class HashSetDemo 
{
public static void sop(Object obj)
{
System.out.println(obj);
}
public static void main(String[] args) 
{
HashSet hs = new HashSet();
 
sop(hs.add("java01"));
sop(hs.add("java01"));
hs.add("java02");
hs.add("java03");
hs.add("java03");
hs.add("java04");
 
Iterator it = hs.iterator();
 
while(it.hasNext())
{
sop(it.next());
}
}
}


 

 

5)TreeSet:可以对元素进行排序;使用元素的自然顺序进行排序,或者根据创建set时提供的Comparator进行排序,具体取决于构造方法。

 

(1)TreeSet:根据构造方法的不用,选择使用自然排序或者比较器排序。

            按照实际的需求,可以对元素进行排序。并且保证元素有序和唯一。

(2)怎么保证的呢?

排序:底层结构是二叉树。按照树节点进行存储和取出。
两种实现:
A:自然排序(元素具备比较性)

TreeSet的无参构造,要求对象所属的类实现Comparable接口。

B:比较器排序(集合具备比较性)

TreeSet的带参构造,要求构造方法接收一个实现了Comparator接口的对象。

 

实现Comparator方式排序

 

通过treeset,提高比较效率;

大的数在右边,小的数在左边,默认取数时从左到右;

 

当元素自身不具备比较性,或者具备的比较性不是所需要的。

这时需要让容器自身具备比较性。

定义一个比较器,将比较器对象作为参数传递给TreeSet集合的构造函数。

 

当两种排序(元素自身具备比较性的排序和容器具备比较性的排序)都存在时,以比较器为主。

 

定义比较器:定义一个类,实现Comparator接口,覆盖compare方法。

 

注意:

compareable 接口,覆盖compareTo()方法;

Comparator接口,覆盖compare方法

字符串自身具备compareTo()方法

 

请看如下案例:

class Student implements Comparable//该接口强制让学生具备默认的比较性。
{
private String name;
private int age;
 
Student(String name,int age)
{
this.name = name;
this.age = age;
}
 
public int compareTo(Object obj)
{
 
//return 0;
if(!(obj instanceof Student))
throw new RuntimeException("不是学生对象");
Student s = (Student)obj;
 
//System.out.println(this.name+"....compareto....."+s.name);
if(this.age>s.age)
return 1;
if(this.age==s.age)
{
return this.name.compareTo(s.name);
}
return -1;
/**/
}
 
public String getName()
{
return name;
 
}
public int getAge()
{
return age;
}
}
class TreeSetDemo2 
{
public static void main(String[] args) 
{
TreeSet ts = new TreeSet();
// TreeSet ts = new TreeSet(new MyCompare());//加上比较器进行排序
ts.add(new Student("lisi02",22));
ts.add(new Student("lisi02",21));
ts.add(new Student("lisi007",20));
ts.add(new Student("lisi09",19));
ts.add(new Student("lisi06",18));
ts.add(new Student("lisi06",18));
ts.add(new Student("lisi007",29));
//ts.add(new Student("lisi007",20));
//ts.add(new Student("lisi01",40));
 
Iterator it = ts.iterator();
while(it.hasNext())
{
Student stu = (Student)it.next();
System.out.println(stu.getName()+"..."+stu.getAge());
}
}
}
 
class MyCompare implements Comparator//定义比较器进行排序
{
public int compare(Object o1,Object o2)
{
Student s1 = (Student)o1;
Student s2 = (Student)o2;
 
int num = s1.getName().compareTo(s2.getName());//字符串比较
if(num==0)//当主要条件相同,比较次要条件
{
//整数的对象包装类有比较方法,所以将整数封装成Integer;
return new Integer(s1.getAge()).compareTo(new Integer(s2.getAge()));
/*
if(s1.getAge()>s2.getAge())
return 1;
if(s1.getAge()==s2.getAge())
return 0;
return -1;
*/
}
 
return num;
 
}
}


 

 

 

 

  注意:在自然排序中,如何让保证排序?
根据返回值看:

--说明元素比以前的元素大,往后放。

--说明元素比以前的元素小,往前放。

O--元素不添加到集合中,这就是保证唯一性的原理。(

实现Comparable接口,覆盖compareTo方法。

注意:

如果同时有两种方案,以谁为主呢?以比较器为主。

 

6)TreeSet如何存储数据的?

规则:
   A 第一个添加的数据作为根节点。
   B 从第二个开始:

每一个数据从根节点开始比较,如果大了,往右边放,如果小了,往左边放,如果相同就替换。

 如何取得呢?

--从根节点开始,获取数据的规则,是按照每个数据的,左中右原则。

 

 

 

 

代码示例:
import java.util.*;

 

/*

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

|--HashSet:数据结构是哈希表。线程是非同步的。

保证元素唯一性的原理:判断元素的hashCode值是否相同。

如果相同,还会继续判断元素的equals方法,是否为true

 

|--TreeSet:可以对Set集合中的元素进行排序。

底层数据结构是二叉树。

保证元素唯一性的依据:

compareTo方法return 0.

 

TreeSet排序的第一种方式:让元素自身具备比较性。

元素需要实现Comparable接口,覆盖compareTo方法。

也种方式也成为元素的自然顺序,或者叫做默认顺序。

 

TreeSet的第二种排序方式。

当元素自身不具备比较性时,或者具备的比较性不是所需要的。

这时就需要让集合自身具备比较性。

在集合初始化时,就有了比较方式。

 

 

 

 

 

 

 

 

需求:

TreeSet集合中存储自定义对象学生。

想按照学生的年龄进行排序。

 

 

记住,排序时,当主要条件相同时,一定判断一下次要条件。

 

 

*/

 

class TreeSetDemo 
{
public static void main(String[] args) 
{
TreeSet ts = new TreeSet();
 
ts.add(new Student("lisi02",22));
ts.add(new Student("lisi007",20));
ts.add(new Student("lisi09",19));
ts.add(new Student("lisi08",19));
//ts.add(new Student("lisi007",20));
//ts.add(new Student("lisi01",40));
 
Iterator it = ts.iterator();
while(it.hasNext())
{
Student stu = (Student)it.next();
System.out.println(stu.getName()+"..."+stu.getAge());
}
}
}
 
 
class Student implements Comparable//该接口强制让学生具备比较性。
{
private String name;
private int age;
 
Student(String name,int age)
{
this.name = name;
this.age = age;
}
 
public int compareTo(Object obj)
{
 
//return 0;
if(!(obj instanceof Student))
throw new RuntimeException("不是学生对象");
Student s = (Student)obj;
 
System.out.println(this.name+"....compareto....."+s.name);
if(this.age>s.age)
return 1;
if(this.age==s.age)
{
return this.name.compareTo(s.name);//调用String类的自身比较方法
}
return -1;
/**/
}
 
public String getName()
{
return name;
 
}
public int getAge()
{
return age;
}
}


 

 

集合 -- map

 

1.概述:map是一个键值对形式的集合,数据不单个,必须同时有键和值组成。

注意:键不能重复,只能唯一。

 

2.map的优点:体现对应关系。

 

3.mapcollection的区别; 

map--是双列形式的集合,键必须是唯一的,不可重复,值可以重复。

collection--是单列值的集合,collectionlist儿子。是可以重复,他的set儿子是唯一的,看成是单身汉的集合。

 

4.map的接口功能:

A 增加功能:

 V put(K key,V value)--Key在集合中不存在时,添加元素;当key在集合中存在的时候,替换元素。

 

B 删除功能:

 void clear()--清除所有键值对数据。

 V remove(Object)--根据指定的键删除键值对。

 

C 判断功能:

 boolean containsKey(Object Key)--判断指定的键是否在集合中存在。

 boolen containsVlaue(Object Vlaue)--判断指定的值是否在集合中存在。

 boolean isEmpty()--判断集合是否为空。

 

D 获取功能:

 Set<Map.Entry<K,V>> entrySet()--键值对对象的集合。

   Object get(object Key)--根据键获取值。

 Set<K> keySet()--所有键的集合。

 Collection<V> value()--所有值的集合。

 

E 长度功能:

 int size()

 

5.Map集合的遍历:

   方式1.丈夫(键)找妻子(值):

A,把所有丈夫给集中起来,Set<K>  keySet()

B,遍历丈夫集合,获取到每一个丈夫。迭代器,增强for

C,让丈夫找妻子。get(Object key)

 

方式2.通过结婚证找丈夫和妻子

 

A,获取所有结婚证的集合。Set<结婚证>  entrySet()

Class 结婚证<K,V>

{

private K key;

private V value;

public 结婚证(K key,V value)

this.key = key;

This.value = value;

public K getKey()

{

return key;

}

public V getValue()

{

return vlaue;

}

 

}

B,遍历结婚正集合,获取每一个结婚证对象。迭代器,增强for

 

C,通过结婚证对象获取丈夫和妻子。

 

 

 

 

 

6.补充:

如果自定义一个对象做键,用TreeMap集合。

就必须实现排序;

两种方式:

让自定义对象所属的类去实现Comparator接口。

使用带参构造方法,创建TreeMap,接收Comparator接口参数。

 

 

 

 

 

7.hashMaphashTable的区别:

A Hashtable线程安全,效率低;不允许null键和值。

B HashMap线程不安全,效率高;允许null键和值。

Hashtable:此类实现一个哈希表,该哈希表将键映射到相应的值。任何非 null 对象都可以用作键或值。从Java 2 平台 v1.2起,此类就被改进以实现 Map 接口,Hashtable 是同步的。为了成功地在哈希表中存储和获取对象,用作键的对象必须实现 hashCode 方法和 equals 方法。
 
HashMap:基于哈希表的 Map 接口的实现。此实现提供所有可选的映射操作,并允许使用 null 值和 null 键。(除了非同步和允许使用 null 之外,HashMap 类与 Hashtable 大致相同。)此类不保证映射的顺序,特别是它不保证该顺序恒久不变。Hash是不同步的。 

 

 
 

 

 

 

8.HashMap(掌握)

(1)HashMap存储字符串并遍历

键:String

值:String

(2)HashMap存储自定义对象并遍历

键:String

值:Student

 

(3)HashMap存储自定义对象并遍历

键:Student(重写hashCodeequals方法,自动生成)

值:String

需求:如果对象的成员变量值都相同,我们则认为是同一个对象。

 

9:TreeMap(理解)

(1)TreeMap存储字符串并遍历

键:String

值:String

(2)TreeMap存储自定义对象并遍历

键:String

值:Student

 

(3)TreeMap(传入一个比较器comparator)存储自定义对象并遍历

键:Student

值:String

需求:如果对象的成员变量值都相同,我们则认为是同一个对象。

      同时,我们还要按照年龄排序。

 

10:案例(理解)

(1)统计字符串中每个字符出现的次数。

 

(2)HashMap嵌套HashMap的使用。

 

(3)HashMap嵌套ArrayList的使用。(掌握)

 

 

 

 

 

代码示例:
/*

Map集合:该集合存储键值对。一对一对往里存。而且要保证键的唯一性。

1,添加。

put(K key, V value) 

putAll(Map<? extends K,? extends V> m) 

 

2,删除。

clear() 

remove(Object key) 

3,判断。

containsValue(Object value) 

containsKey(Object key) 

isEmpty() 

 

 

4,获取。

get(Object key) 

size() 

values() 

 

entrySet() 

keySet() 

 

Map

|--Hashtable:底层是哈希表数据结构,不可以存入nullnull值。该集合是线程同步的。jdk1.0.效率低。

|--HashMap:底层是哈希表数据结构,允许使用 null 值和 null 键,该集合是不同步的。将hashtable替代,jdk1.2.效率高。

|--TreeMap:底层是二叉树数据结构。线程不同步。可以用于给map集合中的键进行排序。

 

 

Set很像。

其实大家,Set底层就是使用了Map集合。

 

 

*/

import java.util.*;
class  MapDemo
{
public static void main(String[] args) 
{
Map<String,String> map = new HashMap<String,String>();
 
//添加元素,添加元素,如果出现添加时,相同的键。那么后添加的值会覆盖原有键对应值。
//并put方法会返回被覆盖的值。
System.out.println("put:"+map.put("01","zhangsan1"));
System.out.println("put:"+map.put("01","wnagwu"));
map.put("02","zhangsan2");
map.put("03","zhangsan3");
 
System.out.println("containsKey:"+map.containsKey("022"));
//System.out.println("remove:"+map.remove("02"));
 
System.out.println("get:"+map.get("023"));
 
map.put("04",null);
System.out.println("get:"+map.get("04"));
//可以通过get方法的返回值来判断一个键是否存在。通过返回null来判断。
 
 
//获取map集合中所有的值。
Collection<String> coll = map.values();
 
System.out.println(coll);
System.out.println(map);
 
 
}
}


 

 

 

 

 

/*

map集合的两种取出方式:

1Set<k> keySet:将map中所有的键存入到Set集合。因为set具备迭代器。

所有可以迭代方式取出所有的键,在根据get方法。获取每一个键对应的值。

 

Map集合的取出原理:将map集合转成set集合。在通过迭代器取出。

 

 

2Set<Map.Entry<k,v>> entrySet:将map集合中的映射关系存入到了set集合中,

而这个关系的数据类型就是:Map.Entry

 

Entry其实就是Map中的一个static内部接口。

为什么要定义在内部呢?

因为只有有了Map集合,有了键值对,才会有键值的映射关系。

关系属于Map集合中的一个内部事物。

而且该事物在直接访问Map集合中的元素。

 

 

 

*/

 

import java.util.*;
 
 
class MapDemo2 
{
public static void main(String[] args) 
{
Map<String,String> map = new HashMap<String,String>();
 
map.put("02","zhangsan2");
map.put("03","zhangsan3");
map.put("01","zhangsan1");
map.put("04","zhangsan4");
 
//将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.Entry 其实Entry也是一个接口,它是Map接口中的一个内部接口。
 
 
 
 
interface Map
{
public static interface Entry
{
public abstract Object getKey();
public abstract Object getValue();
 
}
}
 
class HashMap implements Map
{
class Hahs implements Map.Entry
{
public  Object getKey(){}
public  Object getValue(){}
}
}
*/


 

 

/*

map扩展知识。

 

map集合被使用是因为具备映射关系。

 

"yureban"   Student("01" "zhangsan");

 

"yureban" Student("02" "lisi");

 

"jiuyeban" "01" "wangwu";

"jiuyeban" "02" "zhaoliu";

 

一个学校有多个教室。每一个教室都有名称。

 

 

*/

import java.util.*;
 
class Student
{
private String id;
private String name;
Student(String id,String name)
{
this.id = id;
this.name = name;
}
public String toString()
{
return id+":::"+name;
}
}
class  MapDemo3
{
//学生信息直接用list集合存储
public static void demo()
{
HashMap<String,List<Student>> czbk = new HashMap<String,List<Student>>();
 
List<Student> reyu = new ArrayList<Student>();
List<Student> jiuye = new ArrayList<Student>();
 
czbk.put("yureban",reyu);
czbk.put("jiuyeban",jiuye);
 
reyu.add(new Student("01","zhagnsa"));
reyu.add(new Student("04","wangwu"));
jiuye.add(new Student("01","zhouqi"));
jiuye.add(new Student("02","zhaoli"));
 
 
Iterator<String> it = czbk.keySet().iterator();
 
while(it.hasNext())
{
String roomName = it.next();
List<Student> room = czbk.get(roomName);
System.out.println(roomName);
getInfos(room);
}
 
 
}
public static void getInfos(List<Student> list)
{
Iterator<Student> it = list.iterator();
while(it.hasNext())
{
Student s = it.next();
System.out.println(s);
}
}
 
 
 
 
public static void main(String[] args) 
{
 demo();
/*
HashMap<String,HashMap<String,String>> czbk = new HashMap<String,HashMap<String,String>();
 
HashMap<String,String> yure = new HashMap<String,String>();
HashMap<String,String> jiuye = new HashMap<String,String>();
 
czbk.put("yureban",yure);
czbk.put("jiuyeban",jiuye);
 
 
yure.put("01","zhagnsan");
yure.put("02","lisi");
 
jiuye.put("01","zhaoliu");
jiuye.put("02","wangwu");
 
 
//取出所有学生
//遍历czbk集合。获取所有的教室。
Iterator<String> it = czbk.keySet().iterator();
 
while(it.hasNext())
{
String roomName = it.next();
HashMap<String,String> room = czbk.get(roomName);//获取教室这个键里的学生信息
System.out.println(roomName);
getStudentInfo(room);
}
 
//	getStudentInfo(jiuye);//取出班级的学生
//	getStudentInfo(yure);
*/
}
public static void getStudentInfo(HashMap<String,String> roomMap)
{
Iterator<String> it = roomMap.keySet().iterator();
 
while(it.hasNext())
{
String id = it.next();
String name = roomMap.get(id);
System.out.println(id+":"+name);
}
}
}


 

 

 

集合使用技巧

 

同步与非同步:

明确具体集合对象名称的后缀,如果后缀是List都所属于List体系,通常是非同步的,如果后缀是Set都所属set体系,通常是非同步的;这些体系的其他子类对象,后缀不是所属接口名的,一般都是同步的。如vector

数据结构:

前缀是数据结构,后缀是所属体系。

ArrayList:看到Array,明确是数组结构,查询快。

需要唯一吗?

需要:set

  需要制定顺序吗:

             需要:TreeSet

           不需要:hashset

           但是想要一个和存储一直的顺序(有序):linkedhashset

不需要:list

     选要频繁增删吗:

             需要:linkedlist

           不需要:arraylist

如果记录每一个容器的结构和所属体系呢?

看名字!

List

   Arraylist

   Linkedlist

Set

   Hashset

   Treeset

后缀名就是该集合所属的体系。

前缀名技术该集合的数据结构。

看到array:就要想到数组,就要想到查询快,有角标。

看到link:就要想到链表,就要想到增删快,就要想到 add get remove+first last的方法。

看到hash:就要想到哈希表,就要想到唯一性,就要想到hashcodeequals

看到tree:就要想到排序,想到二叉树,就要想到comparablecomparator

通常这些常用的是线程不安全的。

 

 

 

集合工具类(Collections静态方法-sort,max,binaryseach,swap)

概述:

集合框架的工具类。

Collections:集合框架的工具类。里面定义的都是静态方法。

 

常见操作

1、查找

        Tmax(Collection<? extends T> coll);//根据集合的自然顺序,获取coll集合中的最大元素

        Tmax(Collection<? extends T> coll,Comparator<? super T> comp);//根据指定比较器comp的顺序,获取coll集合中的最大元素

        intbinarySearch(Lsit<? extends Comparable<? super T>> list,Tkey);//二分法搜索list集合中的指定对象

2、替换

        Void fill(List<? super T> list, T obj);//list集合中的全部元素替换成指定对象obj

        Boolean replaceAll(List<T> lsit,T oldVal,T newVal);//newVal替换集合中的oldVal

        void swap(Listlist,int i,int j);/在指定列表的指定位置处交换元素

list - 进行元素交换的列表。

i - 要交换的一个元素的索引。

j - 要交换的另一个元素的索引。

3排序:

        void shuffle(List<?> list);//使用默认随机源对list集合中的元素进行随机排序

        void sort(Lsit<T> list);//根据自然顺序对list集合中的元素进行排序

        void sort(List<T> lsit,Comparator<? super T> c);//根据指定比较器c的排序方式对list集合进行排序

4、反转

        reverse(List<?> list);//反转list集合中元素的顺序

        Comparator reverseOrder();//返回一个比较器,强行逆转了实现Comparable接口的对象的自然顺序

        ComparatorreverseOrder(Comparator<T> cmp);//返回一个比较器,强行逆转了指定比较器的顺序

5、同步的集合

        List<T>synchronizedList(List<T> list);//返回支持的同步(线程安全的)List集合

        Map<K,V>synchronizedList(Map<K,V> m);//返回支持的同步(线程安全的)Map集合

 

 

CollectionsCollection有什么区别?

Collection是集合框架中的一个顶层接口,它里面定义了单列集合的共性方法。

它有两个常用的子接口,

List:对元素都有定义索引。有序的。可以重复元素。

Set:不可以重复元素。无序。

 

Collections是集合框架中的一个工具类。该类中的方法都是静态的

提供的方法中有可以对list集合进行排序,二分查找等方法。

通常常用的集合都是线程不安全的。因为要提高效率。

如果多线程操作这些集合时,可以通过该工具类中的同步方法,将线程不安全的集合,转换成安全的。

 

 

 

应用案例:

import java.util.*;
class  CollectionsDemo
{
public static void main(String[] args) 
{
sortDemo();
 
}
 
public static void binarySearchDemo()//使用二分搜索法查找指定列表,必须是有序集合,获得指定的对象
{
List<String> list = new ArrayList<String>();
 
list.add("abcd");
list.add("aaa");
list.add("zz");
list.add("kkkkk");
list.add("qq");
list.add("z");
Collections.sort(list,new StrLenComparator());
 
sop(list);
 
//int index = Collections.binarySearch(list,"aaaa");
//int index = halfSearch(list,"cc");
int index = halfSearch2(list,"aaaa",new StrLenComparator());
sop("index="+index);
}
//模拟binarysearch功能,折半法原理
public static int halfSearch(List<String> list,String key)
{
int max,min,mid;
max = list.size()-1;
min = 0;
 
while(min<=max)
{
mid = (max+min)>>1;//  /2;
 
String str = list.get(mid);
 
int num = str.compareTo(key);
if(num>0)
max = mid -1;
else if(num<0)
min = mid + 1;
else
return mid;
}
return -min-1;
}
//如果list集合的元素不具备比较性,则定义比较器
public static int halfSearch2(List<String> list,String key,Comparator<String> cmp)
{
int max,min,mid;
max = list.size()-1;
min = 0;
 
while(min<=max)
{
mid = (max+min)>>1;//  /2;
 
String str = list.get(mid);
 
int num = cmp.compare(str,key);
if(num>0)
max = mid -1;
else if(num<0)
min = mid + 1;
else
return mid;
}
return -min-1;
}
 
public static void maxDemo()
{
List<String> list = new ArrayList<String>();
 
list.add("abcd");
list.add("aaa");
list.add("zz");
list.add("kkkkk");
list.add("qq");
list.add("z");
Collections.sort(list);
sop(list);
String max = Collections.max(list/*,new StrLenComparator()*/);
//根据元素的自然顺序,返回给定 collection 的最大元素。
sop("max="+max);
}
 
public static void sortDemo()
{
List<String> list = new ArrayList<String>();
 
list.add("abcd");
list.add("aaa");
list.add("zz");
list.add("kkkkk");
list.add("qq");
list.add("z");
sop(list);
 
//Collections.sort(list);//默认按照ASCII码排序,自然排序
Collections.sort(list,new StrLenComparator());//自定义排序方式,定义比较器
//Collections.swap(list,1,2);// 在指定列表的指定位置处交换元素。
sop(list);
}
 
public static void sop(Object obj)
{
System.out.println(obj);
}
}
 
 
class StrLenComparator implements Comparator<String>
{
public int compare(String s1,String s2)
{
if(s1.length()>s2.length())
return 1;
if(s1.length()<s2.length())
return -1;
return s1.compareTo(s2);
}
}
/*
class Student
{
}
list.add(new Student());
 
public static <T extends Comparable<? super T>> void sort(List<T> list)//T元素必须具备比较性 
,所以必须是compareable的子类
{
}


1. Collections-替换+置换+反转+reverseOrder

 

import java.util.*;
 
class StrComparator implements Comparator<String>
{
public int compare(String s1,String s2)
{
/*//按照自然排序反转输出,return的值替换即可;
int num = s1.compareTo(s2);
if(num>0)
return -1;
if( num<0)
return 1;
return num;
*/
//按照自然排序反转输出,s1与s2换位置即可;
return s2.compareTo(s1);
}
}
 
class StrLenComparator implements Comparator<String>
{
public int compare(String s1,String s2)
{
//按照长度排序
if(s1.length()>s2.length())
return 1;
if(s1.length()<s2.length())
return -1;
return s1.compareTo(s2);
}
}
class CollectionsDemo2 
{
public static void main(String[] args) 
{
shuffleDemo();
}
public static void shuffleDemo()
{
List<String> list = new ArrayList<String>();
 
list.add("abcd");
list.add("aaa");
list.add("zz");
list.add("kkkkk");
list.add("qq");
list.add("z");
 
sop(list);
Collections.shuffle(list);// 使用默认随机源对指定列表进行置换。将集合随机重新排放
sop(list);
}
public static void orderDemo()
{
TreeSet<String> ts = new TreeSet<String>(Collections.reverseOrder(new  
StrLenComparator()));//返回一个比较器,它强行逆转指定比较器的顺序。
 
ts.add("abcde");
ts.add("aaa");
ts.add("k");
ts.add("cc");
 
Iterator it = ts.iterator();
while(it.hasNext())
{
System.out.println(it.next());
}
}
 
 
public static void replaceAllDemo()
{
 
 
List<String> list = new ArrayList<String>();
 
list.add("abcd");
list.add("aaa");
list.add("zz");
list.add("kkkkk");
 
sop(list);
 
Collections.replaceAll(list,"aaa","pp");//按照元素替换,使用另一个值替换列表中出现的所有某一指定值。
 
sop(list);
Collections.reverse(list);//反转,原理就是交换位置,反转指定列表中元素的顺序。
 
sop(list);
}
 
 
/*
练习。fill方法可以将list集合中所有元素替换成指定元素。
,将list集合中部分元素替换成指定元素。
 
*/
public static void fillDemo()
{
List<String> list = new ArrayList<String>();
 
list.add("abcd");
list.add("aaa");
list.add("zz");
list.add("kkkkk");
sop(list);
Collections.fill(list,"pp");//使用指定元素替换指定列表中的所有元素。
sop(list);
 
}
public static void sop(Object obj)
{
System.out.println(obj);
}
}


2. Collections工具类为线程添加锁

synchronizedList(List<T> list): 返回指定列表支持的同步(线程安全的)列表。

添加,删除操作不能同时进行,原理是他们用的是同一个锁;

 

synchronizedMap(Map<K,V> m): 返回由指定映射支持的同步(线程安全的)映射。

3. Arrays工具类:数组转集合

概述:

Arrays:用于操作数组的工具类。里面都是静态方法。

asList:将数组变成list集合

 

把数组变成list集合有什么好处?

可以使用集合的思想和方法来操作数组中的元素。

 

注意:将数组变成集合,不可以使用集合的增删方法。

因为数组的长度是固定。

contains

get

indexOf()

subList();

如果你增删。那么会发生UnsupportedOperationException,

 代码示例:

 

import java.util.*;
class  test
{
public static void main(String[] args) 
{
//int[] arr = {2,4,5};
//
//System.out.println(Arrays.toString(arr));
String[] arr = {"abc","cc","kkkk"};
 
List<String> list = Arrays.asList(arr);
sop("contains:"+list.contains("cc"));
//list.add("qq");//UnsupportedOperationException,
 
sop(list);
 
//int[] nums = {2,4,5};
Integer[] nums = {2,4,5};
 
List<Integer> li = Arrays.asList(nums);
/*
如果数组中的元素都是对象。那么变成集合时,数组中的元素就直接转成集合中的元素。
如果数组中的元素都是基本数据类型,那么会将该数组作为集合中的元素存在。
*/
sop(li);
}
//判断某元素是否存在于数组中,此方法麻烦,所以将数组转换成集合,调用contains方法
public static boolean myContains(String[] arr,String key)
{
for(int x=0;x<arr.length; x++)
{
if(arr[x].equals(key))
return true;
}
return false;
}
public static void sop(Object obj)
{
System.out.println(obj);
}
 
}


4. Arrays工具类:集合转数组

 

集合变数组。

Collection接口中的toArray方法。

 

<T> T[]toArray(T[] a);将集合变为指定类型的数组。

 

1、指定类型的数组到底要定义多长呢?

        当指定类型的数组长度小于了集合的size,那么该方法内部会创建一个新的数组。长度为集合的size

         当指定类型的数组长度大于了集合的size,就不会新创建了数组。而是使用传递进来的数组。

         所以创建一个刚刚好的数组最优。

 

2、为什么要将集合变数组?

         为了限定对元素的操作。不需要进行增删了。

 

 

import java.util.*;
class  CollectionToArray
{
public static void main(String[] args) 
{
ArrayList<String> al = new ArrayList<String>();
 
al.add("abc1");
al.add("abc2");
al.add("abc3");
 
String[] arr = al.toArray(new String[al.size()]);
 
System.out.println(Arrays.toString(arr));
 
}
}


 

 

 

 

 

 




------<a href="http://www.itheima.com" target="blank">Java培训、Android培训、iOS培训、.Net培训</a>、期待与您交流! -------

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值