引言:类集设置的目的
为了方便用户操作各个数据结构,所以引入了类集的概念,可以把类集称为java对数据结构的实现。
类中几个最大的操作接口:Collection(单值)、Map(双值)、Iterator(迭代器)。所有的类集操作的接口或类都在java.util包里。
一、链表与二叉树
1.链表
1)链表的定义
链表是由一组不必相连的内存结构(节点),按特定的顺序链接在一起的抽象数据类型(表示数学中抽象出来的一些操作的集合)。
2)链表的离散存储线性结构
链表的n个节点离散分配,彼此通过指针相连,每个节点只有一个前驱节点和一个后续节点,首节点没有前驱节点,尾节点没有后继节点。
3)链表的优点
空间没有限制,插入删除元素快。
4)链表缺点
存取速度慢
5)链表的分类
单链表
单链表由各个内存结构通过一个 Next 指针链接在一起组成,每一个内存结构都存在后继内存结构(链尾除外),内存结构由数据域 Data 和 Next 指针域组成。
class Node{
Object data;
Node next;
}
双向链表
双链表由各个内存结构通过指针 Next 和指针 Prev 链接在一起组成,每一个内存结构都存在前驱内存结构和后继内存结构(链头没有前驱,链尾没有后继),内存结构由数据域、Prev 指针域和 Next 指针域组成。
class Node{
Node prev;
Object data;
Node next;
}
循环链表–循环单链表
循环单链表由各个内存结构通过一个指针 Next 链接在一起组成,每一个内存结构都存在后继内存结构,内存结构由数据域 Data 和 Next 指针域组成。
链表的尾节点的next指针指向首节点的Data
循环双链表
由各个内存结构通过指针 Next 和指针 Prev 链接在一起组成,每一个内存结构都存在前驱内存结构和后继内存结构,内存结构由数据域 Data 、Prev 指针域和 Next 指针域组成。
6)链表的核心操作
插入、删除、查找(遍历)
7)下面对于单链表作简单代码示例:
(1)节点:
import java.util.Objects;
/**
* @author Mr Bai`s Soda
*/
public class Node1<T> {
/**结点值*/
T value;
/**结点的next指针*/
Node1 next;
public Node1(T value) {
this.value = value;
next = null;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Node1)) {
return false;
}
Node1<?> node1 = (Node1<?>) o;
return Objects.equals(value, node1.value);
}
@Override
public int hashCode() {
return Objects.hash(value);
}
@Override
public String toString() {
return "Node1{" + "value=" + value + '}';
}
}
(2)节点的操作类:
/**
* @author Mr Bai`s Soda
*/
public class MyLinkedList<T> {
/**定义头结点和表示结点数量的计数器*/
private Node1<T> head;
public static int size;
/**初始化链表*/
public MyLinkedList() {
this.head = new Node1<T>(null);
}
/**增加结点*/
public void add(T value) {
/**申请新节点存储传入的值*/
Node1<T> newNode1 = new Node1<>(value);
/**申请临时指针指向头结点*/
Node1<T> temp = head;
/**如果首结点是空*/
if (head.next == null) {
head.next = newNode1;
size++;
return;
}
/**用temp遍历单链表,找到尾结点*/
while (temp.next != null) {
temp = temp.next;
}
/**将新结点挂载到尾结点,计数加1*/
temp.next = newNode1;
size++;
}
/**删除结点*/
public void remove(T value) {
/**申请新节点存储传入的值*/
Node1<T> newNode1 = new Node1<>(value);
/**申请临时指针指向头结点*/
Node1<T> temp = head;
/**找到与所要删除的结点值相同的上一个结点*/
while (!temp.next.equals(newNode1) && temp.next != null) {
temp = temp.next;
}
/**将要删除的结点的下一个结点挂到要删除的结点的上一个结点,计数减1*/
if (temp.next.equals(newNode1)) {
temp.next = temp.next.next;
size--;
System.out.println("删除成功!");
} else {
/**遍历结束,无此结点*/
System.out.println("无此结点!");
}
}
/**改变结点的值*/
public void replace(int index,T value){
/**申请新节点存储传入的值*/
Node1<T> newNode1 = new Node1<>(value);
/**申请临时指针指向头结点*/
Node1<T> temp = head;
/**申请临时计数变量,统计是第几个下标*/
int count = 0;
/**找到与要修改的下标的上一个结点跳出循环*/
while (count < index-1 && temp.next != null) {
temp = temp.next;
count++;
}
/**将要修改的结点的下一个结点挂到新结点上,并将新结点挂到要修改的结点的上一个结点,由GC回收原结点*/
if (count < index) {
newNode1.next = temp.next.next;
temp.next = newNode1;
System.out.println("修改成功!");
} else {
/**遍历结束,无此结点*/
System.out.println("无此结点!");
}
}
/**查找指定值的结点*/
public void find(T value) {
/**申请新节点存储传入的值*/
Node1<T> newNode1 = new Node1<>(value);
/**申请临时指针指向头结点*/
Node1<T> temp = head;
int count = 0;
/**找到要查找的值的上一个结点*/
while (!temp.next.equals(newNode1) && temp.next != null) {
temp = temp.next;
count++;
}
/**打印所查询结点的下标,为count+1*/
if (temp.next.equals(newNode1)) {
System.out.println("查找成功,该结点下标为:"+(count+1));
} else {
System.out.println("无此结点!");
}
}
/**遍历链表*/
public void show() {
Node1<T> temp = head;
System.out.println("该链表一共存储了:"+size+"个结点");
/**调用重写的toString方法,遍历输出所有结点*/
while (temp.next != null) {
System.out.println(temp.next.toString());
temp = temp.next;
}
}
}
(3)测试类:
/**
* @author Mr Bai`s Soda
*/
public class Main {
public static void main(String[] args) {
MyLinkedList<Integer> myLinkedList = new MyLinkedList<>();
myLinkedList.add(8);
myLinkedList.add(9);
myLinkedList.add(10);
myLinkedList.add(12);
myLinkedList.show();
System.out.println("-----------------------");
myLinkedList.remove(10);
myLinkedList.show();
System.out.println("-----------------------");
myLinkedList.replace(2,15);
myLinkedList.show();
myLinkedList.find(12);
}
}
2.二叉树
1)二叉树及其格式
二叉树是树的一种,每个节点最多可以有两个子树,即结点的度最大为2.
class Node{
Object data;
Node left;
Node right;
}
二叉树可分为斜树、满二叉树、完全二叉树等,在此不作详述。
2)二叉树的遍历
先序遍历:根左右
中序遍历:左根右
后序遍历:左右根
3)二叉树一些性质
(1)二叉树第 i 层上的结点数目最多为 2^(i-1) (i≥1)
(2)深度为 h 的二叉树至多有 2^h-1 个结点(h≥1)
(3)包含 n 个结点的二叉树的高度至少为 log2 (n+1)
(4)在任意一棵二叉树中,若终端结点的个数为 n0,度为 2 的结点数为 n2,则 n0=n2+1
4)简单代码示例
(1)节点
import java.util.Objects;
/**
* @author Mr Bai`s Soda
*/
public class Node2 {
/**结点值*/
int value;
/**结点的左右孩子指针*/
Node2 leftChild;
Node2 rightChild;
public Node2() {}
/**构造函数*/
public Node2(int value) {
this.value = value;
this.leftChild = null;
this.rightChild = null;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Node2)) {
return false;
}
Node2 node2 = (Node2) o;
return value == node2.value;
}
@Override
public int hashCode() {
return Objects.hash(value);
}
/**toShow显示结点值*/
public void toShow() {
if (this != null) {
System.out.println(value);
}
}
}
(2)节点操作类
/**
* @author Mr Bai`s Soda
*/
public class MyBinarySearchTree<T> {
/**定义二叉查找树的根结点和表示二叉树结点数量的计数器*/
private Node2 root;
public static int size;
/**初始化二叉树*/
public MyBinarySearchTree() {}
/**返回根结点*/
public Node2 getRoot() {
return root;
}
/**增加结点*/
public void add(int value) {
/**申请新节点存储传入的值*/
Node2 newNode2 = new Node2(value);
/**如果根结点是空*/
if (root == null) {
root = newNode2;
size++;
} else {
/**申请临时指针指向根结点,并定义一个双亲结点指针*/
Node2 temp = root;
Node2 parent = null;
while (true) {
/**值小于根结点,在左边插入*/
if (value < temp.value) {
parent = temp;
temp = temp.leftChild;
/**当其左孩子为空,把新结点挂到左孩子结点上*/
if (temp == null) {
parent.leftChild = newNode2;
size++;
break;
}
} else if (value > temp.value) {
/**值大于根结点,在右边插入*/
parent = temp;
temp = temp.rightChild;
/**当其右孩子为空,把新结点挂到右孩子结点上*/
if (temp == null) {
parent.rightChild = newNode2;
size++;
break;
}
} else {
/**新插入的结点值与已插入某个结点值相同*/
System.out.println("不能插入值相同的结点!");
return;
}
}
}
}
/**先序遍历*/
public void preOder(Node2 node2) {
if (node2 == null) {
return;
}
node2.toShow();
preOder(node2.leftChild);
preOder(node2.rightChild);
}
/**中序遍历*/
public void inOder(Node2 node2) {
if (node2 == null) {
return;
}
inOder(node2.leftChild);
node2.toShow();
inOder(node2.rightChild);
}
/**后序遍历*/
public void postOder(Node2 node2) {
if (node2 == null) {
return;
}
postOder(node2.leftChild);
postOder(node2.rightChild);
node2.toShow();
}
}
(3)测试类
/**
* @author Mr Bai`s Soda
*/
public class Main {
public static void main(String[] args) {
MyBinarySearchTree myBinarySearchTree = new MyBinarySearchTree();
myBinarySearchTree.add(15);
myBinarySearchTree.add(9);
myBinarySearchTree.add(21);
myBinarySearchTree.add(14);
myBinarySearchTree.add(18);
myBinarySearchTree.add(19);
myBinarySearchTree.add(13);
myBinarySearchTree.add(15);
System.out.println("该树共存储了:"+MyBinarySearchTree.size+"个结点");
System.out.println("先序遍历:");
myBinarySearchTree.preOder(myBinarySearchTree.getRoot());
System.out.println("中序遍历:");
myBinarySearchTree.inOder(myBinarySearchTree.getRoot());
System.out.println("后序遍历:");
myBinarySearchTree.postOder(myBinarySearchTree.getRoot());
}
}
3.常见的数据结构
1)栈
(1)栈的定义
栈又称堆栈,是限定仅在表尾进行插入和删除操作的线性表。能够插入和删除的一端称为栈顶,另一端称为栈底。
栈的入口、出口都是该栈的顶端位置。
压栈是存元素,弹栈是取元素。
(2)遵循原则
栈遵循先进后出的原则。
2)队列
(1)队列定义
队列是一种特殊的线性表,运算受到限制,一端插入,另一端删除,队尾进,队头删。
队列的入口出口各占一侧。
(2)遵循原则
队列遵循先进先出的原则。
3)数组
数组在此不作叙述。
4)链表
链表见博客开始部分
5)二叉树
见上。
二、Collection接口(重点)
1.定义
Collection接口是在整个java类集中保存单值的最大操作接口,里面每次操作的时候都只能保存一个对象的数据。此接口定义在java.util包中。
2.接口定义
public interface Collection<E> extends Iterable<E>
3.常用方法
public boolean add(E e); //向集合中插入一个元素
public Iterator<E> iterator(); //为Iterator接口实例化
public int size(); //求出集合中的元素个数
通常使用子接口List(允许重复)、Set(不允许重复)。
4.List接口
1)定义
public interface List<E> extends Collection<E>
2)常用方法
public E get(int index); //根据索引位置取出每一个元素
public ListIterator<E> listIterator(); //返回ListIterator接口的实例
public E remove(int intdex); //删除指定位置的内容,重载了母接口的remove方法,删除的时候把元素取出,方便使用后删除。
public void add(int index,E element); //修改指定位置的内容
public E set(int index,E element); //在指定位置处增加元素
List接口常用的实现类:ArrayList(95%)、Vector(4%)、LinkedList(1%)
3)ArrayList
(1)ArrayList是List接口的子类,定义:
public class ArrayList extends AbstractList implements List, RandomAccess, Cloneable, Serializable
可以使用add()方法添加元素,和remove()方法删除元素,根据指定位置取的内容的方法,只有List接口才有定义,其他的任何接口都没有任何的定义。
(2)示例
import java.util.ArrayList;
import java.util.List;
/**
* @author Mr Bai`s Soda
*/
public class MyArrayList {
public static void main(String[] args) {
List<String> myArrayList = new ArrayList<>();
myArrayList.add("this");
myArrayList.add("is");
myArrayList.add("myArrayList");
System.out.println(myArrayList);
System.out.println("------------------");
myArrayList.remove(2);
myArrayList.remove("this");
System.out.println(myArrayList.get(myArrayList.size()-1));
}
}
4)Vector
(1)Vector也属于List接口的子类,定义:
public class Vector extends AbstractList implements List, RandomAccess, Cloneable, Serializable
使用add()方法添加元素,和remove()方法删除元素,size()方法获取集合的大小。
(2)示例
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
/**
* @author Mr Bai`s Soda
*/
public class MyVector {
public static void main(String[] args) {
List<String> myVector = new Vector<>();
myVector.add("this");
myVector.add("is");
myVector.add("myVector");
System.out.println(myVector);
System.out.println("------------------");
myVector.remove(1);
myVector.remove("this");
System.out.println(myVector.get(myVector.size()-1));
}
}
5)Vector类和ArrayList类的区别
ArrayList性能较高,是采用了异步处理,支持Iterator、ListIterator输出
Vector性能较低,是采用了同步处理,除了支持Iterator、ListIterator输出,还支持Enumeration输出
5.Set接口
Set接口也是Collection的子接口但是接口里面的内容是不允许重复的。此接口没有List接口中定义的get(int index)方法,所以无法使用循环进行输出。
此接口中有两个常用的子类:HashSet、TreeSet
1)HashSet
里面的内容是无序存放的,可以进行循环输出,因为在Collection接口中定义了将集合变为对象数组进行输出(toArray())。
示例:
import java.util.HashSet;
import java.util.Set;
/**
* @author Mr Bai`s Soda
*/
public class MyHashSet {
public static void main(String[] args) {
Set<String> myHashSet = new HashSet<String>();
myHashSet.add("this");
myHashSet.add("is");
myHashSet.add("is");
myHashSet.add("myHashSet");
String [] strings = myHashSet.toArray(new String[]{});
for (String s: strings) {
System.out.println(s);
}
}
}
2)TreeSet
TreeSet是属于排序的子类,但是增加之后可以为用户进行排序功能的实现。
要想判断两个对象是否相等,第一种判断两个对象的编码是否一致,这个方法需要通过hashCode()完成,因为每个对象有唯一的编码,如果还需要进一步验证对象中的每个属性是否相等,需要通过equals()完成。所以此时需要覆写Object 类中的hashCode()方法,此方法表示一个唯一的编码,一般是通过公式计算出来的。
关于TreeSet的排序实现,如果是集合中对象是自定义的或者说其他系统定义的类没有实现Comparable接口,则不能实现TreeSet的排序,会报类型转(转向Comparable接口)错误。要添加到 TreeSet 集合中的对象的类型必须实现了 Comparable 接口。不过TreeSet的集合因为借用了Comparable接口,同时可以去除重复值,而HashSet虽然是Set接口子类,但是对于没有复写Object的equals和hashCode方法的对象,即使加入了HashSet集合中也是不能去掉重复值的。
示例:
import java.util.Set;
import java.util.TreeSet;
/**
* @author Mr Bai`s Soda
*/
public class MyTreeSet {
public static void main(String[] args) {
Set<Integer> myTreeSet = new TreeSet<Integer>();
myTreeSet.add(5);
myTreeSet.add(10);
myTreeSet.add(2);
myTreeSet.add(2);
System.out.println(myTreeSet);
}
}
6.集合的输出
Iterator迭代输出(90%)、ListIterator(5%)、Enumeration(1%)、foreach(4%)
1)Iterator
Iterator属于迭代输出,基本的操作原理:不断的判断是否有下一个元素,有的话,则直接输出。
接口定义:public interface Iterator<E>
要想使用此接口,则必须使用 Collection 接口,在Collection接口中有iterator()方法,可以用于Iterator接口进行实例化操作。
hasNext()是否有下一个元素
next()取出内容
remove()删除当前内容
Iterator中的操作指针是在第一条元素之上,当调用next()方法的时候,获取当前指针指向的值并向下移动,使用hasNext()可以检查序列中是否还有元素。
注意:在进行迭代输出的时候如果要想删除当前元素,则只能使用Iterator接口中的remove()方法,而不能使用集合中的remove()方法。否则将出现未知的错误。
示例:
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* @author Mr Bai`s Soda
*/
public class MyIterator {
public static void main(String[] args) {
List<String> myArrayList = new ArrayList<>();
myArrayList.add("this");
myArrayList.add("is");
myArrayList.add("myArrayList");
Iterator<String> iterator = myArrayList.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
2)foreach
for (类型 变量名 : 集合名或数组名) {
System.out.println(变量名) ;
}
三、Map接口
1.Map接口及其定义
Map集合用于操作一对对象,里面的所有内容都按照 key–>value的形式保存,也称为二元偶对象。
接口定义:public interface Map<K,V>
Set<Map.Entry<K,V>> entrySet();//将Map接口变为Set集合
V get(Object key);//根据key找到其对应的value
Set<K> keySet();//将全部的key变为Set集合
Collection<V> values();//将全部的value变为Collection集合
V put(K key,V value);//向集合中增加内容
Map本身是一个接口,所以一般会使用以下的几个子类:HashMap、TreeMap、Hashtable
用put()和get()方法增加和取出元素
2.HashMap
1)定义
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable
可以用:
Set<Integer> set = map.keySet(); // 得到全部的key
Collection<String> value = map.values(); // 得到全部的value
2)示例
import java.util.*;
/**
* @author Mr Bai`s Soda
*/
public class MyHashMap {
public static void main(String[] args) {
Map<Integer,String> myHashMap = new HashMap<Integer,String>();
myHashMap.put(1,"this");
myHashMap.put(2,"is");
myHashMap.put(2,"isn't");
myHashMap.put(3,"myHashMap");
String string = myHashMap.get(2);
System.out.println(string);
System.out.println("-------------------");
Set<Integer> key = myHashMap.keySet();
Collection<String> value = myHashMap.values();
Iterator<Integer> iterator1 = key.iterator();
Iterator<String> iterator2 = value.iterator();
while (iterator1.hasNext()) {
System.out.print(iterator1.next()+"、");
}
System.out.println();
while (iterator2.hasNext()) {
System.out.print(iterator2.next()+"、");
}
}
}
3.Hashtable
使用方法如:
Map<String, Integer> numbers = new Hashtable<String, Integer>();
Hashtable中不能向集合中插入null值。
示例:
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
/**
* @author Mr Bai`s Soda
*/
public class MyHashTable {
public static void main(String[] args) {
Map<Integer,String> myHashtable = new Hashtable<>();
myHashtable.put(1,"this");
myHashtable.put(2,"is");
myHashtable.put(3,"myHashtable");
String string = myHashtable.get(3);
if (string != null) {
System.out.println(string);
}
}
}
4.HashMap与Hashtable的区别
HashMap:异步处理,性能较高,允许设置为null
Hastabl:同步处理,性能较低,不允许设置null,否则将出现空指向异常
5.TreeMap
TreeMap子类是允许key进行排序的操作子类,其本身在操作的时候将按照key进行排序,另外,key中的内容可以为任意的对象,但是要求对象所在的类必须实现Comparable接口。
示例:
import java.util.*;
/**
* @author Mr Bai`s Soda
*/
public class MyTreeMap {
public static void main(String[] args) {
Map<Integer,String> myTreeMap = new TreeMap<Integer,String>();
myTreeMap.put(1,"this");
myTreeMap.put(2,"is");
myTreeMap.put(3,"myTreeMap");
Set<Integer> set = myTreeMap.keySet();
Iterator<Integer> iterator = set.iterator();
while (iterator.hasNext()) {
int i = iterator.next();
System.out.println(i+"-->"+myTreeMap.get(i));
}
}
}
6.Map的输出
Map接口非要使用Iterator进行输出的话,则可以按照如下的步骤进行:
1、使用Map接口中的entrySet()方法将Map接口的全部内容变为Set集合
2、可以使用 Set接口中定义的iterator()方法为Iterator接口进行实例化
3、之后使用Iterator接口进行迭代输出,每一次的迭代都可以取得一个Map.Entry的实例
4、通过Map.Entry进行key和value的分离
K getKey()//得到 key
V getValue()//得到 value
例如:
Set<Map.Entry<String, String>> set = map.entrySet();// 变为Set实例
Iterator<Map.Entry<String, String>> iter = set.iterator();
while (iter.hasNext()) {
Map.Entry<String, String> me = iter.next();
System.out.println(me.getKey() + " --> " + me.getValue());
}
四、Collections类
Collections实际上是一个集合的操作类
1.定义
public class Collections extends Object
List<String> all = Collections.emptyList() ;// 空的集合
使用Collections类返回的空的集合对象,本身是不支持任何的修改操作的,因为所有的方法都没实现。
List<String> all = new ArrayList<String>();
Collections.addAll(all, "A", "B", "C");// 向集合增加元素
2.在 java 的集合中,判断两个对象是否相等的规则
1)判断两个对象的 hashCode 是否相等
如果不相等,认为两个对象也不相等,完毕
如果相等,转入 2
2)判断两个对象用 equals 运算是否相等
如果不相等,认为两个对象也不相等
如果相等,认为两个对象相等(equals()是判断两个对象是否相等的关键)
五、总结
1、 类集就是一个动态的对象数组,可以向集合中加入任意多的内容。
2、 List接口中是允许有重复元素的,Set接口中是不允许有重复元素。
3、 所有的重复元素依靠hashCode()和equals进行区分
4、 List接口的常用子类:ArrayList、Vector
5、 Set接口的常用子类:HashSet、TreeSet
6、 TreeSet是可以排序,一个类的对象依靠Comparable接口排序
7、 Map接口中允许存放一对内容,key–>value
8、 Map接口的子类:HashMap、Hashtable、TreeMap
9、 Map使用Iterator 输出的详细步骤