一、数据存储的常用结构
栈、队列、数组、链表和红黑树。
栈:stack,又称堆栈,它是运算受限的线性表,其限制是仅允许在标的一端进行插入和删除操作,不允许在其他任何位置进行添加、查找、删除等操作。
对元素的存取有以下的特点:先进后出,栈的入口、出口都是栈的顶端位置。
压栈:就是存元素。即,把元素存储到栈的顶端位置,栈中已有元素依次向栈底方向移动一个位置。
弹栈:就是取元素。即,把栈的顶端位置元素取出,栈中已有元素依次向栈顶方向移动一个位置。
队列:queue,简称队,它同堆栈一样,也是一种运算受限的线性表,其限制是仅允许在表的一端进行插入,而在表的另一端进行删除。
简单的说,采用该结构的集合,对元素的存取有如下的特点:先进先出。
数组:Array,是有序的元素序列,数组是在内存中开辟一段连续的空间,并在此空间存放元素。
对元素的存取有如下的特点:
- 查找元素快:通过索引,可以快速访问指定位置的元素
- 增删元素慢
- 指定索引位置增加元素:需要创建一个新数组,将指定新元素存储在指定索引位置,再把原数组元素根据索引,复制到新数组对应索引的位置。
- 指定索引位置删除元素:需要创建一个新数组,把原数组元素根据索引,复制到新数组对应索引的位置,原数组中指定索引位置元素不复制到新数组中。
链表:linked list,由一系列结点node(链表中每一个元素称为结点)组成,结点可以在运行时i动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。我们常说的链表结构有单向链表与双向链表,那么这里给大家介绍的是单向链表。
简单的说,采用该结构的集合,对元素的存取有如下的特点:
-
多个结点之间,通过地址进行连接。
-
查找元素慢:想查找某个元素,需要通过连接的节点,依次向后查找指定元素
-
增删元素快:
-
增加元素:只需要修改连接下个元素的地址即可。
-
删除元素:只需要修改连接下个元素的地址即可。
-
红黑树
二叉树是每个节点最多有两个子树的树结构。顶上的叫根结点,两边被称作“左子树”和“右子树”。
红黑树本身就是一颗二叉查找树,将节点插入后,该树仍然是一颗二叉查找树。也就意味着,树的键值仍然是有序的。
红黑树的约束:
- 节点可以是红色的或者黑色的
- 根节点是黑色的
- 叶子节点(特指空节点)是黑色的
- 每个红色节点的子节点都是黑色的
- 任何一个节点到其每一个叶子节点的所有路径上黑色节点数相同
红黑树的特点:
速度特别快,趋近平衡树,查找叶子元素最少和最多次数不多于二倍。
二、 List集合
List接口特点:
- 它是一个元素存取有序的集合。例如,存元素的顺序是a、b、c。那么集合中,元素的存储就是按照a、b、c的顺序完成的)。
- 它是一个带有索引的集合,通过索引就可以精确的操作集合中的元素(与数组的索引是一个道理)。
- 集合中可以有重复的元素,通过元素的equals方法,来比较是否为重复的元素。
List接口的子类java.util.ArrayList类,该类中的方法都是来自List中定义。
list作为Collection集合的子接口,不但继承了Collection接口中的全部方法,而且还增加了一些根据元素索引来操作集合的特有方法,如下:
public void add(int index, E element)
: 将指定的元素,添加到该集合中的指定位置上。public E get(int index)
:返回集合中指定位置的元素。public E remove(int index)
: 移除列表中指定位置的元素, 返回的是被移除的元素。public E set(int index, E element)
:用指定元素替换集合中指定位置的元素,返回值的更新前的元素。
练习一
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class test1 {
public static void main(String[] args) {
List<String> l1 = new ArrayList<>();
//往尾部添加指定元素
l1.add("你好");
l1.add("我好");
l1.add("大家好");
System.out.println(l1);
//add(int incex,String s)往指定位置添加
l1.add(1,"我不好");
System.out.println(l1);
//往指定位置删除
l1.remove(2);
System.out.println(l1);
//set修改指定位置的元素
l1.set(1,"我很好");
System.out.println(l1);
//get获取指定位置的元素
String s = l1.get(1);
System.out.println(s);
//三种遍历。
System.out.println("for增强");
for (String s1 : l1) {
System.out.println(s1);
}
System.out.println("普通for循环");
for (int i = 0; i < l1.size(); i++) {
System.out.println(l1.get(i));
}
System.out.println("Iterator遍历");
Iterator<String> it = l1.iterator();
while(it.hasNext()){
String s1 = it.next();
System.out.print(s1);
}
}
}
1. ArrayList集合
java.util.ArrayList
集合数据存储的结构是数组结构。元素增删慢,查找快,由于日常开发中使用最多的功能为查询数据、遍历数据,所以ArrayList
是最常用的集合。
list接口大小可变数组的实现。底层是一个数组。此实现不是同步的,也就是说这是多线程。效率高。
2.LinkedList
java.util.LinkedList 集合 implements List接口
LinkedList集合的特点:
1.底层是一个链表结构:查询慢,增删快。
2.里边包含了大量操作首尾元素的方法。
LinkedList是一个双向链表
实际开发中对一个集合元素的添加与删除经常涉及到首尾操作,而LinkedList提供了大量首尾操作的方法。这些方法我们作为了解即可:
public void addFirst(E e)
:将指定元素插入此列表的开头。public void addLast(E e)
:将指定元素添加到此列表的结尾。public E getFirst()
:返回此列表的第一个元素。public E getLast()
:返回此列表的最后一个元素。public E removeFirst()
:移除并返回此列表的第一个元素。public E removeLast()
:移除并返回此列表的最后一个元素。public E pop()
:从此列表所表示的堆栈处弹出一个元素。public void push(E e)
:将元素推入此列表所表示的堆栈。public boolean isEmpty()
:如果列表不包含元素,则返回true。
在开发时,LinkedList集合也可以作为堆栈,队列的结构使用。(了解即可)
练习
import java.util.LinkedList;
/*
* `public void addFirst(E e)`:将指定元素插入此列表的开头。
* `public void addLast(E e)`:将指定元素添加到此列表的结尾。
* `public E getFirst()`:返回此列表的第一个元素。
* `public E getLast()`:返回此列表的最后一个元素。
* `public E removeFirst()`:移除并返回此列表的第一个元素。
* `public E removeLast()`:移除并返回此列表的最后一个元素。
* `public E pop()`:从此列表所表示的堆栈处弹出一个元素。
* `public void push(E e)`:将元素推入此列表所表示的堆栈。
* `public boolean isEmpty()`:如果列表不包含元素,则返回true。
*/
public class test1 {
public static void main(String[] args) {
LinkedList<String > L1 = new LinkedList<>();
//将元素插入列表的开头
L1.addFirst("AA1");
L1.addFirst("AB1");
L1.addFirst("AC1");
System.out.println(L1);
//获取元素
System.out.println(L1.getFirst());
System.out.println(L1.getLast());
//删除元素
System.out.println(L1.removeFirst());
System.out.println(L1.removeLast());
System.out.println(L1);
//添加元素
L1.add("ABC");
System.out.println(L1);
//弹出栈顶元素
System.out.println("弹出栈顶元素");
/* while(!L1.isEmpty()){
System.out.println(L1.pop());//弹出栈顶元素
}*/
//压入栈
L1.push("ccc");
System.out.println(L1);
}
}
三、Set接口
java.util.Set接口 extends Collection接口
Set接口的特点:
1.不允许存储重复的元素。
2.没有索引,没有带索引的方法,不能用普通的for循环遍历。
java.util.HashSet集合 implements Set接口
HashSet特点:
1.不允许存储重复的元素,
2.没有索引,没有带索引的方法,也不能使用普通的for循环遍历。
3.是一个无序集合,存储和取出元素的顺序可能不一致。
4.底层是一个哈希表结构(查询速度非常快)
Set
集合有多个子类,这里介绍其中的java.util.HashSet
、java.util.LinkedHashSet
这两个集合。Set集合取出元素的方式可以采用:迭代器、增强for。
java.util.HashSet
底层的实现其实是一个java.util.HashMap
支持。
HashSet是 根据对象的哈希值来确定元素在 集合中的存储位置,因此具有良 好的存取和查找性能。保证元素唯一性的方式依赖于:
hashCode与
equals`方法。
哈希表
在JDK1.8之前,哈希表底层采用数组+链表实现,即使用链表处理冲突,同一hash值的链表都存储在一个链表里。但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率较低。而JDK1.8中,哈希表存储采用数组+链表+红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。
简单的来说,哈希表是由数组+链表+红黑树(JDK1.8增加了红黑树部分)实现的。
如果往集合中存放自定义的对象,那么保证其唯一,就必须复写hashCode和equals方法建立属于当前对象的比较方式。
1.HashSet存储自定义类型元素
给HashSet中存放自定义类型元素时,需要重写对象中的hashCode、toString()方法和equals方法,建立自己的比较方式,才能保证HashSet集合中的对象唯一。
练习
public class Student {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public 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;
}
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Student student = (Student) o;
return age == student.age &&
Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
练习
public class HashSetDemo {
public static void main(String[] args) {
//创建集合对象 该集合中存储 Student类型对象
HashSet<Student> stuSet = new HashSet<Student>();
//存储
Student stu = new Student("于谦", 43);
stuSet.add(stu);
stuSet.add(new Student("郭德纲", 44));
stuSet.add(new Student("于谦", 43));
stuSet.add(new Student("郭麒麟", 23));
stuSet.add(stu);
for (Student stu2 : stuSet) {
System.out.println(stu2);
}
}
}
2. LinkedHashSet
我们知道HashSet保证元素唯一,可是元素存放进去是没有顺序的,那么我们要保证有序,怎么办呢?
在HashSet下面有一个子类java.util.LinkedHashSet
,它是链表和哈希表组合的一个数据存储结构。
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;
public class test1 {
public static void main(String[] args) {
HashSet<String> H1 = new HashSet<>();
H1.add("bbb");
H1.add("aaa");
H1.add("abc");
H1.add("bbc");
System.out.println(H1);//无序的
LinkedHashSet<String> set = new LinkedHashSet<String>();
set.add("bbb");
set.add("aaa");
set.add("abc");
set.add("bbc");
System.out.println(set);//有序,存什么数据就输出什么数据
Iterator<String> it = set.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
}
}
四、可变参数
在JDK1.5之后,如果定义一个方法需要接受多个参数,并且多个参数类型一致,可以对其简化成如下格式:
修饰符 返回值类型 方法名(参数类型... 形参名){ }
…代表可以传递多个参数。
修饰符 返回值类型 方法名(参数类型[] 形参名){ }
只是后面这种定义,在调用时必须传递数组,而前者可以直接传递数据即可。
JDK1.5以后。出现了简化操作。… 用在参数上,称之为可变参数。
同样是代表数组,但是在调用这个带有可变参数的方法时,不用创建数组(这就是简单之处),直接将数组中的元素作为实际参数进行传递,其实编译成的class文件,将这些元素先封装到一个数组中,在进行传递。这些动作都在编译.class文件时,自动完成了。
可变参数底层是一个数组,如
pubic static int add (int …arr){}
pubic static int add()
pubic static int method(String b,double c,int d ,int …a){}
可变参数得注意事项
- 一个方法的参数列表,只能有一个可变参数
- 如果方法的参数有多个,那么可变参数 必须写在参数列表的末尾。
可变参数终极写法
public static void method (Object…obj){ }
练习
public static int add(int …arr){
System.out.println(arr);
System.out.println(arr.length);
return 0;
}
练习
public static int add(int...arr){
int sum =0;
for(itn I :arr){
sum = i+sum;
}
return sum;
}
五、Collections集合工具类
1. 常用功能
java.utils.Collections
是集合工具类,用来对集合进行操作。部分方法如下:
public static <T> boolean addAll(Collection<T> c, T... elements)
:往集合中添加一些元素。public static void shuffle(List<?> list) 打乱顺序
:打乱集合顺序。public static <T> void sort(List<T> list)
:将集合中元素按照默认规则排序。public static <T> void sort(List<T> list,Comparator<? super T> )
:将集合中元素按照指定规则排序。
2.Comparator比较器
排序,简单的说就是两个对象之间比较大小,那么在JAVA中提供了两种比较实现的方式,一种是比较死板的采用java.lang.Comparable
接口去实现,一种是灵活的当我需要做排序的时候在去选择的java.util.Comparator
接口完成。
那么采用的public static <T> void sort(List<T> list)
这个方法完成的排序,实际上要求了被排序的类型需要实现Comparable接口完成比较的功能,在String类型上如下:
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {}
String类实现了这个接口,并完成了比较规则的定义,但是这样就把这种规则写死了,那比如我想要字符串按照第一个字符降序排列,那么这样就要修改String的源代码,这是不可能的了,那么这个时候我们可以使用
public static <T> void sort(List<T> list,Comparator<? super T> )
方法灵活的完成,这个里面就涉及到了Comparator这个接口,位于位于java.util包下,排序是comparator能实现的功能之一,该接口代表一个比较器,比较器具有可比性!顾名思义就是做排序的,通俗地讲需要比较两个对象谁排在前谁排在后,那么比较的方法就是:
-
public int compare(String o1, String o2)
:比较其两个参数的顺序。两个对象比较的结果有三种:大于,等于,小于。
如果要按照升序排序,
则o1 小于o2,返回(负数),相等返回0,01大于02返回(正数)
如果要按照降序排序
则o1 小于o2,返回(正数),相等返回0,01大于02返回(负数)
简述Comparable和Comparator两个接口的区别。
Comparable:强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序,类的compareTo方法被称为它的自然比较方法。只能在类中实现compareTo()一次,**不能经常修改类的代码实现自己想要的排序。**实现此接口的对象列表(和数组)可以通过Collections.sort(和Arrays.sort)进行自动排序,对象可以用作有序映射中的键或有序集合中的元素,无需指定比较器。自己(this)和别人(参数)比较,自己需要实现Comparable接口,重写比较规则compareTo方法
Comparator 强行对某个对象进行整体排序。可以将Comparator 传递给sort方法(如Collections.sort或 Arrays.sort),从而允许在排序顺序上实现精确控制。还可以使用Comparator来控制某些数据结构(如有序set或有序映射)的顺序,或者为那些没有自然顺序的对象collection提供排序。相当于找一个第三方的裁判,比较两个.数据。
练习
/*
- `public static <T> boolean addAll(Collection<T> c, T... elements) `:往集合中添加一些元素。
- `public static void shuffle(List<?> list) 打乱顺序`:打乱集合顺序。
- `public static <T> void sort(List<T> list)`:将集合中元素按照默认规则排序。
- `public static <T> void sort(List<T> list,Comparator<? super T> )`:将集合中元素按照指定规则 排序。
*/
public class test {
public static void main(String[] args) {
//往集合中添加多个元素
ArrayList<String> str = new ArrayList<>();
/*str.add("小");
str.add("明");
str.add("和");
str.add("小");
str.add("花");*/
Collections.addAll(str,"a","b","c","d","e");
System.out.println(str);
Collections.shuffle(str);
//打乱集合中元素的顺序
System.out.println(str);
//排序方法 按照第一个单词的降序
Collections.sort(str, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o2.charAt(0) - o1.charAt(0);
}
});
System.out.println(str);
/* //按默认的规则排序
Collections.sort(str);
System.out.println(str);*/
}
}