常见的数据结构
数组
数组是多个相同类型数据按一定顺序排列的集合。
结构特点:
- 数组是有序排列的。
- 数组属于引用数据类型的变量。数组的元素既可以是基本数据类型,也可以是引用数据类型。
- 创建数组对象会在内存中开辟一整块连续的空间。
- 数组的长度一旦确定,就不能修改。
功能特点:可以根据下标进行随机访问,查找快但增删慢
链表
链表是物理存储单元上非连续的、非顺序的存储结构。
链表由一系列数据结点构成,每个数据结点包括数据域和指针域两部分。其中,指针域保存了数据结构中下一个元素存放的地址。链表结构中数据元素的逻辑顺序是通过链表中的指针链接次序来实现的。
特点:增删快,查找慢
栈
栈是一种特殊的线性表,它只能在一个表的一个固定端进行数据结点的插入和删除操作。
栈按照后进先出的原则来存储数据,也就是说,先插入的数据将被压入栈底,最后插入的数据在栈顶,读出数据时,从栈顶开始逐个读出。栈在汇编语言程序中,经常用于重要数据的现场保护。栈中没有数据时,称为空栈。
队列
队列只允许在表的一端进行插入操作,而在另一端进行删除操作。也就是类似于常见的排队。
一般来说,进行插入操作的一端称为队尾,进行删除操作的一端称为队头。队列中没有元素时,称为空队列。
树
树是典型的非线性结构,是n(n>=0)个节点的有限集。n=0时称为空树。n>0时,有限集的元素构成一个具有层次感的数据结构。
区别于线性表一对一的元素关系,树中的节点是一对多的关系。树具有以下特点:
- n>0时,根节点是唯一的,不可能存在多个根节点。
- 每个节点有零个至多个子节点;除了根节点外,每个节点有且仅有一个父节点。根节点没有父节点。
堆
堆是一种特殊的树形数据结构,一般讨论的堆都是二叉堆。堆的特点是根结点的值是所有结点中最小的或者最大的,并且根结点的两个子树也是一个堆结构。
哈希
哈希表(Hash Table)是一种特殊的数据结构,它最大的特点就是可以快速实现查找、插入和删除。
哈希表(Hash Table):也叫散列表,是根据关键码值(key-value)而直接进行访问的数据结构,也就是我们常用到的map。
哈希函数:也称为是散列函数,是Hash表的映射函数,它可以把任意长度的输入变换成固定长度的输出,该输出就是哈希值。哈希函数能使对一个数据序列的访问过程变得更加迅速有效,通过哈希函数,数据元素能够被很快的进行定位。
图
在图结构中,数据结点一般称为顶点,而边是顶点的有序偶对。如果两个顶点之间存在一条边,那么就表示这两个顶点具有相邻关系。
和线性表,树的差异:
- 线性表中我们把数据元素叫元素,树中将数据元素叫结点,在图中数据元素,我们则称之为顶点(Vertex)。
- 线性表可以没有元素,称为空表;树中可以没有节点,称为空树;但是,在图中不允许没有顶点(有穷非空性)。
- 线性表中的各元素是线性关系,树中的各元素是层次关系,而图中各顶点的关系是用边来表示(边集可以为空)。
小结
一般来说,数组、栈、队列、链表、哈希表这几个数据结构是用得比较多的,在具体的项目开发中,一般使用的是Java集合框架中的各种集合接口,如List 接口、set接口以及map接口等等。
Java集合
集合、数组都是对多个数据(对象)进行存储操作的结构,简称Java容器。
- 数组的缺点
初始化数组时要指定长度 , 且长度不可修改
数组采用连续的地址存储 , 添加和删除效率低下
数组无法直接保存映射关系
数组提供的方法有限
- Java集合就像一种容器,可以动态地把多个对象的引用放入容器中
- 集合的元素必须是对象类型,不能是基本数据类型,如果是基本数据类型,此时使用的是包装类((自动装箱/拆箱)
如 : coll.add(12); == coll.add(Integer.valueOf(12));
容器分类
容器主要包括Collection 和Map两种,Collection存储着对象的集合,而Map存储着键值对的映射表。
单列集合框架概述
/----Collection接口 : 单列集合 ; 用来存储一个一个的对象
/----List接口 : 元素 有序 可重复; --->"动态数组"
/----ArrayList , LinkedList , Vector
/----Set接口 : 元素 无序且不可重复; --->高中"集合"概念
/----HashSet , LinkedHashSet , TreeSet
Collection接口常用方法
1、添加
add(Object obj)
addAll(Collection coll)
2、获取有效元素的个数
int size()
3、清空集合
void clear()
4、是否是空集合
boolean isEmpty()
5、是否包含某个元素
boolean contains(Object obj):是通过元素的equals方法来判断是否是同一个对象
boolean containsAll(Collection c):也是调用元素的equals方法来比较的。拿两个集合的元素挨个比较。
6、删除
boolean remove(Object obj) :通过元素的equals方法判断是否是要删除的那个元素。只会删除找到的第一个元素
boolean removeAll(Collection coll):取当前集合的差集
7、取两个集合的交集
boolean retainAll(Collection c):把交集的结果存在当前集合中,不影响c
8、集合是否相等
boolean equals(Object obj)
9、转成对象数组
Object[] toArray()
10、获取集合对象的哈希值
hashCode()
11、遍历
iterator():返回迭代器对象,用于集合
Collection集合与数组间的转换:
//集合 --->数组:toArray()
Object[] arr = coll.toArray();
for(int i = 0;i < arr.length;i++){
System.out.println(arr[i]);
}
//拓展:数组 --->集合:调用Arrays类的静态方法asList(T ... t)
List<String> list = Arrays.asList(new String[]{"AA", "BB", "CC"});//长度确定的List数组, 不属
于List接口的任何实现类
System.out.println(list);
List arr1 = Arrays.asList(new int[]{123, 456});
System.out.println(arr1.size());//1
List arr2 = Arrays.asList(new Integer[]{123, 456});
System.out.println(arr2.size());//2
迭代器Iterator 和 foreach循环
Iterator对象称为迭代器(设计模式的一种),主要用于遍历 Collection 集合中的元素。
遍历的代码实现:
//每次调用coll.iterator() 都会生成一个新的迭代器对象
//相对于定义了一个指针指向集合首元素的上方
Iterator iterator = coll.iterator();
//hasNext():判断是否还下一个元素
while(iterator.hasNext()){
//next():①指针下移 ②将下移以后集合位置上的元素返回
System.out.println(iterator.next());
foreach 循环 : 可用于 遍历 集合 和数组 (内部仍然调用了迭代器。)
@Test
public void test1(){
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(new Person("Jerry",20));
coll.add(new String("Tom"));
coll.add(false);
//for(集合元素的类型 局部变量 : 集合对象)
for(Object obj : coll){
System.out.println(obj);
}
}
遍历数组举例:
@Test
public void test2(){
int[] arr = new int[]{1,2,3,4,5,6};
//for(数组元素的类型 局部变量 : 数组对象)
for(int i : arr){
System.out.println(i);
}
}
说明:
内部仍然调用了迭代器。
List接口
- 数据特点 : 有序 , 可重复
- 主要实现类
/----List接口 : 元素 有序 可重复; --->"动态数组"
/----ArrayList :作为List接口的主要实现类;线程不安全的,效率高;底层使用一个Object[]进行存储
/----LinkedList :适用于频繁进行插入,删除操作的集合;底层使用双向链表实现.
/----Vector : 古老实现类,jdk1.0时就有了;线程安全的,效率低.底层也使用Object[]进行存储
- 常用方法(记住): 相比较与Collection接口, List 是有序的, 所以增加了有关索引的方法
增: add(Object obj)
删: remove(int index) / remove(Object obj)
改: set(int index , Object obj)
查: get(int index)
插入: add(int index , Object obj)
长度: size()
遍历: Iterator迭代器
foreach循环
for循环
ArrayList:数组实现----查找快,增删慢
ArrayList 底层就是一个长度可变的 Object 数组 .ArrayList 底层使用了一个 Object [] 来存储数据 , 还定义了一个 int 型 size 来记录集合中元素的个数 .在 jdk1 . 7 中 , 使用无参构造方法创建 ArrayList 对象时 , 底层 Object [] 的默认长度为 10 ;ArrayList list = new ArrayList (); // 底层创建了长度是 10 的 Object[] 数组elementData而在 jdk1 . 8 中 , 使用使用无参构造方法创建 ArrayList 对象时 , 底层 Object [] 的默认长度为 0 ;在第一次添加数据的时候 , 底层才创建了长度 10 的数组,ArrayList list = new ArrayList (); // 底层 Object[] elementData 初始化为 {}. 并没创建长度为 10 的数组ArrayList 中的扩容操作 : int newCapacity = oldCapacity + ( oldCapacity >> 1 );默认情况下,扩容为原来的容量的 1 . 5 倍,同时需要将原有数组中的数据复制到新的数组中
LinkedList: 双向链表实现-----增删快,查找慢
LinkedList :双向链表,内部没有声明数组,而是定义了 Node 类型的 first 和 last ,用于记录首末元素 .LinkedList 类中定义了一个结点类 Node ; 用来保存数据和指针域 ;
Vector:线程安全的,效率低
jdk7和jdk8中通过Vector()构造器创建对象时,底层都创建了长度为10的数组。 在扩容方面,默认扩容为原来的数组长度的2倍。
Set接口
- 数据特点: 无序 , 不可重复
具体体现:
1. 无序性:指元素存储位置的无序性 , 不等于随机性。存储的数据在底层数组中并非照数组索引 的顺序添加,而是根据数据的哈希值决定的。2. 不可重复性:相同的元素只能添加一个 ; 比较时调用的方法是 equals() 方法
- 主要实现类
/----Set接口 : 元素 无序 且 不可重复; ---->高中"集合"概念
/----HashSet : 作为Set接口的主要实现类 ; 线程不安全的 ;可以存储null值
/----LinkedHashSet : 作为HashSet的子类; 遍历数据时,可以按照添加的顺序遍历; 在添加数据的同时 , 每个数据还维护了两个引用,记录此数据的前一个数据和后一个数据 (双向链表); 对于频繁遍历的操作,LinkedHashSet的效率比HashSet的效率要高.
/TreeSet : 底层采用红黑树实现; 可以按照添加对象的指定属性,进行排序
- HashSet 和 LinkedHashSet 底层 都是用 哈希表来实现的 。
-
常用方法 :Set 接口中没额外定义新的方法,使用的都是 Collection 中声明过的方法。
-
存储对象所在类的要求:HashSet/LinkedHashSet:要求:向 Set( 主要指: HashSet 、 LinkedHashSet) 中添加的数据,其所在的类一定要重写hashCode() 和 equals()
双列集合框架Map
/----Map接口: 双列集合 , 存储具有映射关系的键值对数据(key - value) --类似函数:y=f(x)
/----HashMap : 作为 Map接口的主要实现类; 线程不安全的 效率高;可以存储 null的key和
value.
/----LinkedHashMap : 可以按照添加的顺序对集合进行遍历;(原因 : 在原HashMap底层结构基础上,添加了一个双向链表;指向前一个和后一个元素)
/----TreeMap : 可以对添加的元素进行排序 ;此时考虑 key的自然排序或定制排序(和value无
关)
/----HashTable : 古老的实现类; 线程安全的,效率低; 不能存储 null的key和value
/----properties : 常用来处理配置文件. key 和value 都是String 类型的.
Map接口常用方法
* 添加: put ( Object key , Object value )* 删除: remove ( Object key )* 修改: put ( Object key , Object value )* 查询: get ( Object key )* 长度: size ()* 遍历: keySet () / values () / entrySet ()
HashMap底层
HashMap 是 Map 接口的主要实现类 ; 是线程不安全的 ; 它存储的是 key-value 形式的键值对数据, 而且允许存储 null 的 key 和 value.hashmap 的底层结构是哈希表 ; 也就是数组 + 链表 ; 但在 jdk1.8 之后就变成了数 组+ 链表 + 红黑树 ;hashmap 底层数组的默认长度为 16; 默认的加载因子为 0.75; 底层使用一个Node 内部类来封装 key-value 数据 ;node 实例对象在数组中以单链表的形式存在.在 HashMap 中, hashCode() 方法决定了对象会被放到哪个 bucket 里,当多个 对象的哈希值冲突时,equals() 方法决定了这些对象是否是 “ 同一个对象 ” 。 当我们使用put(key,value) 方法添加数据时 , 首先会对 key 进行 hash 计算 , 得到在数组中的存放位置; 如果该位置上为空 , 那就新建一个 node 对象 , 保存 key-value到该数组位置上 ; 如果计算出来的数组位置上有元素的话 ; 就沿着此数组位 置对应的单链表上的结点一个个比较,如果遇到相同的key ,就用新的 value 替换掉旧的value ,如果找不到相同的 key ,就新建一个 node 结点,保存 hash值,key 和 value ,然后插入到此单链表的尾部。 插入之后 , 如果链表的长度大 于8 且数组的长度大于 64, 那么链表就会转为红黑树 . 这样做的目的是为了提高get( key )的速度。由时间复杂度原来的 O ( n )变成了 O ( logn ) ;插入数据的时候,如果 HashMap 的装载量(全部键值对的个数)超过阈值 , 那么 就会调用resize() 方法进行扩容 , 数组长度为原来的两倍 .
Collections工具类:操作Collection和Map的工具类
reverse(List):反转 List 中元素的顺序
shuffle(List):对 List 集合元素进行随机排序
sort(List):根据元素的自然顺序对指定 List 集合元素升序排序
sort(List,Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
swap(List,int i, int j):将指定 list 集合中的 i 处元素和 j 处元素进行交换
Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素
Object min(Collection)
Object min(Collection,Comparator)
int frequency(Collection,Object):返回指定集合中指定元素的出现次数
void copy(List dest,List src):将src中的内容复制到dest中
boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换List 对象的所有旧值
- ArrayList和HashMap都是线程不安全的,如果程序要求线程安全,我们可以将ArrayList、 HashMap转换为线程安全的。 使用synchronizedList(List list) 和 synchronizedMap(Map map)