Java进阶知识学习:集合

目录,更新ing,学习Java的点滴记录

  目录放在这里太长了,附目录链接大家可以自由选择查看--------Java学习目录

引入

  1. 在实现方法的时候,选择不同的数据结构会导致其实现风格以及性能存在很大的差异.比如是否需要快速的搜索成千上万个有序数据项,是否需要快速的在有序的序列中间插入元素或删除元素,是否需要建立键与值之间的关联,而Java中的集合类库就可以帮助我们在程序设计中实现传统的数据结构
  2. Java最初版本只为最常用的数据结构提供了很少的一组类:Vector,Stack,Hashtable,BigSet与Enumeration接口,但是缺乏一个全面的集合类库.
  3. Java使用类库中提供了一套相当完整的集合类来解决这个问题,统称为集合类

基本概念

  1. Java集合类类库的用于是保存对象,可以分为两大类:一类是实现Collection接口,另一类实现Map接口
     1) Collection.一个独立元素的序列,这些元素都服从一条或多条规则.List必须按照插入的顺序保存元素,而Set不能有重复元素,Queue按照排队规则来确定对象产生的顺序(通常与插入顺序相同)
    Java进阶知识学习:集合_第1张图片
     2) Map,一组成对的"键值对"对象,允许使用键来查找值.
    Java进阶知识学习:集合_第2张图片
  2. Java集合框架提供了一套性能优良,使用方便的接口和类,位于java.util包中,存放在集合中的数据被称为元素

Collection接口

  1. 在Java类库中,集合类的基本接口是Collection接口,下图为Collection接口中定义的方法:
    Java进阶知识学习:集合_第3张图片 其中add方法用于向集合中添加元素,如果添加元素改变了集合就返回true,如果集合没有发生变化就返回false
     iterator方法用于返回一个实现了Iterator接口的对象,可以使用这个迭代器对象依次访问集合中的元素
  2. 常用方法介绍
    在这里插入图片描述
    Java进阶知识学习:集合_第4张图片
    Java进阶知识学习:集合_第5张图片

迭代器

  1. 任何集合类,都必须有某种方式可以插入元素并将它们再次取回.毕竟,持有事物是集合最基本的工作.迭代器(也是一种设计模式)的概念可以用于达成此目的.迭代器是一个对象,它的工作是遍历并选择序列中的对象.
  2. Iterator接口包含4个方法
    Java进阶知识学习:集合_第6张图片
    Java进阶知识学习:集合_第7张图片在这里插入图片描述
    &emsp通过反复调用next方法,可以逐个访问集合中的每个元素.但是,如果到达了集合的末尾,next方法就会抛出一个NoSuchElementException.因此需要在调用next之前调用hasNext方法.如果迭代器对象还有多个供访问的元素,这个方法就返回true,如果想查看集合中的所有元素,就请求一个迭代器,并在hasNext返回true时反复调用next方法
  3. Collection接口扩展了Iterable接口.因此,对于标准类库中任何集合都可以使用迭代器遍历
  4. 需要注意的是,使用迭代器遍历的话元素被访问的顺序取决于集合类型.如果对ArrayList进行迭代,迭代器将从索引0开始,每迭代一次,索引值+1.然后如果访问HashSet中的元素,每个元素将会按照某种随机的次序出现.
  5. 此外,应该将Java迭代器认为是位于两个元素之间.当调用next时,迭代器就越过下一个元素,并返回刚刚越过的那个元素的引用
    Java进阶知识学习:集合_第8张图片
  6. Iterator接口的remove方法将会删除上次调用next方法时返回的元素,对于next方法和remove方法的调用具有互相依赖性.如果调用remove之前没有调用next将是不合法的.如果这样做将会抛出一个IllegalStateException异常
  7. ListIterator接口是一个更加强大的Iterator的子类型,它只能用于各种List类的访问.并且可以实现双向移动,它可以产生相对于迭代器在列表中指向的当前位置的前一个和后一个元素的索引,并且可以使用set()方法替换它访问过的最后一个元素.
    Java进阶知识学习:集合_第9张图片
  8. 迭代器遍历List,Set示例
    Java进阶知识学习:集合_第10张图片
  9. 迭代器遍历Map示例
    Java进阶知识学习:集合_第11张图片
    Java进阶知识学习:集合_第12张图片

List接口

  1. List是一个存储元素有序且不唯一集合.元素会增加到容器的特定位置.可以采用两种方式访问元素:`使用迭代器访问,或者使用整数索引来访问.迭代器访问时必须顺序访问元素,后一种方式可以进行任意顺序访问元素
  2. List接口在Collection接口的基础上添加了大量的方法,使得可以在List的中间插入和删除元素等等
    Java进阶知识学习:集合_第13张图片
  3. 分类
    ArrayList:擅长随机访问元素,但是在ArrayList中间插入和移除元素较慢
    LinkedList,擅长插入和删除操作,在随机访问方面比较慢

List接口–ArrayList

  1. ArrayList是一个数组队列,相当于动态数组.与Java中数组相比,它的容量能动态增长.继承与AbstractList,实现了List,RandomAccess, Cloneable, java.io.Serializable这些接口。
     ArrayList 继承了AbstractList,实现了List。它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。
     ArrayList 实现了RandmoAccess接口,即提供了随机访问功能。RandmoAccess是java中用来被List实现,为List提供快速访问功能的。在ArrayList中,我们即可以通过元素的序号快速获取元素对象;这就是快速随机访问。
     ArrayList 实现了Cloneable接口,即覆盖了函数clone(),能被克隆。
     ArrayList 实现java.io.Serializable接口,这意味着ArrayList支持序列化,能通过序列化去传输。
     ArrayList中的操作不是线程安全的

  2. ArrayList构造函数
    Java进阶知识学习:集合_第14张图片
     特别说明,使用默认构造方法创建了ArrayList之后,当调用list.add()方法时,才会真正设置为初始容量10
    Java进阶知识学习:集合_第15张图片
    Java进阶知识学习:集合_第16张图片
    Java进阶知识学习:集合_第17张图片
    Java进阶知识学习:集合_第18张图片
    Java进阶知识学习:集合_第19张图片
    Java进阶知识学习:集合_第20张图片

  3. ArrayList的继承结构
    Java进阶知识学习:集合_第21张图片

  4. ArrayList中两个中啊哟对象:elementData和size
     1)elementData是"Object[]类型的数组",它保存了添加到ArrayList中的元素。实际上,elementData是个动态数组,我们能通过构造函数 ArrayList(int initialCapacity)来执行它的初始容量为initialCapacity;如果通过不含参数的构造函数ArrayList()来创建ArrayList,则elementData的容量默认是10。elementData数组的大小会根据ArrayList容量的增长而动态的增长
     2) size 则是动态数组的实际大小

  5. 常用方法源码分析(JDK1.8)
     1) add方法
      在这里插入图片描述 2) get方法
      
    Java进阶知识学习:集合_第22张图片
     3) size方法
      在这里插入图片描述
     4) 判断是否为空方法
      在这里插入图片描述
     5) set方法
      Java进阶知识学习:集合_第23张图片
     6) remove方法
      Java进阶知识学习:集合_第24张图片
     7) clear方法
      Java进阶知识学习:集合_第25张图片

  6. ArrayList遍历方式
     第一种,通过迭代器遍历,即通过Iterator遍历
      Java进阶知识学习:集合_第26张图片
     第二种,随机访问,通过索引值
      Java进阶知识学习:集合_第27张图片
     第三种,for循环遍历
      Java进阶知识学习:集合_第28张图片
     遍历ArrayList时,使用随机访问(即,通过索引序号访问)效率最高,而使用迭代器的效率最低

List接口–LinkedList

  1. 数组和数组列表有一个重大的缺陷.就是从数组的中间位置删除和添加元素要付出很大代价,原因在于数组中处于被删除元素之后的所有元素都要向数组的前端移动.而链表这个数据结构解决这个问题,链表将每个对象存放在独立的结点中,每个节点存放着序列中下一个节点的引用.在Java程序设计语言中,所有链表实际上都是双向链表的—即每个结点还存放着指向前驱结点的引用
    Java进阶知识学习:集合_第29张图片
  2. 两种常见链表结构
     1) 单向链表
      Java进阶知识学习:集合_第30张图片
      Java进阶知识学习:集合_第31张图片
     2) 双向链表
      Java进阶知识学习:集合_第32张图片
      Java进阶知识学习:集合_第33张图片
  3. LinkedList也像ArrayList一样实现了基本的List接口,但是它执行某些操作(在List的中间插入和移除)时比ArrayList更加高效,但在随机访问操作方面会差一点
  4. 常用方法
    Java进阶知识学习:集合_第34张图片
  5. LinkedList添加了一些可以让它作为栈,队列或双端队列的方法.这些方法彼此之间只是名称有些差异.
     getFirst()和element()完全一样,它们否返回列表的头,而并不会移除它,如果list为空,则抛出NoSuchElementException,peek()方法在列表为空时返回null
     removeFirst()与remove()也是完全一样的,他们移除并返回列表的头,而在列表为空时抛出NoSuchElementException异常,poll()稍有区别,在列表为空时返回null
     addFirst()与ad()和addLast()相同,都将某个元素插入到列表的尾部
  6. 源码分析几个常用方法
     1)创建一个LinkedList对象
      在这里插入图片描述
      Java进阶知识学习:集合_第35张图片
     2)添加一个元素
      Java进阶知识学习:集合_第36张图片
      Java进阶知识学习:集合_第37张图片
     3) addFirst()
      Java进阶知识学习:集合_第38张图片
      Java进阶知识学习:集合_第39张图片
     4) removeFirst()
      Java进阶知识学习:集合_第40张图片
      Java进阶知识学习:集合_第41张图片
     5)removeLast()
      Java进阶知识学习:集合_第42张图片
      Java进阶知识学习:集合_第43张图片

Vector类

  1. Vector结构
    Java进阶知识学习:集合_第44张图片
  2. Vector的用法
    Java进阶知识学习:集合_第45张图片
  3. Vector_JDK源码分析
  4. Vector与ArrayList的区别
     1) Vector类的所有单独方法都是同步的.可以由两个线程安全地访问一个Vector对象.但是,如果由一个线程访问Vector,代码要在同步操作上耗费大量时间.而ArrayList不是同步的,建议在不需要同步使用ArrayList,而不要使用Vector
     2) Vector每次扩容是原先的1倍,ArrayList每次扩容原来的0.5倍(JDK1.8)
     3) Vector在调用构造方法时,直接初始化容量为10,ArrayList是在第一次调用添加方法时,初始化容量为10
     4) Vector线程同步,安全性高,效率低;ArrayList线程不同步,安全性低,效率高
     5) 相同:底层数据结构相同,都是Object类型的数组

Set接口

  1. Set接口特点:元素唯一且无序,加入Set的元素必须定义equals()方法以确保对象的唯一性.
  2. Set不保存重复的元素(至于如何判断元素相同较为复杂).如果你视图将相同对象的多个实例添加到Set中,那么它就会阻止这种重复现象.Set中最常使用的是测试归属性,你可以很容易地询问某个对象是否在某个Set中.正因如此,查找成了Set中最重要的操作,因此你通常都会选择一个HashSet的实现,它专门对快速查找进行优化
  3. Set具有与Collection完全一样的接口,因此没有任何额外的功能,实际上Set就是Collection,只是行为不同.Set是基于对象的值来确定归属性的.
    Java进阶知识学习:集合_第46张图片
  4. 不同的Set实现不仅具有不同的行为,而且它们对于可以在特定的Set中方式的元素的类型也有不同的要求
     1) HashSet(最常用的选择):为快速查找而设计的Set,使用了散列,存入HashSet的元素必须定义hashCode()
     2) TreeSet:保持次序(次序依照compareTo方法定义的规则)的Set,底层为红黑树结构,使用它可以从Set中提取有序的序列,元素必须实现Comparable接口
     3) LinkedHashSet:具有HashSet的查询速度,且内部使用链表维护元素的顺序(插入次序).于是在使用迭代器遍历Set时,结果会按元素插入的次序显示.也使用了散列,元素也必须定义hashCode()方法
     PS:其实上面谈到的定义equals和hasCode方法,我们正常情况下创建一个类就应该要覆盖这两个方法,这也是良好编程风格的体现

Set接口–HashSet

  1. HashSet类,实现了基于散列表的集合.可以用add方法添加元素,使用contains方法快速查看某个元素是否在某个集合中.
  2. HashSet底层结构是hash表(数组+链表),那么HashSet是如何确保添加的数据唯一呢?
     首先调用hashCode方法计算添加对象的哈希码值,然后以计算出的哈希码值为参数,通过函数计算该对象在哈希表的存储位置,其次查看添加对象是否已经存在于哈希表中,如果元素已经存在则返回false,表示已存在,否则添加成功
  3. 强调:HashSet(最常用的选择):为快速查找而设计的Set,使用了散列,存入HashSet的元素必须定义hashCode()
  4. 常用方法
    Java进阶知识学习:集合_第47张图片在这里插入图片描述
  5. 示例
    Java进阶知识学习:集合_第48张图片
  6. 几个关键方法的底层源码(相关Map的知识见后面)
     1) 构造方法
      Java进阶知识学习:集合_第49张图片
      发现底层实际上是创建了一个HashMap
     2) add方法
      Java进阶知识学习:集合_第50张图片
     3) size方法
      Java进阶知识学习:集合_第51张图片
     4) contains方法
      Java进阶知识学习:集合_第52张图片
     5) iterator方法
      Java进阶知识学习:集合_第53张图片

Set接口–TreeSet

  1. TreeSet:唯一且保持次序(次序依照compareTo方法定义的规则)的Set,底层为红黑树结构,使用它可以从Set中提取有序的序列,元素必须实现Comparable接口(Comparable是一个内部比较器).
  2. TreeSet也可称为树集,它是一个有序集合,可以以任意顺序将元素插入到集合中.在对集合进行遍历时,每个值将自动按照排序后的顺序呈现.
  3. 常见方法
    Java进阶知识学习:集合_第54张图片
  4. 常见方法源码分析
     1) 构造方法源码
      Java进阶知识学习:集合_第55张图片
      可以看出TreeSet底层就是使用的TreeMap
     2) add方法
      Java进阶知识学习:集合_第56张图片

Set接口–LinkedHashSet

  1. LinkedHashSet:具有HashSet的查询速度,且内部使用链表维护元素的顺序(插入次序).于是在使用迭代器遍历Set时,结果会按元素插入的次序显示.也使用了散列,元素也必须定义hashCode()方法

Queue接口

  1. 队列是一个先进先出的容器,从容器一端放入事物,从另一端取出,并且事物放入容器的顺序与取出的顺序是相同的.
  2. LinkedList实现了Deque接口,而Deque接口继承Queue接口,因此LinkedList可以用作队列的一种实现.LinkedList中的offer方法是与Queue相关的方法之一,它在允许的情况下,将一个元素插入队尾,或者返回false.peek()和element都将在不移除的情况下返回队头,但是peek()方法在队列为空时返回null,而element()会抛出NoSuchElementException异常.pool()和remove()方法将移除并返回队头,但是poll()在队列为空时返回null,而remove()会抛出NoSuchElementException异常
  3. 先进先出声明的是下一个元素应该是等待时间最长的元素.而优先级队列声明下一个弹出元素是最需要的元素(具有最高的优先级).PriorityQueue(优先级队列)上调用offer()方法来插入一个对象时,这个对象会在队列中被排序.默认的排序将使用对象在队列中的自然顺序,但是可以通过提供自己的Comparator来修改这个顺序.PriorityQueue可以确保当调用peek(),poll()和remove()方法时,获取的元素将是队列中优先级最高的元素.

Map接口

  1. 将对象映射到其他对象的能力是一种解决编程问题的杀手锏.通常,我们知道某些键的信息,并想要查找与之对应的元素.映射(map)数据结构就是为此设计的.映射用来存放键/值对.比如:提供了键,就能够找到值.
  2. Map与数组和其他Collection一样,可以很容易扩展到多维,而我们只需要将其值设置为Map(也可以是其他容器).因此,我们可以很容易地将容器组合起来从而快速生成强大的数据结构.例如保存一个人的多个兴趣爱好,Map
  3. 映射的基本思想是它维护的是键–值关联,因此你可以用键来查找值.标准的Java类库包含了Map的几种基本实现,包括:HashMap,TreeMap,LinkedHashMap,WeakHashMap,ConcurrentHashMap,IdentityHashMap等等.他们有着相同的基本接口Map,但是行为特性各不相同,这表现在效率,键值对的保存及呈现测序,对象的保存周期等策略方面.
     1) HashMap(这也是最推荐使用的,对速度进行了优化,是最快的):Map基于散列表的实现(它取代了Hashtable).插入和查询"键值对"的开销是固定的.可以通过构造器设置容量装填因子以调整容器的性能,键对象必须实现hashCode()方法
     2) LinkedHashMap:类似于HashMap,但是迭代遍历时,取得"键值对"的顺序是其插入次序,或者是最近最少使用(LRU)的次序.只比HashMap慢一点,而在迭代访问时反而更快,因为它使用链表维护内部次序,键对象必须实现hashCode()方法
     3) TreeMap:基于红黑树的实现.查看"键"或"键值对"时,它们会被排序(次序由内部排序器Comparable或外部排序器Comparator决定).TreeMap的特点在于,所得到的的结果是经过排序的-.TreeMap是唯一带有subMap()方法的Map,它可以返回一个子树.键对象必须实现Comparable或者自定义Comparator.
     4) WeakHashMap:弱键映射,允许释放映射所指向的对象,这是为解决某类特殊问题而设计的.如果映射之外没有引用指向某个"键",则此"键"可以被垃圾收集器回收.
     5) ConcurrenthashMap:一种线程安全的Map,它不涉及同步加锁
     6) IdentityHashMap:使用==代替equals对"键"进行比较的散列映射.
  4. Map类型的键必须是唯一的,值可以有重复,任何键都必须具有一个equals方法
  5. Map接口中定义的方法
    Java进阶知识学习:集合_第57张图片

Map接口–HashMap

  1. HashMap(这也是最推荐使用的,对速度进行了优化,是最快的):Map基于散列表的实现(它取代了Hashtable).插入和查询"键值对"的开销是固定的.可以通过构造器设置容量装填因子以调整容器的性能,键对象必须实现hashCode()方法
     装填因子默认为0.75,为啥装填因子一般为0.75?
    Java进阶知识学习:集合_第58张图片 也就是说,当map中映射数量>0.75*map集合容量的时候,就会自动扩容
  2. 常用方法
    Java进阶知识学习:集合_第59张图片
  3. 示例
    Java进阶知识学习:集合_第60张图片Java进阶知识学习:集合_第61张图片
  4. HashMap与Hashtable的区别
      1) HashMap继承AbstractMap,实现了Map接口;Hashtable继承Directory,实现Map接口
      2) HashMap允许null值和null键,但是null作为key只能有一个;Hashtable不允许null键和值
      3) HashMap线程不同步,效率高,不安全;Hashtable线程同步,效率低,安全性高
  5. HashMap底层结构----哈希表
     1)特点:速度很快
     2) 最常见的结构:顺序表+链表,主结构为顺序表
     3)结构图
      Java进阶知识学习:集合_第62张图片
     4) 步骤:
      1.计算保存的键值对中的键的hashCode值
      2. 采用特定的哈希函数(如直接定址法,平方取中法,除留取余法等),将以hashcode值为参数,根据函数计算出存储位置
      3.如果该位置上有元素,需要调用equals()方法去比较,如果两者相等则直接覆盖,如果不等则在原元素下面使用链表的结构存储该元素
     5) DJK1.8之后,当变量的个数>=8后,就会将链表转为红黑树,目的是为了减少查询比较的次数

Map接口–TreeMap

  1. TreeMap:基于红黑树的实现.查看"键"或"键值对"时,它们会被排序(次序由内部排序器Comparable或外部排序器Comparator决定).TreeMap的特点在于,所得到的的结果是经过排序的-.TreeMap是唯一带有subMap()方法的Map,它可以返回一个子树.键对象必须实现Comparable或者自定义Comparator.
  2. 常用方法
    Java进阶知识学习:集合_第63张图片
  3. 使用案例
    Java进阶知识学习:集合_第64张图片
  4. TreeMap底层一些方法代码
     1) 构造方法
      Java进阶知识学习:集合_第65张图片
      comparator是一个外部比较器,用来比较大小
     2)put方法
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;//初始为null
	// 如果外部比较器不等于null,
	if (cpr != null) {
		do {
			parent = t; //将root赋给父节点
			// 调用外部比较器方法进行比较,结果为int类型
			cmp = cpr.compare(key, t.key);
			if (cmp < 0)
				t = t.left; //左子树查找
			else if (cmp > 0)
				t = t.right; //右子树查找
			else
				return t.setValue(value);//找到,则值覆盖
		} while (t != null);
	}
	else { //外部比较器不存在
		if (key == null)
			throw new NullPointerException();
		@SuppressWarnings("unchecked")
			// 将键赋给一个内部比较器
			Comparable<? super K> k = (Comparable<? super K>) key;
		do {
			parent = t;//root赋给父节点
			cmp = k.compareTo(t.key);//调用内部比较器比较大小的方法,结果返回int
			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;
}

  结合一下3中的使用案例,由于传递的key都是String类型,而String类型默认就实现了Comparable接口,具备比较能力
  Java进阶知识学习:集合_第66张图片
  Java进阶知识学习:集合_第67张图片
  该方法会基于字符串中每个字符的Unicode值进行比较

二叉树和红黑树


  1.  1)树是由一个集合以及在该集合上定义的一种关系构成的。集合中的元素称为树的结点,所定义的关系称为父子关系。
     2)父子关系在树的结点之间建立了一个层次结构
     3)树的结点包含一个数据元素以及若干指向其子树的若干分支
     4)在这种层次结构中有一个结点具有特殊的地位,这个结点称为该树的根节点,或简称树根
      Java进阶知识学习:集合_第68张图片
      例如图(a)是一棵空树、(b)是只有一个根节点的树、(c)是一课有10个结点的树,其中A是根
  2. 二叉树:每个结点的度均不超过2的有序树,称为二叉树
     1) 二叉树中每个结点的孩子数只能是0、1或2个,并且每个孩子都有左右之分
     2)位于左边的孩子称为左孩子,位于右边的孩子称为右孩子
     3)以左孩子为根的子树称为左子树,以右孩子为根的子树称为右子树
    Java进阶知识学习:集合_第69张图片
  3. 二叉查找/搜索/排序树 BST
     1)定义:是一颗空树,或者是具有下列性质的二叉树:
      a.若它的左子树不空,则左子树上所有结点的值均小于它的根节点的值
      b.若它的右子树上所有结点的值均大于它的根节点的值
      c. 它的左右子树也分别为二叉排序树
      Java进阶知识学习:集合_第70张图片
  4. 平衡二叉树
     1)它是一棵空树或它的左右两个子树的高度差(平衡因子)的绝对值不超过1
     2)平衡二叉树:每个结点的平衡因子都为1、-1、0的二叉排序树。或者说每个结点的左右子树的高度最多差1的二叉排序树。
     3) 平衡二叉树的目的是为了减少二叉查找树层次,提高查找速度
    Java进阶知识学习:集合_第71张图片
  5. 红黑树
     定义:它是一种平衡二叉树,红黑树的每个节点都有存储位表示节点的颜色,可以是红或黑
     1)红黑树的特性:
      a.每个节点或者是黑色,或者是红色。
      b.根节点是黑色
      c.每个叶子节点(NULL)是黑色。【注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点】
      d.如果一个节点是红色的,则它的子节点必须是黑色的
      e.从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点
     2) 注意:
      特性c中的叶子节点,是只为空(NUL或NULL)的节点
      特性e,确保没有一条路径会比其他路径长出两倍。因而,红黑树是相对接近平衡的二叉树。
    &emspp;在这里插入图片描述

你可能感兴趣的:(Java)