Java集合框架详解

集合

概念:对象的容器,定义了对多个对象进行操作的常用方法。可实现数组的功能。

和数组的区别:

  • 数组长度固定,集合长度不固定
  • 数组可以存储基本类型和引用类型,集合只能存储引用类型(集合想存储基本类型,需要进行"装箱"操作)
  • 位置:java.util.*; 需导入

Collection体系

Collection父接口

特点:代表一组任意类型的对象,无序、无下标、不重复。

方法

  • boolean add(Object obj) 添加
  • boolean addAll(Collection c) 将一个集合中的所有对象添加到此集合中
  • void clear()
  • boolean contains(Object o) 检查此对象中是否包含o对象
  • boolean equals(Object o)
  • boolean isEmpty()
  • boolean remove(Object o) 在此集合中移除o对象
  • int size() 返回此集合中的元素个数
  • Object[] toArray() 将此集合转换成数组

Collection使用

存储基本类型信息

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;public class CollectionDemo01 {public static void main(String[] args) {//创建集合  不能实例化接口,所以要用ArrayListCollection collection = new ArrayList();//①添加元素collection.add("西瓜");collection.add("榴莲");collection.add("樱桃");System.out.println("元素个数"+collection.size());System.out.println(collection);//②删除元素collection.remove("榴莲");    //collection.clear(); 全部清除System.out.println("删除之后元素的个数:"+collection.size());//③遍历元素 重点//第一种遍历方式,使用增强for   因为for循环需要下标,而增强for不需要下标System.out.println("------使用增强for------");for (Object o : collection) {System.out.println(o);}//第二种遍历方式,使用迭代器(迭代器是专门用来遍历集合的方式) 可以在JDK 11 中具体查看//hasNext(); 有没有下一个元素//next(); 获取下一个元素//remove(); 删除当前元素System.out.println("------使用迭代器Iterator------");Iterator it = collection.iterator();while(it.hasNext()){String s = (String)it.next();   //已知是String类型,就顺便进行一下强制转换System.out.println(s);//在迭代过程中不能使用 collection.remove();方法,不能并发修改(正用着呢,不能修改)it.remove();    //要想删除就用这个方法}System.out.println("迭代结束之后元素的个数为:"+collection.size());//④判断System.out.println(collection.contains("西瓜"));System.out.println(collection.isEmpty());}
}
结果:
元素个数3
[西瓜, 榴莲, 樱桃]
删除之后元素的个数:2
------使用增强for------
西瓜
樱桃
------使用迭代器Iterator------
西瓜
樱桃
迭代结束之后元素的个数为:0
false
true

Iterator的执行过程

存储类信息

先新建一个学生类

public class Student {//学生类private String name;private int age;//添加无参构造器   Alt+Ins→Constructor→Select Nonepublic Student() {}//添加有参构造器   Alt+Ins→Constructor→选定两个参数public Student(String name, int age) {this.name = name;this.age = age;}//添加 Getter and Setterpublic String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}//为了打印好看,重写一下方法//Alt+Ins→toString();@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +'}';}
}

然后进行collection操作

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class CollectionDemo01 {//存储学生信息public static void main(String[] args) {//新建collection对象Collection collection = new ArrayList();//①添加数据Student s1 = new Student("张三",20);Student s2 = new Student("张无忌",18);Student s3 = new Student("王二",22);collection.add(s1);collection.add(s2);collection.add(s3);//因为实例化的是ArrayList,所以可以添加重复的元素collection.add(s3);System.out.println(collection.size());//因为在Student类中重写了toString方法,所以打印结果比较直观System.out.println(collection.toString());//②删除collection.remove(s3);System.out.println(collection.size());collection.remove(new Student("王二",22));//new出来的对象不在集合内,只是该对象的属性和本想删除的集合内的对象相同,但是无法删除System.out.println(collection.size());//collection.clear();//System.out.println(collection.size());//集合和3个学生类的对象是同级的地位,将其添加到集合中是将3个地址添加到集合中,clear也只是清空集合中的三个地址,但是堆中的三个对象是没有消失的//③遍历System.out.println("------使用增强for------");for (Object o : collection) {Student s = (Student) o;System.out.println(s);}System.out.println("------使用迭代器Iterator------");Iterator it = collection.iterator();while (it.hasNext()){Student s = (Student) it.next();System.out.println(s);}//④判断System.out.println(collection.contains(s1));System.out.println(collection.contains(new Student("张三",20)));//⑤判断是否为空System.out.println(collection.isEmpty());}
}
结果:
4
[Student{name='张三', age=20}, Student{name='张无忌', age=18}, Student{name='王二', age=22}, Student{name='王二', age=22}]
3
3
------使用增强for------
Student{name='张三', age=20}
Student{name='张无忌', age=18}
Student{name='王二', age=22}
------使用迭代器Iterator------
Student{name='张三', age=20}
Student{name='张无忌', age=18}
Student{name='王二', age=22}
true
false
false

List子接口

特点:有序(添加顺序=遍历顺序),有下标,元素可以重复

方法

  • void add(int index,Object o) //指定位置插入对象
  • boolean addAll(int index,Collection c)
  • Object get(int index)
  • List sublist(int fromIndex,int toIndex) //返回子集合

List接口的使用

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;public class Demo01 {public static void main(String[] args) {//创建集合对象List list = new ArrayList<>();//①添加元素list.add("苹果");list.add("小米");list.add(0,"华为");System.out.println(list.size());System.out.println(list);//②删除元素//list.remove("苹果");list.remove(0);System.out.println("删除之后的长度为:"+list.size());System.out.println(list);//③遍历//使用for 因为有下标System.out.println("------①使用for循环遍历------");for (int i = 0; i < list.size(); i++) {System.out.println(list.get(i)); //返回的是Object类型,如果想转成实际类型需要进行强制转换}System.out.println("------②使用增强for遍历------");for (Object o : list) {System.out.println(o);}System.out.println("------③使用迭代器遍历------");Iterator iterator = list.iterator();while(iterator.hasNext()){Object m = iterator.next();System.out.println(m);}//listIterator的不同:可以向前或者向后遍历,可以添加、删除、修改元素System.out.println("------④使用列表迭代器从前往后遍历------");ListIterator lit = list.listIterator();while(lit.hasNext()){System.out.println(lit.nextIndex()+":"+lit.next());}System.out.println("------⑤使用列表迭代器从后往前遍历------");while (lit.hasPrevious()){System.out.println(lit.previousIndex()+":"+lit.previous());}//④判断System.out.println(list.contains("苹果"));System.out.println(list.isEmpty());//⑤获取位置System.out.println(list.indexOf("华为"));}
}
结果:
3
[华为, 苹果, 小米]
删除之后的长度为:2
[苹果, 小米]
------①使用for循环遍历------
苹果
小米
------②使用增强for遍历------
苹果
小米
------③使用迭代器遍历------
苹果
小米
------④使用列表迭代器从前往后遍历------
0:苹果
1:小米
------⑤使用列表迭代器从后往前遍历------
1:小米
0:苹果
true
false
-1
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
public class Demo04 {public static void main(String[] args) {//创建一个集合List list = new ArrayList();//①添加数值元素(自动装箱,因为集合不能保存基本类型数据,所以给了数值元素20之后就会被包装成包装类)list.add(20);list.add(30);list.add(40);list.add(50);list.add(60);System.out.println("元素个数为:"+list.size());System.out.println(list);//②删除list.remove((Object)20);    //默认是根据下标删除,所以不能直接写数字 除了这样之外还可以:list.remove(new Integer(20));System.out.println("删除元素后集合的长度:"+list.size());//③subList 返回子集合List sublist = list.subList(1,3);  //前包后不包System.out.println(sublist);}
}
结果:
元素个数为:5
[20, 30, 40, 50, 60]
删除元素后集合的长度:4
[40, 50]

List也可以添加类元素,和collection一样,就不重复记录了。

List实现类

  • ArrayList 【重点】

    • 在里面维护了一个数组结构,并且由于数组连续存储的特点,使得查询快、增删慢
    • 效率快,不安全
  • Vector
    • 也采用了数组结构,查询快、增删慢
    • 但是运行效率慢、线程安全
  • LinkedList
    • 采用链表结构实现,增删快,查询慢

ArrayList的使用

import java.util.ArrayList;
import java.util.Iterator;
import java.util.ListIterator;public class Demo05 {public static void main(String[] args) {//创建集合ArrayList arrayList = new ArrayList();//①添加类元素Student s1 = new Student("刘德华",20);Student s2 = new Student("郭富城",22);Student s3 = new Student("梁朝伟",18);arrayList.add(s1);arrayList.add(s2);arrayList.add(s3);System.out.println("元素个数为:"+arrayList.size());System.out.println(arrayList);//②删除元素arrayList.remove(s1);System.out.println("删除元素之后,个数为:"+arrayList.size());//③遍历元素 重点System.out.println("------①使用迭代器遍历------");Iterator it = arrayList.iterator();while (it.hasNext()){Student s = (Student)it.next(); //将Object类型强制转换成Student类型System.out.println(s);}System.out.println("------②使用列表迭代器从前往后遍历------");ListIterator lit = arrayList.listIterator();while (lit.hasNext()){Student s = (Student)lit.next();System.out.println(s);}System.out.println("------③使用列表迭代器从后往前遍历------");while (lit.hasPrevious()){Student s = (Student)lit.previous();System.out.println(s);}//④判断   和删除一样,修改了equals()方法之后"new"格式也可以成功运行System.out.println(arrayList.contains(s1));System.out.println(arrayList.contains(new Student("郭富城",22)));//⑤查找System.out.println(arrayList.indexOf(s2));System.out.println(arrayList.indexOf(new Student("梁朝伟",18)));}
}结果:
元素个数为:3
[Student{name='刘德华', age=20}, Student{name='郭富城', age=22}, Student{name='梁朝伟', age=18}]
删除元素之后,个数为:2
------①使用迭代器遍历------
Student{name='郭富城', age=22}
Student{name='梁朝伟', age=18}
------②使用列表迭代器从前往后遍历------
Student{name='郭富城', age=22}
Student{name='梁朝伟', age=18}
------③使用列表迭代器从后往前遍历------
Student{name='梁朝伟', age=18}
Student{name='郭富城', age=22}
false
true
0
1
小问题

如果将删除操作更改成:

arrayList.remove(new Student("刘德华",20));

(即不是同一个对象),是否还能成功删除指定元素呢?

需要使用equals()方法,首先在Student类中重写equals()方法:

//equals+Enter@Overridepublic boolean equals(Object o) {//①判断是否是同一个对象if (this == o) return true;//②判断是否为空if(o == null){return false;}//③判断o是否是Student类型,如果不是就返回false;如果是,就进行强制转换。if (!(o instanceof Student)) return false;Student student = (Student) o;//④比较属性 和视频中的写法不同//视频中的写法:   if(this.name.equals(s.getName())&&this.age == s.getAge()){retuen true;}return getAge() == student.getAge() && Objects.equals(getName(), student.getName());}

然后回到集合类中来:

arrayList.remove(new Student("刘德华",20));
结果:
删除元素之后,个数为:2

就可以成功删除元素。

原理就是重写equals()方法之后,比较的东西从是否是同一个对象变成了对象的属性(name,age)是否相同。

ArrayList源代码解析

  • 默认容量为10(10是向集合添加元素之后的默认容量,如果没有向集合中添加任何元素时,容量就是0)
DEFAULT_CAPACITY = 10;
  • 存放元素的数组
Object[] elementData;
  • 实际元素个数
int size;
  • 添加元素add

点击进入ensureCapacityInternal();方法

如上图所示:

黄:创建数组的时候将后者赋予给前者了,所以相等

红:在前者10和后者0中选取最大值,将其赋值给minCapacity并将其带入到后面当中

蓝:10-0>0→点击进入grow();方法

如下图所示:

所以添加任意一个元素之后,容量就扩容为10。

再想,如果添加第11个元素之后会怎样执行?如下图所示:

(11-10)满足>0的条件,执行grow。如果不满足就不执行grow,就直接放入数组了。

然后进入gorw();方法:

所以当输入第11个元素时,capacity扩容为15。

也就是说,当容量不够时每次都扩容为原来的1.5倍。

Vector的使用

  • 数组结构,查询快、增删慢
  • JDK1.0版本,运行慢、线程安全

演示Vector集合的使用:

package com.qf.chapter12_2;import java.util.Enumeration;
import java.util.Vector;public class Demo01 {public static void main(String[] args) {//创建集合Vector vector = new Vector();//①添加元素vector.add("草莓");vector.add("芒果");vector.add("西瓜");System.out.println("元素个数:"+vector.size());//②删除//vector.remove("西瓜");//vector.clear();//③遍历//因为有脚标,所以for、增强for、迭代器都可以,下面介绍特有的遍历方法://使用枚举器Enumeration en = vector.elements();while(en.hasMoreElements()){String o = (String)en.nextElement();//Object o = en.nextElement();System.out.println(o);}//④判断System.out.println(vector.contains("西瓜"));System.out.println(vector.isEmpty());//⑤Vector其它的方法//firstElenment、lastElement、elementAt;}
}
结果:
元素个数:3
草莓
芒果
西瓜
true
false

LinkedList的使用

  • 链表结构实现,增删快、查询慢
package com.qf.chapter12_2;import java.util.Iterator;
import java.util.LinkedList;
import java.util.ListIterator;public class Demo02 {public static void main(String[] args) {//创建集合LinkedList linkedList = new LinkedList();//①添加元素Student2 s1 = new Student2("张三",20);Student2 s2 = new Student2("张无忌",18);Student2 s3 = new Student2("王二",22);linkedList.add(s1);linkedList.add(s2);linkedList.add(s3);linkedList.add(s3);System.out.println("元素个数:"+linkedList.size());System.out.println(linkedList);//②删除//linkedList.remove(new Student2("刘德华",20)); //需要在Student2中重写equals();方法就可以成功删除//System.out.println("删除后:"+linkedList.size());//inkedList.clear();//③遍历//for遍历System.out.println("------①使用for遍历------");for (int i = 0; i < linkedList.size(); i++) {System.out.println(linkedList.get(i));}//增强forSystem.out.println("------②使用增强for遍历------");for (Object o : linkedList) {Student2 s = (Student2) o;System.out.println(s);}//③使用迭代器System.out.println("------③使用迭代器遍历------");Iterator it = linkedList.iterator();while (it.hasNext()){Student2 s = (Student2) it.next();}//④使用listiterator遍历ListIterator lit = linkedList.listIterator();while (lit.hasNext()) {Student2 s = (Student2) lit.next();System.out.println(s);}//⑤判断System.out.println(linkedList.contains(s1));System.out.println(linkedList.isEmpty());//⑥获取System.out.println(linkedList.indexOf(s1));}
}
结果:
元素个数:4
[Student{name='张三', age=20}, Student{name='张无忌', age=18}, Student{name='王二', age=22}, Student{name='王二', age=22}]
------①使用for遍历------
Student{name='张三', age=20}
Student{name='张无忌', age=18}
Student{name='王二', age=22}
Student{name='王二', age=22}
------②使用增强for遍历------
Student{name='张三', age=20}
Student{name='张无忌', age=18}
Student{name='王二', age=22}
Student{name='王二', age=22}
------③使用迭代器遍历------
Student{name='张三', age=20}
Student{name='张无忌', age=18}
Student{name='王二', age=22}
Student{name='王二', age=22}

LinkedList源码分析

查看LinkedList源码会发现初始大小size=0,以及头结点和尾结点first和last,如下图所示:

节点结构:

添加元素的过程:

ArrayList和linkedList区别

实现结构不同:一个连续存储空间;一个靠指针索引

泛型概述

  • 本质:参数化类型(和方法不同,传递的不是数据,是数据类型)
  • 常用形式:
    • 泛型类
    • 泛型接口
    • 泛型方法
  • 语法:<大写字母,…> 大写字母为类型占位符,表示一种引用类型
  • 好处:
    • 提高代码的重用性
    • 防止类型转换异常,提高代码安全性

泛型类

创建Generic类

package com.qf.chapter12_2;import javax.swing.*;public class MyGeneric <T,E,k>{//语法:   类名<T,E,K>//使用这个泛型T可以://①创建变量:    但是不可以实例化:T t1 = new T(); 因为T的类型不确定,如果是私有类型就会报错T t;E e;//②作为方法的参数public void show(T t){System.out.println(t);}//③使用泛型作为方法的返回值public E getE(){return e;}}

测试类:

package com.qf.chapter12_2;public class TestGeneric {public static void main(String[] args) {//使用泛型类创建对象 要给引用类型MyGeneric<String,String,String> myGeneric = new MyGeneric<String,String,String>();myGeneric.t = "hello";myGeneric.e = "beach";myGeneric.show("大家好,加油");String string = myGeneric.getE();System.out.println(string);MyGeneric<Integer,Integer,Integer> myGeneric1 = new MyGeneric<Integer,Integer,Integer>();myGeneric1.t = 100;myGeneric1.e = 200;myGeneric1.show(200);Integer integer = myGeneric1.getE();System.out.println(integer);}
}
结果:
大家好,加油
beach
200
200
注意:
  • 泛型只能使用引用类型
  • 不同泛型类型对象之间不能相互赋值(Integer类型不能赋值给String类型)

泛型接口

先创建一个接口类:

  • 如果已经确定了泛型接口的类型,先写接口和其实现类:


  • 然后在测试类中进行尝试和输出:

  • 如果不确定泛型接口实现的引用类型:


  • 然后在实现类里实现:

泛型方法

先写泛型方法类

package com.qf.chapter12_2;
//语法:<T>返回值类型
public class MyGenericMethod {public <T> void show(T t){T t2;System.out.println("泛型方法");}//在其他的方法里面不能使用Tpublic void haha(){ }
}

然后在测试类中调用

 //调用泛型方法时,根据传入的数据决定类型,不需要自己指定MyGenericMethod myGenericMethod = new MyGenericMethod();myGenericMethod.show("中国加油");myGenericMethod.show(200);myGenericMethod.show(3.1415927);

泛型好处

  • 提高代码的重用性
  • 防止类型转换异常,提高代码安全性

泛型集合

 LinkedList linkedList = new LinkedList();

上述代码本来是泛型类,没有限制类型,默认是Object类,可以是可以,但是所有添加的元素都变成Object类,在遍历获取的时候,所有的类又都要强转成原来的类,容易出现错误。所以Java中使用泛型集合来对此进行限制。

  • 概念:参数化类型、类型安全的集合,强制集合元素的类型必须一致
  • 特点:
    • 编译时可以检查,而非运行时抛出异常
    • 访问时不必类型转换(拆箱)。
    • 不同泛型之间引用不能相互赋值,泛型不存在多态

如果不判断一下之前是什么类型,在类型转换的时候会出现错误。

出错举例:

package com.qf.chapter12_2;import java.util.ArrayList;public class Demo03 {public static void main(String[] args) {ArrayList arrayList = new ArrayList();arrayList.add("xxx");arrayList.add("yyy");arrayList.add(10);arrayList.add(20);for (Object o : arrayList) {String str = (String)o;System.out.println(str);}}
}
结果:
//错误原因:Integer不能转成String
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.Stringat com.qf.chapter12_2.Demo03.main(Demo03.java:13)

改进:

package com.qf.chapter12_2;import java.util.ArrayList;
import java.util.Iterator;public class Demo03 {public static void main(String[] args) {ArrayList<String> arrayList = new ArrayList<String>();//泛型集合强制元素都为String,就避免了强制类型转换失败的错误arrayList.add("xxx");arrayList.add("yyy");//arrayList.add(10); int类型就无法加入集合//arrayList.add(20);for (String s : arrayList) {    //使用arrayList.for+Enter自动生成时,类型自动变为StringSystem.out.println(s);}ArrayList<Student2> arrayList1 = new ArrayList<Student2>();  //Student2是之前写好的一个类Student2 s1 = new Student2("刘德华",20);Student2 s2 = new Student2("郭富城",22);Student2 s3 = new Student2("梁朝伟",18);arrayList1.add(s1);arrayList1.add(s2);arrayList1.add(s3);Iterator<Student2> it = arrayList1.iterator();while(it.hasNext()){Student2 s = it.next(); //使用泛型集合在这里就少一步强制转换System.out.println(s);}}
}
结果:
xxx
yyy
Student{name='刘德华', age=20}
Student{name='郭富城', age=22}
Student{name='梁朝伟', age=18}

Set集合

set子接口

  • 特点:无序/添加顺序和遍历顺序不一样,无下标,元素不可重复(和List正好相反)
  • 方法:全部继承Collection中的方法

set接口的使用

Set实现类
  • HashSet 【重点】

    • 基于HashCode/哈希表实现元素不重复
    • equals进行确认
  • TreeSet
    • 基于二叉树实现
    • 基于排列顺序实现元素不重复

set接口使用过举例

package com.qf.chapter12_3;import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;public class Demo01 {public static void main(String[] args) {//创建集合  创建一个Set的哈希表子集Set<String> set = new HashSet<>();//①添加数据set.add("苹果");set.add("华为");set.add("小米");set.add("华为");  //重复的不添加System.out.println("数据个数:"+set.size()); //当小米在最前面的时候,打印结果相同→无序System.out.println(set);//②删除数据set.remove("小米");System.out.println(set);//③遍历 【重点】//使用增强forSystem.out.println("------使用增强for进行遍历------");for (String s : set) {System.out.println(s);}//使用迭代器System.out.println("------使用增迭代器进行遍历------");Iterator<String> it = set.iterator();while(it.hasNext()){System.out.println(it.next());}//④判断System.out.println(set.contains("华为"));System.out.println(set.isEmpty());}
}
结果:
数据个数:3
[苹果, 华为, 小米]
[苹果, 华为]
------使用增强for进行遍历------
苹果
华为
------使用增迭代器进行遍历------
苹果
华为
true
false
HashSet使用 【重点】
  • 基于HashCode计算元素存放的位置
  • 当存入元素的哈希码相同时,会调用equals进行确认,如果结果为true,则拒绝后者存入
  • 存储结构:哈希表(数组+链表+红黑树)

哈希表实现过程如下:

HashSet使用案例1:

package com.qf.chapter12_3;import java.util.HashSet;
import java.util.Iterator;public class Demo02 {public static void main(String[] args) {//新建集合 存放字符串的哈希集合HashSet<String> hashSet = new HashSet<>();//①添加元素hashSet.add("刘德华");hashSet.add("梁朝伟");hashSet.add("林志玲");hashSet.add("周润发");hashSet.add("刘德华");System.out.println("元素个数为:"+hashSet.size());System.out.println(hashSet);    //因为set接口无序所以HashSet也是无序的//②删除数据hashSet.remove("刘德华");System.out.println("删除之后的元素个数:"+hashSet.size());System.out.println(hashSet);//③遍历//使用增强forSystem.out.println("------使用增强for遍历------");for (String s : hashSet) {System.out.println(s);}//使用迭代器System.out.println("------使用迭代器遍历------");Iterator<String> it = hashSet.iterator();while(it.hasNext()){System.out.println(it.next());}//④判断System.out.println(hashSet.contains("郭富城"));System.out.println(hashSet.isEmpty());}
}
元素个数为:4
[林志玲, 梁朝伟, 周润发, 刘德华]
删除之后的元素个数:3
[林志玲, 梁朝伟, 周润发]
------使用增强for遍历------
林志玲
梁朝伟
周润发
------使用迭代器遍历------
林志玲
梁朝伟
周润发
false
false

HashSet使用案例2:

添加类数据,新建一个Person类

package com.qf.chapter12_3;public class Person {private String name;private int age;//添加无参构造public Person() {}//添加带参构造public Person(String name, int age) {this.name = name;this.age = age;}//添加getter and setterpublic String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}//为了好打印数据,重写一个toString方法    Alt+Ins→toString()@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +", age=" + age +'}';}
}

然后进行集合操作:

package com.qf.chapter12_3;import java.util.HashSet;public class Demo03 {public static void main(String[] args) {//创建集合HashSet<Person> persons = new HashSet<>();//①添加数据Person p1 = new Person("刘德华",20);Person p2 = new Person("林志玲",22);Person p3 = new Person("梁朝伟",25);persons.add(p1);//把p1的引用/地址添加到person集合中来persons.add(p2);persons.add(p3);persons.add(p3);//因为地址是重复的,所以添加不进去//但是采用new的方式会新建一个对象,所以引用也不同,就可以添加进集合persons.add(new Person("梁朝伟",25));System.out.println("元素个数:"+persons.size());System.out.println(persons);    //因为重写了toString(),所以输出格式好//②删除操作persons.remove(p1);//因为重写了hashcod和equals,所以下一行代码的删除方式也是可以的//persons.remove(new Person("刘德华",20));System.out.println("删除之后:"+persons.size());//③遍历//增强forSystem.out.println("------使用增强for遍历------");for (Person person : persons) {System.out.println(person);}//迭代器System.out.println("------使用迭代器遍历------");Iterator<Person> it = persons.iterator();while(it.hasNext()){System.out.println(it.next());}//④判断System.out.println(persons.contains(p2));//同理,因为重写了hashcod和equals,所以下一行代码的判断方式也是可以的//System.out.println(persons.contains(new Person("林志玲",22)));System.out.println(persons.isEmpty());}
}
结果:
元素个数:4
[Person{name='刘德华', age=20}, Person{name='梁朝伟', age=25}, Person{name='梁朝伟', age=25}, Person{name='林志玲', age=22}]
删除之后:2
------使用增强for遍历------
Person{name='林志玲', age=22}
Person{name='梁朝伟', age=25}
------使用迭代器遍历------
Person{name='林志玲', age=22}
Person{name='梁朝伟', age=25}
true
false
HashSet存储方式/重复依据

生成hashCode和equals的快捷键方法

通过Alt+Ins选择"equals() and hashCode()"即可,如下图所示:

补充:

用上述方法自动生成的hashCode会用到“31”,如下图所示:

因为

  • 31是一个质数/素数,可以尽量减少散列冲突/让根据hashCode计算出来的元素所处位置尽量不一样
  • 使用31可以提高执行效率(将乘法运算转换成位运算):31*i=(i<<5)-i 。
TreeSet概述
  • 存储结构:红黑树/红黑树就是二叉查找树。所以元素不重复
  • 基于排序顺序来实现的,所以元素不重复
  • 实现了SortedSet接口,对集合元素自动排序
  • 元素对象的类型必须实现Comparable接口,制定排序规则
  • 通过CompareTo方法确定是否为重复元素
TreeSet的Comparable接口的使用
package com.qf.chapter12_3;import java.util.Iterator;
import java.util.TreeSet;public class Demo04 {public static void main(String[] args) {//创建集合TreeSet<String> treeSet = new TreeSet<>();//①添加元素treeSet.add("xyz");treeSet.add("abc");treeSet.add("hello");treeSet.add("xyz"); //红黑树无法添加重复元素System.out.println("元素个数:"+treeSet.size());System.out.println(treeSet);//②删除元素//treeSet.remove("xyz");//System.out.println("删除之后的元素个数:"+treeSet.size());//③遍历//使用增强forSystem.out.println("------使用增强for遍历------");for (String s : treeSet) {System.out.println(s);}//使用迭代器System.out.println("------使用迭代器遍历------");Iterator<String> it = treeSet.iterator();while(it.hasNext()){System.out.println(it.next());}//④判断System.out.println(treeSet.contains("abc"));}}
结果:
元素个数:3
[abc, hello, xyz]
------使用增强for遍历------
abc
hello
xyz
------使用迭代器遍历------
abc
hello
xyz

添加其余类元素,会出现下列问题:

出现了类型转换错误:Person类型不能转换成Comparable类型,为了将p1-p3添加到Treeset当中,要求:Person元素必须实现Comparable接口

于是打开Person类:

Comparable接口中只有一个方法,在Person类中进行重写:

然后就可以进行添加和其他操作:

package com.qf.chapter12_3;import java.util.TreeSet;public class Demo05 {public static void main(String[] args) {//创建集合TreeSet<Person> persons = new TreeSet<>();//①添加元素//**也就是说,实现Comparable接口之后,返回值是0就说明元素重复,不能添加。**Person p1 = new Person("刘德华",20);Person p2 = new Person("林志玲",22);Person p3 = new Person("梁朝伟",25);Person p4 = new Person("刘德华",30);persons.add(p1);persons.add(p2);persons.add(p3);persons.add(p4);    //实现接口之后,先比较姓名然后比较年龄,年龄大的排在后面System.out.println("元素个数:"+persons.size());System.out.println(persons);//②删除元素persons.remove(p1);System.out.println(persons.size());//System.out.println(new Person("刘德华",20)); 也是可以删除的,因为Comparable接口现在比较的是姓名+年龄//③遍历//使用增强forSystem.out.println("------使用增强for遍历------");for (Person person : persons) {System.out.println(person);}//使用迭代器System.out.println("------使用迭代器遍历------");Iterator<Person> it = persons.iterator();while(it.hasNext()){System.out.println(it.next());}//④判断System.out.println(persons.contains(p1));// System.out.println(persons.contains(new Person("刘德华",20));   同理,也是因为Comparable接口的原因,返回值是true}}
结果:
元素个数:4
[Person{name='刘德华', age=20}, Person{name='刘德华', age=30}, Person{name='林志玲', age=22}, Person{name='梁朝伟', age=25}]
3
------使用增强for遍历------
Person{name='刘德华', age=30}
Person{name='林志玲', age=22}
Person{name='梁朝伟', age=25}
------使用迭代器遍历------
Person{name='刘德华', age=30}
Person{name='林志玲', age=22}
Person{name='梁朝伟', age=25}
TreeSet的Comparator接口的使用

用来实现定制比较(比较器)

package com.qf.chapter12_3;import java.util.Comparator;
import java.util.TreeSet;public class Demo06 {public static <p1, p3> void main(String[] args) {//创建集合,并指定比较规则//创建集合的时候把比较规则告诉他/制定比较规则,这样Person类中的元素就不必要实现Comparator这个接口了TreeSet<Person> persons = new TreeSet<>(new Comparator<Person>() {//不能new接口,所以使用匿名内部类//在Comparator接口中只需要实现一个方法就行了——compare@Overridepublic int compare(Person o1, Person o2) {int n1 = o1.getAge()-o2.getAge();int n2 = o1.getName().compareTo(o2.getName());return n1==0?n2:n1;}});Person p1 = new Person("刘德华",20);Person p2 = new Person("林志玲",22);Person p3 = new Person("梁朝伟",25);Person p4 = new Person("野原广志",25);persons.add(p1);persons.add(p2);persons.add(p3);persons.add(p4);System.out.println(persons);}
}
结果:
[Person{name='刘德华', age=20}, Person{name='林志玲', age=22}, Person{name='梁朝伟', age=25}, Person{name='野原广志', age=25}]

Tree案例:

package com.qf.chapter12_3;import java.util.Comparator;
import java.util.TreeSet;
//使用TreeSet集合实现字符串按照长度进行排序  → 定制比较规则
public class Demo07 {public static void main(String[] args) {//创建集合,并指定比较规则TreeSet<String> treeSet = new TreeSet<>(new Comparator<String>(){@Overridepublic int compare(String o1, String o2) {int n1 = o1.length()-o2.length();int n2 = o1.compareTo(o2);  //如果长度相同就按照之前的字符串比较规则来return n1==0?n2:n1;}});//添加数据treeSet.add("helloworld");treeSet.add("pingguo");treeSet.add("lisi");treeSet.add("zhangsan");treeSet.add("beijing");treeSet.add("cat");treeSet.add("nanjing");treeSet.add("xian");System.out.println(treeSet); }
}
结果:
[cat, lisi, xian, beijing, nanjing, pingguo, zhangsan, helloworld]

Map集合

Map集合概述

  • 结构和特点:

  • 方法

    • V put(K key,V value) //将对象存入集合中,关联键值。key重复则覆盖原值
    • Object get(Object key) //根据键获取对应的值
    • Set //返回所有的key
    • Collection values() //返回包含所有值的Collection集合
    • Set<Map.Entry<K,V>> //键值匹配的Set集合

Map接口使用

Map遍历有两种方式:

  • keySet:将Map中所有的键/key拿出来当做一个集合,然后使用增强for遍历,再通过get()方法用key获取值/value
  • Map.Entry(效率高一点):把key和value封装成Entry/键值对,再用增强for遍历,用getkey()和getvalue()分别的到键和值
  • 过程如图所示:

使用范例:

package com.qf.chapter12_4;import java.util.HashMap;
import java.util.Map;
import java.util.Set;public class Demo01 {public static void main(String[] args) {//创建Map集合Map<String,String> map = new HashMap<>();//①添加元素map.put("cn","中国");map.put("uk","英国");map.put("usa","美国");map.put("cn","zhongguo");   //键一样无法添加,但是替换了value值System.out.println(map.size());System.out.println(map);//②删除元素map.remove("cn");System.out.println("删除之后:"+map.size());System.out.println(map);//③遍历//使用keySet  返回值是所有key的Set集合System.out.println("------使用keySet遍历------");Set<String> keyset = map.keySet();//再使用增强for遍历Set集合里面的keyfor (String s : keyset) {System.out.println(s+"-------"+map.get(s)); //用key获取value的值}System.out.println("------使用Map.Entry遍历------");//使用entryset方法 也是一个Set集合,集合里面的类型是Map.Entry,这是在Map接口里面写的一个内部接口(内部接口要加前缀),代表一个个的键值对,类型就是<String,String>Set<Map.Entry<String,String>> entries = map.entrySet();//再使用增强for遍历for (Map.Entry<String, String> entry : entries) {System.out.println(entry.getKey()+"------"+entry.getValue());}//④判断System.out.println(map.containsKey("cn"));System.out.println(map.containsValue("泰国"));}
}
结果:
3
{usa=美国, uk=英国, cn=zhongguo}
删除之后:2
{usa=美国, uk=英国}
------使用keySet遍历------
usa-------美国
uk-------英国
------使用Map.Entry遍历------
usa------美国
uk------英国

HashMap使用

  • 存储结构:数组+链表+红黑树

  • 线程不安全/单线程情况下使用

  • 运行效率快

  • 允许用null作为key或value

加载因子:当存储数量到达总的存储量的75%时进行扩容

使用范例:

先创建Student类:

package com.qf.chapter12_4;import java.util.Objects;public class Student {private String name;private int stuNo;//添加无参构造public Student() {}//添加带参构造public Student(String name, int stuNo) {this.name = name;this.stuNo = stuNo;}//添加getter and setter方法public String getName() {return name;}public void setName(String name) {this.name = name;}public int getStuNo() {return stuNo;}public void setStuNo(int stuNo) {this.stuNo = stuNo;}//为了好打印,重写toString方法@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", stuNo=" + stuNo +'}';}//重写HashCode和equals@Overridepublic boolean equals(Object o) {if (this == o) return true;if (!(o instanceof Student)) return false;Student student = (Student) o;return getStuNo() == student.getStuNo() && Objects.equals(getName(), student.getName());}@Overridepublic int hashCode() {return Objects.hash(getName(), getStuNo());}
}

然后进行操作:

package com.qf.chapter12_4;import java.util.HashMap;
import java.util.Map;
import java.util.Set;public class Demo02 {public static void main(String[] args) {//创建集合HashMap<Student,String> students = new HashMap<>();//①添加元素Student s1 = new Student("孙悟空",100);Student s2 = new Student("猪八戒",101);Student s3 = new Student("沙和尚",102);students.put(s1,"北京");students.put(s2,"上海");students.put(s3,"杭州");students.put(s3,"南京");  //上海/北京/杭州这和些value是可以重复的,虽然没有加进来,但是s3的value值变了students.put(new Student("孙悟空",100),"杭州");  //除了value其余都一样,是可以加进来的,因为new的地址和之前的”沙和尚102“不同,所以可以存储在HashMap中//去重需要在Student类中重写equals和HashCodeSystem.out.println(students.size());System.out.println(students);//②删除students.remove(s1);    //删除的是"s1"所指的元素,不是删除第一个输出的元素System.out.println("删除之后:"+students.size());//③遍历//使用keySetSystem.out.println("------使用keySet遍历------");Set<Student> keyset = students.keySet();for (Student student : keyset) {System.out.println(student+"======"+students.get(student));}//如果把创建Set写进增强for里面,就会变成下面的写法//for(Student key:students.keySet()){//    System.out.println(key+"======"+students.get(key));//}//使用Entry//使用entrySystem.out.println("------使用Map.Entry遍历------");Set<HashMap.Entry<Student,String>> entries = students.entrySet();for (Map.Entry<Student, String> entry : entries) {System.out.println(entry.getKey()+"======"+entry.getValue());}//如果把创建Entry写进增强for里面,就会变成下面的写法//for(Map.Entry<Student,String> entry:students.entrySet()){//    System.out.println(entry.getKey()+"======"+entry.getValue());//}//④判断System.out.println(students.containsKey(s1));System.out.println(students.containsKey(new Student("猪八戒",101))); //因为重写了equals()和hashcode(),判断依据发生了变化,所以是trueSystem.out.println(students.containsValue("杭州"));System.out.println(students);}
}
结果:
3
{Student{name='猪八戒', stuNo=101}=上海, Student{name='沙和尚', stuNo=102}=南京, Student{name='孙悟空', stuNo=100}=杭州}
删除之后:2
------使用keySet遍历------
Student{name='猪八戒', stuNo=101}======上海
Student{name='沙和尚', stuNo=102}======南京
------使用Map.Entry遍历------
Student{name='猪八戒', stuNo=101}======上海
Student{name='沙和尚', stuNo=102}======南京
false
true
false
{Student{name='猪八戒', stuNo=101}=上海, Student{name='沙和尚', stuNo=102}=南京}

HashMap源码分析

假设键值/key相同,会进入equals()算法,如果为true就表明键值相同,不会存放,但会替换掉value值;如果为false就会接在相同键值的链表里。

如上图所示,注意"8"的含义。

部分源码:

键值对Entery就是一个个节点,如下图所示:

总结:

  • 当HashMap刚创建时,table是null,为了节省空间,当添加第一个元素时,table容量调整为16。
  • 当元素个数大于阈值(16*0.75=12)时,会进行扩容,扩容后大小为原来的2倍。目的是为了调整元素的个数。
  • jdl1.8当每个链表长度>8,并且元素个数≥64时,会将链表调整为红黑树,目的是方便查找,提高执行效率。
  • jdl1.8当链表长度<6时,就会调整为链表
  • jdl1.8以前,链表是头插入,jdl1.8以后是尾插入。

HashSet和HashMap

HashSet源码里面用的就是HashMap

Hashtable/了解

线程安全,运行慢;不允许用null作为key或value

Properties

是Hashtable的子类,要求key和value都是String。通常用于配置文件的读取。和"流"相似。

TreeMap使用

  • 存储接口是红黑树,可以进行排序。

如上图所示:因为存储结构是红黑树,需要进行比较,而s1、s2、s3是Student结构,该类只重写equals()和hashCode()是不足以进行比较的,所以报错。

需要在其类中重写Comparable接口,如下图所示:

使用范例:

package com.qf.chapter12_4;import java.util.Map;
import java.util.TreeMap;public class Demo03 {public static void main(String[] args) {//创建集合TreeMap<Student,String> treeMap = new TreeMap<>();//①添加元素 实现Comparable接口  也可以在创建TreeMap的时候传入比较器Comparator:TreeMap<Student,String> treeMap = new TreeMap<>(new Comparator<Student>);   然后在其相应的函数compare里面写比较方法即可Student s1 = new Student("孙悟空",100);Student s2 = new Student("猪八戒",101);Student s3 = new Student("沙和尚",102);treeMap.put(s1,"北京");treeMap.put(s2,"上海");treeMap.put(s3,"深圳");treeMap.put(new Student("沙和尚",102),"南京");   //比较依据是重写的Comparable(),而学号一样就无法添加。但是value被替代了System.out.println(treeMap.size());System.out.println(treeMap);//②删除//treeMap.remove(s3);//System.out.println("删除之后:"+treeMap.size());//treeMap.remove(new Student("猪八戒",101)); //是可以删除的,因为也用了comparable接口,学号一样就可以删除//③遍历System.out.println("------使用keySet遍历------");for (Student key: treeMap.keySet()) {System.out.println(key+"======"+treeMap.get(key));}System.out.println("------使用entrySet遍历------");for(Map.Entry<Student,String> entry: treeMap.entrySet()){System.out.println(entry.getKey()+"======"+entry.getValue());}//④判断System.out.println(treeMap.containsKey( new Student("孙悟空",100)));}
}
结果:
3
{Student{name='孙悟空', stuNo=100}=北京, Student{name='猪八戒', stuNo=101}=上海, Student{name='沙和尚', stuNo=102}=南京}
------使用keySet遍历------
Student{name='孙悟空', stuNo=100}======北京
Student{name='猪八戒', stuNo=101}======上海
Student{name='沙和尚', stuNo=102}======南京
------使用entrySet遍历------
Student{name='孙悟空', stuNo=100}======北京
Student{name='猪八戒', stuNo=101}======上海
Student{name='沙和尚', stuNo=102}======南京
true

Collections工具类

定义了除存取以外的集合重用方法:

  • public static void reverse(List<?> list) //翻转集合中元素的顺序
  • public static void shuffle(List<?> list) //随机重置集合里的元素顺序
  • public static void sort (List list) //升序排序(必须实现Comparable接口)

使用范例:

package com.qf.chapter12_4;import java.util.*;public class Demo04 {public static void main(String[] args) {List<Integer> list = new ArrayList<>();list.add(20);list.add(5);list.add(12);list.add(30);list.add(6);//sort排序System.out.println("排序之前:"+list);Collections.sort(list);System.out.println("排序之后:"+list);//binarySearch 二分查找System.out.println(Collections.binarySearch(list,12));System.out.println(Collections.binarySearch(list,1112));    //负数就是不存在//copy  要求目的集合dest和源集合src的大小相同,而刚创建的集合其大小默认是0List<Integer> dest = new ArrayList<>();for(int i=0;i<list.size();i++){dest.add(0);    //通过添加零元素来占位子,是两个集合大小相同}Collections.copy(dest,list);System.out.println(dest);//reverseCollections.reverse(list);System.out.println("反转之后:"+list);//shuffle   打乱Collections.shuffle(list);System.out.println("打乱之后:"+list);System.out.println("======集合list转成数组======");Integer[] arr = list.toArray(new Integer[5]);Integer[] arr2 = list.toArray(new Integer[10]);System.out.println(arr.length);System.out.println(Arrays.toString(arr));   //便于查看结果,把arrays变成字符串输出出来System.out.println(Arrays.toString(arr2));System.out.println("======数组转成集合list======");String[] names = {"张三","李四","王五"};List<String> nammelist = Arrays.asList(names);//转换成的集合是受限集合,不能添加或删除//nammelist.add("111"); 会出错System.out.println(nammelist);//int[] nums = {100,200,300,400,500};//这里不能写List<Integer>,改成下图写法之后,numslist集合中的元素就不是数字/Integer了,变成数组/int[]了//List<int[]> numslist = Arrays.asList(nums);//所以要更改创建数组的方法: 基本类型数组转换成集合时,需要修改为包装类型Integer[] nums = {100,200,300,400,500};List<Integer> numslist = Arrays.asList(nums);System.out.println(numslist);}
}
结果:
排序之前:[20, 5, 12, 30, 6]
排序之后:[5, 6, 12, 20, 30]
2
-6
[5, 6, 12, 20, 30]
反转之后:[30, 20, 12, 6, 5]
打乱之后:[6, 12, 5, 20, 30]
======集合list转成数组======
5
[6, 12, 5, 20, 30]
[6, 12, 5, 20, 30, null, null, null, null, null]
======数组转成集合list======
[张三, 李四, 王五]
[100, 200, 300, 400, 500]

总结

集合和数组类似,是存储多个对象的容器。但是集合大小可以更改,只能存储引用类型数据。同时定义了对多个对象进行操作的方法。

Java塈百日而求新,念三番未发,其一相关推荐

  1. Java塈百日而求新,念三番未发,其二

    Java IO框架 流 是内存与存储设备(硬盘)之间传输数据的通道. 数据借助流来传输. 流的分类 按方向来分: 输入流:将 存储设备 中的内容读入 内存 输出流:将 内存 中的内容写入 存储设备 中 ...

  2. Visual Age for Java_VisualAge for Java使用技巧

    VisualAge for Java使用技巧 杨 旭 青 IBM 的VisualAge for Java 是 一 个 功 能 强 大. 灵 活 的Java 编程 环 境, 本 文 介 绍 几 个 与 ...

  3. java解压,压缩.gz文件

    /*      * gz文件是linux下常见的压缩格式.使用 java.util.zip.GZIPInputStream即可,压缩是 java.util.zip.GZIPOutputStream   ...

  4. 唯有作茧自缚,方可破茧成蝶

    作茧自缚,原是贬义词,比喻做了某件事,结果使自己受困;或者说是自己给自己找麻烦.可是,在我的世界里,它代表的从不是这个意思.于我而言,唯有作茧自缚,方可破茧成蝶. 一开始做这个决定的时候,没想到今天会 ...

  5. java未发现数据源名称并且未指定默认驱动程序_转:java.sql.SQLException: [Microsoft][ODBC 驱动程序管理器] 未发现数据源名称并且未指定默认驱动程序...

    在Win7 64位系统下,使用Java+Access数据库编程,用Java连数据库时,出现错误提示,如下: Java java.sql.SQLException: [Microsoft][ODBC 驱 ...

  6. java念整数 你的程序要读入一个整数,范围是[-100000,100000]。然后,用汉语拼音将这个整数的每一位输出出来。 如输入1234,则输出: yi er san si

    念整数(5分) 题目内容: 你的程序要读入一个整数,范围是[-100000,100000].然后,用汉语拼音将这个整数的每一位输出出来. 如输入1234,则输出: yi er san si 注意,每个 ...

  7. 7-25 念数字(15 分)Java与C++

    7-25 念数字(15 分) 输入一个整数,输出每个数字对应的拼音.当整数为负数时,先输出fu字.十个数字对应的拼音如下: 0: ling 1: yi 2: er 3: san 4: si 5: wu ...

  8. Java实现 蓝桥杯VIP 算法提高 分分钟的碎碎念

    算法提高 分分钟的碎碎念 时间限制:1.0s 内存限制:256.0MB 问题描述 以前有个孩子,他分分钟都在碎碎念.不过,他的念头之间是有因果关系的.他会在本子里记录每一个念头,并用箭头画出这个念头的 ...

  9. java数字相减_Java的百日计划(day14)

    ###14.01_常见对象(正则表达式的概述和简单使用) * A:正则表达式 * 是指一个用来描述或者匹配一系列符合某个语法规则的字符串的单个字符串.其实就是一种规则.有自己特殊的应用. * 作用:比 ...

最新文章

  1. iOS开发-Certificates、Identifiers和Profiles详解
  2. 计算机组成原理(一)计算机系统概述
  3. python字符串倒数第三个_python字符串常用方法
  4. 解决IE8 无法使用 JS 中Array() 的 indexOf 方法
  5. link 和 style 元素在 HTML 文档中的位置
  6. 2016/2/13 《计算机系统要素》(The Elements of Computing Systems)读书笔记(1)
  7. c语言实现简易图书管理系统
  8. 批量生成测试非重复命名的图片数据
  9. 计算机小写换大写函数,Excel函数公式应用:小写数字转换成人民币大写9种方法-excel技巧-电脑技巧收藏家...
  10. linux 筛选重复数据,Linux下uniq筛选
  11. 智慧幼儿园方案:AI技术如何助力幼儿园智慧建设?
  12. 今日头条怎么引流?头条暴力引流方法
  13. 计算机系单身率排行榜,中国高校单身率排行榜,第一名实至名归!
  14. TCP/IP协议分层模型详解
  15. android环境监测,基于Wi―Fi和Android家居环境监测与实现
  16. c#串口模拟互发数据(COM1-COM2)
  17. mongoDB从入门到实战最全小白教程
  18. 用PyTorch来实现手写体数字识别
  19. python爬虫之音乐下载
  20. 西门子200smart与施耐德ATV变频器modbus通讯 西门子s7-200smart与施耐德ATV12变频器通讯

热门文章

  1. 游戏开发中的问题-----摘自《大型多人在线游戏开发》
  2. linux下如何设置固定的ip地址?
  3. 使用Visual Studio 2010 创建简单的Silverlight应用程序
  4. Centos7下挂载超过2T以上硬盘
  5. Prism:Uber 的 Presto 查询网关服务
  6. Android微信/QQ红包自动抢(AccessibilityService)
  7. 模电笔记1:半导体基础知识
  8. 数据库实验一 在SQL Server 中创建数据库
  9. 爬虫——把jason格式用pandas化成dataframe
  10. [WPF] WPF做的漂亮的登陆界面[附源码]