常见的数据结构
数据存储的常用结构有:栈、队列、数组、链表和二叉树
栈: 又称堆栈(stack),是限定只能在表底插入和删除的线性表。把允许插入和删除的一段成为栈顶,另一端为栈底。
先进后出
这里两个名词需要注意:
压栈:存元素。
弹栈:取元素
队列: queue简称队,队列是一种特殊的线性表,是运算受到限制的一种线性表,只允许在表的
一端进行插入,而在另一端进行删除元素的线性表。
先进先出
数组
链表
链表是由一组不必相连(不必相连:可以连续也可以不连续)的内
存结构(节点),按特定的顺序链接在一起的抽象数据类型。
链表常用的有 3 类: 单链表、双向链表、循环链表。
数组的优点:
存取速度快
数组的缺点:
事先必须知道数组的长度
插入删除元素很慢
空间通常是有限制的
需要大块连续的内存块
插入删除元素的效率很低
链表是离散存储线性结构
n 个节点离散分配,彼此通过指针相连,每个节点只有一个前驱节点,每个节点只有一
个后续节点,首节点没有前驱节点,尾节点没有后续节点。
链表优点:
空间没有限制
插入删除元素很快
链表缺点:
存取速度很慢
类集
Java对于数据结构成熟的实现
集合和数组的区别
- 数组的长度是固定的,集合的长度是可变的。
- 数组中存储的是同一类型的数据,可以存储基本数据类型。集合存储的是对象,而且对象的类型可以不一致。
类集中最大的数据接口 Iteration、Collection、Map
这些接口在java.util包中
Collection
Collection 接口是在整个 Java 类集中保存单值的最大操作父接口,里面每次操作的时候都只能保存一个对象的数据。
此接口定义在 java.util 包中。
此接口定义如下:
public interface Collection extends Iterable
List接口
在整个集合中 List 是 Collection 的子接口,里面的所有内容都是允许重复的。
List 子接口的定义:
public interface List extends Collection
ArrayList
线程不安全
使用的是数组结构,对于增加删除慢,查找快。
构造方法:
ArrayList() 构造一个初始容量为10的空列表
ArrayList(int initialCapcity) 构造具有指定出事容量的空列表
当初始就要存入大量元素时,使用一参构造方法
Vector
同步 线程安全 效率低
通过构造方法可以指定每次扩容增量
LinkedList
线程不安全
使用的是双向链表结构,增删快,搜索慢
压栈
push();
弹栈:
pop();
Set
不包含重复元素的集合。 更正式地说,集合不包含元素对e1和e2 ,使得e1.equals(e2)和最多一个null元素。
部分set子类属于无序存储,由于属性更改可能会导致错误发生,在修改值时需小心谨慎
没有get()方法获取集合内数组
1.用toArray()将其转化为数组
2.使用iterator迭代的方式
HashSet
Set接口下的子类.通过哈希表实现散列存放
import java.util.HashSet;
import java.util.Iterator;
public class Demo1 {
public static void main(String[] args) {
HashSet<String> s1 = new HashSet<>();
s1.add("少年不知愁滋味");
s1.add("你胖你丑你先睡");
s1.add("蓬门今始为君开");
s1.add("睡腻麻痹起来嗨");
s1.add("你胖你丑你先睡");
Iterator <String> iterator = s1.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
System.out.println("================================");
for(String s:s1){
System.out.println(s);
}
}
}
TreeSet
存储有序
此类的iterator方法返回的迭代器是快速失败的 :如果在创建迭代器之后的任何时间修改集合,除了通过迭代器自己的remove方法之外,迭代器将抛出ConcurrentModificationException 。 因此,在并发修改的情况下,迭代器快速而干净地失败,而不是在未来的未确定时间冒任意,非确定性行为的风险。
安全失败:通过拷贝一份集合,对拷贝集合进行遍历,从而避免因为集合内容的增加、删除、修改所导致的问题。
不做描述则视为安全失败
向集合加入多个类时,为了规定存储顺序,需要类来实现Comparable接口,并实现接口下的方法(compareTo())比较大小来决定存储顺序。
import java.util.TreeSet;
public class Demo2 {
public static void main(String[] args) {
TreeSet<Person> treeset = new TreeSet<>();
Person p1 = new Person("张三",19);
Person p2 = new Person("李四",25);
treeset.add(p1);
treeset.add(p2);
for(Person p :treeset){
System.out.println(p);
}
}
static class Person implements Comparable<Person> {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public int compareTo(Person o) {
if(this.age>o.age){
return 1;
}
if(this.age==o.age){
return 0;
}
return -1;
}
}
}
运行结果:
Map
Map是双值存储的最大接口,存储的是一对键值,将键映射到对象,键唯一不可重复。
HashMap
HashMap根据键的hashCode值存储数据,可以通过它的键定位到它的值。
线程不安全,效率高
哈希桶初始量 16
默认散列因子/负载因子 0.75 (散列因子越大,存储效率越高,查询更难;散列因子越小,存储效率越低,查询更容易)
当数据量到达哈希桶容量*散列因子时,对桶进行扩容,扩容量为原来的2倍(16–>32)
对象数组+链表
当hashMap中某一个下标数据量到达8时,从链表转化为红黑二叉树
减少到6时,会从红黑树转化为链表
对象作为键存入哈希表中,不要更改对象内属性导致哈希值改变
源码:
1.通过hash(key)传入key获得哈希值
2.计算下标:
1. 首先判断容量 如果长度为空或者容量为0那么进行resize()
2. n为哈希桶容量 n-1 & 哈希值 取余 length
3. 根据下标找到集合table内的位置,查看table内该位置是否有数据,如果没有则插入一个链表新节点。
4. 如果该地址有一个节点,那么会对连者的哈希值和键值进行比较。如果相同
5. 如果该节点有重复,但不是链表的首位,判断链表是否为treenode 长度为8。.则按照红黑树的方式存值
6. 如果该节点有重复,但不是链表守卫,链表长度为1-7.
7. 一旦发现重复,e就会被赋值,然后覆盖。
HashTable
HashTable继承自Dictionary类,线程是安全的,同一时刻只能有一个线程HashTable
线程安全,效率低
HashTable 不允许有 null 键和 null 值,否则会抛出 NullPointerException。
ConcurrentHashMap
采用分段锁机制,保证线程安全,效率又比较高
LinkedHashMap
线程不安全
在HashMap的基础上 通过一个额外的双向链表 保证了数据存储的有序性
TreeMap
线程不安全
实现 NavigableMap 接口让 TreeMap 有了对集合内元素的搜索的能力。
实现SortMap接口让 TreeMap 有了对集合中的元素根据键排序的能力。默认是按 key 的升序排序,不过我们也可以指定排序的比较器。示例代码如下:
相比于HashMap来说 TreeMap 主要多了对集合中的元素根据键排序的能力以及对集合内元素的搜索的能力。