12.2.5 链表
1、链表是一种数据结构
(1)单向链表
(2)双向链表
(3)循环链表
数组的元素是连续存储的,位置是相邻的。
链表:元素不要求连续存储。
结点:包含数据(对象)及其关系。
2、源码中LinkedList也是双向链表。
12.2.5 自定义双向链表
import java.util.Iterator;
/*
尝试自定义双向链表(部分方法)
*/
public class PairLinkedList<E> implements Iterable<E>{
private PairNode<E> first;
private PairNode<E> last;
private int size;
public void add(E e){
// (1)先创建结点把e包装起来
PairNode<E> newNode = new PairNode<>(last, e, null);
//(2)分情况
if(first == null){//链表是空的 first == null 或 last == null
first = newNode;
}else{
last.next = newNode;
}
//(3)新结点链表的最后一个结点
last = newNode;
//(4)元素个数增加
size++;
}
public void add(int index, E e){
//检查index的合法性
if(index < 0 || index > size){
throw new IndexOutOfBoundsException();
}
//确定index对应的结点
PairNode<E> node = first;
for(int i = 0; i< index; i++){
node = node.next;
}
if(node == null){//末尾添加
add(e);
}else {//非末尾位置添加
//在该结点的前面插入新的结点
// (1)先创建结点把e包装起来
PairNode<E> newNode = new PairNode<>(node.previous, e, node);
if(node.previous == null){//插入位置是头部
first = newNode;
}else{//插入位置非头部
node.previous.next = newNode;
}
node.previous = newNode;
size++;
}
}
public void remove(Object obj){
//找到obj对应的node
PairNode<E> node = first;
if(obj == null){
while(node != null){
if(node.data == null){
break;
}
node = node.next;
}
}else{
while(node != null){
if(obj.equals(node.data)){
break;
}
node = node.next;
}
}
if(node == null){//被删除结点不存在,不是删除
return;
}
if(node.previous == null){//头部删除
node.next.previous = null;
//或 node.next.previous = node.previous;
first = node.next;
}else {//非头部删除
node.previous.next = node.next;
if (node.next == null) {//尾部删除
last = node.previous;
} else {//非尾部删除
node.next.previous = node.previous;
}
}
node.previous = null;
node.next = null;
node.data = null;
size--;
}
@Override
public Iterator<E> iterator() {
return new Itr();
}
private class Itr implements Iterator<E>{
PairNode<E> node = first;
@Override
public boolean hasNext() {
return node != null;
}
@Override
public E next() {
E data = node.data;
node = node.next;
return data;
}
}
private static class PairNode<E>{
PairNode<E> previous;
E data;
PairNode<E> next;
PairNode(PairNode<E> previous, E data, PairNode<E> next) {
this.previous = previous;
this.data = data;
this.next = next;
}
}
}
import org.junit.Test;
public class TestPairLinkedList {
@Test
public void test1(){
PairLinkedList<String> list = new PairLinkedList<>();
list.add("hello");
list.add("world");
list.add("java");
list.add("atguigu");
for (String s : list) {
System.out.println(s);
}
}
@Test
public void test2(){
PairLinkedList<String> list = new PairLinkedList<>();
list.add("hello");
list.add("world");
list.add("java");
list.add("atguigu");
list.add(2,"haha");
for (String s : list) {
System.out.println(s);
}
}
@Test
public void test3(){
PairLinkedList<String> list = new PairLinkedList<>();
list.add(0,"hello");
list.add(0,"world");
list.add(1,"java");
list.add(2,"atguigu");
for (String s : list) {
System.out.println(s);
}
}
@Test
public void test4(){
PairLinkedList<String> list = new PairLinkedList<>();
list.add("hello");
list.add("world");
list.add("java");
list.add("atguigu");
list.remove("world");
list.remove("hello");
list.remove("atguigu");
for (String s : list) {
System.out.println(s);
}
}
}
12.2.6 自定义单向链表
import java.util.Iterator;
public class SingleLinkedList<E> implements Iterable<E>{
private SingleNode<E> first;
private int size;
public void add(E e){
//创建结点
SingleNode<E> newNode = new SingleNode<>(e,null);
if(first == null){//链表是空额
first = newNode;
}else {//链表非空
//查找末尾结点
SingleNode<E> node = first;
while (node.next != null) {
node = node.next;
}
//把新结点连接到node的后面
node.next = newNode;
}
//元素个数增加
size++;
}
@Override
public Iterator<E> iterator() {
return new Itr();
}
private class Itr implements Iterator<E>{
SingleNode<E> node = first;
@Override
public boolean hasNext() {
return node != null;
}
@Override
public E next() {
E data = node.data;
node = node.next;
return data;
}
}
private static class SingleNode<E>{
E data;
SingleNode<E> next;
SingleNode(E data, SingleNode<E> next) {
this.data = data;
this.next = next;
}
}
}
import org.junit.Test;
public class TestSingleLinkedList {
@Test
public void test1(){
SingleLinkedList<String> list = new SingleLinkedList<>();
list.add("hello");
list.add("java");
list.add("world");
for (String s : list) {
System.out.println(s);
}
}
}
12.2.7 链表与动态数组的对比
动态数组和链表的特点的对比
(1)底层结构
数组的元素是连续存储的,元素的位置是相邻的,元素的访问可以快速的根据下标进行元素访问。
链表:元素不要求连续存储,而是通过“结点”将元素“串联”起来。
(2)性能的对比
A:根据下标访问,数组效率非常高,链表相对慢一点。
B:添加元素的效率
①末尾位置添加元素,动态数组更快一点(虽然数组需要扩容,但是反而更快),链表反而更慢(因为链表需要创建结点)
②任意位置添加元素,动态数组更快一点(虽然数组需要扩容,但是反而更快),链表反而更慢(因为链表需要创建结点)(测试目标1000000个对象)
③任意位置添加元素,动态数组更快一点(虽然数组需要扩容,但是反而更快),链表反而更慢(因为链表需要创建结点)(测试目标10000个对象)
C:删除元素的效率
①根据下标的删除,动态数组更快一点,链表反而更慢(因为链表需要统计下标)
②末尾位置删除,动态数组更快一点,链表反而更慢(因为链表需要清理结点,处理last)
③根据目标的删除,动态数组和链表比较接近
对象少,链表快
对象多,数组快
package com.atguigu.link;
import org.junit.Test;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Random;
public class TestTime {
@Test
public void test1() {
long start = System.currentTimeMillis();
ArrayList<Double> list = new ArrayList<>();
for (int i = 1; i <= 10000000; i++) {
list.add(Math.random());
}
long end = System.currentTimeMillis();
System.out.println("ArrayList末尾添加耗时:" + (end - start));//457毫秒
long memory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
System.out.println("ArrayList末尾添加占用内存: " + memory);//361114168字节
}
@Test
public void test2(){
long start = System.currentTimeMillis();
LinkedList<Double> list = new LinkedList<>();
for(int i=1; i<=10000000; i++){
list.add(Math.random());
}
long end = System.currentTimeMillis();
System.out.println("LinkedList末尾添加耗时:" + (end-start));//4194毫秒
long memory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
System.out.println("LinkedList末尾添加占用内存: " + memory);//498723056字节
}
@Test
public void test3(){
long start = System.currentTimeMillis();
ArrayList<Double> list = new ArrayList<>();
Random random = new Random();
for (int i = 1; i <= 10000; i++) {
int index = Math.abs(random.nextInt());
index = list.size() != 0 ? index%list.size() : 0;
list.add(index,Math.random());
}
long end = System.currentTimeMillis();
System.out.println("ArrayList插入耗时:" + (end - start));//25239毫秒(100万)、7毫秒(1万)
long memory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
System.out.println("ArrayList插入占用内存: " + memory);//54395728字节
}
@Test
public void test4(){
long start = System.currentTimeMillis();
LinkedList<Double> list = new LinkedList<>();
Random random = new Random();
for (int i = 1; i <= 10000; i++) {
int index = Math.abs(random.nextInt());
index = list.size() != 0 ? index%list.size() : 0;
list.add(index,Math.random());
}
long end = System.currentTimeMillis();
System.out.println("LinkedList插入耗时:" + (end-start));//85(1万)
long memory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
System.out.println("LinkedList插入占用内存: " + memory);//13439648
}
@Test
public void test5() {
ArrayList<Double> list = new ArrayList<>();
for (int i = 1; i <= 10000000; i++) {
list.add(Math.random());
}
long start = System.currentTimeMillis();
//删除
for (int i = list.size()-1; i >=0 ; i--) {
list.remove(i);
}
long end = System.currentTimeMillis();
System.out.println("ArrayList末尾删除耗时:" + (end - start));//17(10000000个对象)
long memory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
System.out.println("ArrayList末尾删除耗占用内存: " + memory);//361138184
}
@Test
public void test6(){
LinkedList<Double> list = new LinkedList<>();
for(int i=1; i<=10000000; i++){
list.add(Math.random());
}
long start = System.currentTimeMillis();
//删除
for (int i = list.size()-1; i >=0 ; i--) {
list.remove(i);
}
long end = System.currentTimeMillis();
System.out.println("LinkedList末尾删除耗时:" + (end-start));//118毫秒
long memory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
System.out.println("LinkedList末尾删除占用内存: " + memory);//498733848
}
@Test
public void test7() {
ArrayList<Double> list = new ArrayList<>();
for (int i = 1; i <= 100000; i++) {
list.add(Math.random());
}
long start = System.currentTimeMillis();
//删除
Random random = new Random();
for (int i = 1; i <= 10000; i++) {
int index = Math.abs(random.nextInt());
index = list.size() != 0 ? index % list.size() : 0;
list.remove(index);
}
long end = System.currentTimeMillis();
System.out.println("ArrayList非末尾删除耗时:" + (end - start));//42毫秒
long memory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
System.out.println("ArrayList非末尾删除耗占用内存: " + memory);//361138184
}
@Test
public void test8(){
LinkedList<Double> list = new LinkedList<>();
for(int i=1; i<=100000; i++){
list.add(Math.random());
}
long start = System.currentTimeMillis();
//删除
Random random = new Random();
for (int i = 1; i <= 10000; i++) {
int index = Math.abs(random.nextInt());
index = list.size() != 0 ? index % list.size() : 0;
list.remove(index);
}
long end = System.currentTimeMillis();
System.out.println("LinkedList非末尾删除耗时:" + (end-start));//583毫秒
long memory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
System.out.println("LinkedList非末尾删除占用内存: " + memory);//18808352
}
@Test
public void test9() {
ArrayList<Double> list = new ArrayList<>();
for (int i = 1; i <= 100000; i++) {
list.add(Math.random());
}
long start = System.currentTimeMillis();
//删除
for(int i=1; i<=10000; i++) {
list.remove(Math.random());
}
long end = System.currentTimeMillis();
System.out.println("ArrayList非末尾,查询对象删除耗时:" + (end - start));//1218
long memory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
System.out.println("ArrayList末尾,查询对象删除耗占用内存: " + memory);//18808376
}
@Test
public void test10(){
LinkedList<Double> list = new LinkedList<>();
for(int i=1; i<=100000; i++){
list.add(Math.random());
}
long start = System.currentTimeMillis();
//删除
for(int i=1; i<=10000; i++){
list.remove(Math.random());
}
long end = System.currentTimeMillis();
System.out.println("LinkedList非末尾删除耗时:" + (end-start));//2732
long memory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
System.out.println("LinkedList末尾删除占用内存: " + memory);//21492728
}
}
12.3 Map集合
12.3.1 Map接口
1、Java中的集合分为两大类:
(1)Collection系列:存储一组对象,里面的对象是单个、单个的
(2)Map系列:存储一组键值对(key,value),又称为映射关系
A:一个key可以映射到一个value;
B:key不能重复,但是value允许重复
C:key,value可以是任意引用数据类型
2、java.util.Map<K,V>接口(必须掌握)
(1)增加
V put(K key, V value):添加一对键值对
void putAll(Map<? extends K,? extends V> m):添加一组键值对
(2)删除
void clear():清空
V remove(Object key) :根据key删除一对(key,value)
(3)修改
JDK1.8之前,要替换value,需要重新put
JDK1.8之后:
V replace(K key, V value):只要key匹配就行,用新的value替换旧的value
boolean replace(K key, V oldValue, V newValue) :必须匹配(key,oldValue),再用 新的value替换旧的value
void replaceAll(BiFunction<? super K,? super V,? extends V> function):根据function指定的操作,替换所有满足的value
(4)查询
boolean containsKey(Object key) :判断某个key是否存在
boolean containsValue(Object value) :判断某个value是否存在
int size() :键值对的数量
boolean isEmpty() :是否为空
V get(Object key) :根据key获取value(常用)
(5)遍历
A:Set<K> keySet() :遍历所有的key。(因为key不会重复,所以所有的key组成一个set集合)
B:Collection<V> values() :遍历所有的value。(因为value可能重复,所以所有的value组成一个Collection的某集合)
C:Set<Map.Entry<K,V>> entrySet() :遍历所有的(key,value)
(因为key不会重复,所以所有的(key,value)组成一个set集合)
(key,value)在map内部其实是以Entry对象存在。可以把Entry比喻为链表的结点。
Entry是Map接口中的内部接口。
D:Java8版本,增加了一个forEach方法
void forEach(BiConsumer<? super K,? super V> action)
12.3.2 Map实现类
3、Map接口的实现类:
(1)HashMap<K,V>:哈希表,key不可重复,完全无序
(2)TreeMap<K,V>:红黑树,key不可重复,按照key的大小顺序,所以它会依赖于Comparable接口和Comparator接口。
(3)LinkedHashMap<K,V>:key不可重复,按照(key,value)的添加顺序
LinkedHashMap是HashMap的子类。比HashMap多加了一条双向链表,记录它们的添加顺序。
(4)Hashtable<K,V>:哈希表,key不可重复,完全无序
Hashtable:旧版,线程安全的,不支持key,value为null
HashMap:新版,线程不安全的,支持key,value为null
(5)Properties:属性表,key不可重复,完全无序
Properties是Hashtable的子类,而且它的key和value都是String类型。
package com.atguigu.map;
import org.junit.Test;
import java.io.IOException;
import java.text.Collator;
import java.util.*;
public class TestMapImpl {
@Test
public void test1(){
HashMap<String, String> map = new HashMap<>();
map.put("一", "张三");
map.put("二", "李四");
map.put("三", "王五");
map.put("四", null);
map.put(null, "熊大");
System.out.println(map);
//{一=张三, null=熊大, 三=王五, 四=null, 二=李四}
}
@Test
public void test2(){
Hashtable<String, String> map = new Hashtable<>();
map.put("一", "张三");
map.put("二", "李四");
map.put("三", "王五");
// map.put("四", null);
// map.put(null, "熊大");
System.out.println(map);//{一=张三, 三=王五, 二=李四}
}
@Test
public void test3(){
TreeMap<String, String> map = new TreeMap<>();
map.put("yi", "张三");
map.put("er", "李四");
map.put("san", "王五");
System.out.println(map);//{er=李四, san=王五, yi=张三}
}
@Test
public void test4(){
TreeMap<String, String> map = new TreeMap<>(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return Collator.getInstance(Locale.CHINA).compare(o1,o2);
}
});
map.put("一", "张三");
map.put("二", "李四");
map.put("三", "王五");
System.out.println(map);//{二=李四, 三=王五, 一=张三}
}
@Test
public void test5(){
LinkedHashMap<String, String> map = new LinkedHashMap<>();
map.put("一", "张三");
map.put("二", "李四");
map.put("三", "王五");
System.out.println(map);//{一=张三, 二=李四, 三=王五}
}
@Test
public void test6(){
Properties properties = new Properties();
//虽然也支持put,但是更推荐setProperty
properties.setProperty("user","chai");
properties.setProperty("password","123456");
System.out.println(properties);
}
@Test
public void test7(){
Properties properties = System.getProperties();
Set<Map.Entry<Object, Object>> entries = properties.entrySet();
for (Map.Entry<Object, Object> entry : entries) {
System.out.println(entry);
}
}
@Test
public void test8() {
Properties properties = System.getProperties();
String value = properties.getProperty("file.encoding");
System.out.println(value);
}
@Test
public void test9() throws IOException {
Properties properties = new Properties();
properties.load(ClassLoader.getSystemResourceAsStream("jdbc.properties"));
System.out.println(properties);
}
@Test
public void test10(){
HashMap<Integer, String> map = new HashMap<>();
map.put(155841, "张三");
map.put(2222, "李四");
map.put(3333, "王五");
System.out.println(map);
}
}
12.3.3 Map与Set
4、Map与Set
Set是基于Map结构又再次封装,抽象出来的一类集合。
把Map中所有的key抽出来构成了“元素不可重复的集”。
常见的Set:
HashSet:底层是HashMap
TreeSet:底层是TreeMap
LinkedHashSet:底层是LinkedHashMap
12.4 集合工具类Collections
1、集合工具类:java.util.Collections
2、API
- public static <T> boolean addAll(Collection<? super T> c,T... elements)
将所有指定元素添加到指定 collection 中。
- public static <T> int binarySearch(List<? extends Comparable<? super T>> list,T key)
在List集合中查找某个元素的下标,但是List的元素必须是T或T的子类对象,而且必须是可比较大小的,即支持自然排序的。而且集合也事先必须是有序的,否则结果不确定。
- public static <T> int binarySearch(List<? extends T> list,T key,Comparator<? super T> c)
在List集合中查找某个元素的下标,但是List的元素必须是T或T的子类对象,而且集合也事先必须是按照c比较器规则进行排序过的,否则结果不确定。
- public static <T extends Comparable<? super T>> void sort(List<T> list)根据元素的自然顺序对指定 List 集合元素按升序排序
- public static <T> void sort(List<T> list,Comparator<? super T> c)根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
- public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll)在coll集合中找出最大的元素,集合中的对象必须是T或T的子类对象,而且支持自然排序
- public static <T> T max(Collection<? extends T> coll,Comparator<? super T> comp)在coll集合中找出最大的元素,集合中的对象必须是T或T的子类对象,按照比较器comp找出最大者
- public static void reverse(List<?> list)反转指定列表List中元素的顺序。
- public static void shuffle(List<?> list) List 集合元素进行随机排序,类似洗牌
- public static void swap(List<?> list,int i,int j)将指定 list 集合中的 i 处元素和 j 处元素进行交换
- public static int frequency(Collection<?> c,Object o)返回指定集合中指定元素的出现次数
public static <T> void copy(List<? super T> dest,List<? extends T> src)将src中的内容复制到dest中,会覆盖原来的元素
public static <T> boolean replaceAll(List<T> list,T oldVal,T newVal):使用新值替换 List 对象的所有旧值
package com.atguigu.collections;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
public class TestCollections {
@Test
public void test1(){
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "hello","java","world","hello");
System.out.println(list);
}
@Test
public void test2(){
HashSet<String> list = new HashSet<>();
Collections.addAll(list, "hello","java","world","hello");
System.out.println(list);
}
@Test
public void test3(){
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list, 4,5,1,2,6,9,41);//没有按照大小排序
int index = Collections.binarySearch(list, 1);
System.out.println(index);
}
@Test
public void test4(){
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list, 1,2,4,5,6,9,41);//按照大小排序
int index = Collections.binarySearch(list, 1);
System.out.println(index);//0
}
@Test
public void test5(){
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list, 4,5,1,2,6,9,41);//没有按照大小排序
Collections.sort(list);
System.out.println(list);//[1, 2, 4, 5, 6, 9, 41]
}
@Test
public void test6(){
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list, 4,5,1,2,6,9,41);//没有按照大小排序
Integer max = Collections.max(list);
System.out.println(max);//41
}
@Test
public void test7(){
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list, 1,2,4,5,6,9,41);
System.out.println("反转之前:" + list);
Collections.reverse(list);
System.out.println("反转之后:" + list);
Collections.shuffle(list);
System.out.println("洗牌之后:" + list);
}
@Test
public void test8(){
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list, 41,2,41,5,6,9,41);
int count = Collections.frequency(list, 41);
System.out.println(count);
}
@Test
public void test9(){
ArrayList<Integer> list1 = new ArrayList<>();
Collections.addAll(list1, 1,2,4,5,6,9,41);
ArrayList<Integer> list2 = new ArrayList<>();
Collections.addAll(list2, 8,8,8,8,8,8);
Collections.copy(list1, list2);
System.out.println(list1);//[8, 8, 8, 8, 8, 8, 41]
System.out.println(list2);//[8, 8, 8, 8, 8, 8]
}
@Test
public void test10(){
ArrayList<Integer> list1 = new ArrayList<>();
Collections.addAll(list1, 1,2,4,5,6,9,41);
ArrayList<Integer> list2 = new ArrayList<>();
Collections.addAll(list2, 8,8,8,8,8,8);
list1.addAll(list2);
System.out.println(list1);//[1, 2, 4, 5, 6, 9, 41, 8, 8, 8, 8, 8, 8]
}
@Test
public void test11(){
ArrayList<Integer> list1 = new ArrayList<>();
Collections.addAll(list1, 1,2,4,5);
ArrayList<Integer> list2 = new ArrayList<>();
Collections.addAll(list2, 8,8,8,8,8,8);
Collections.copy(list1, list2);// Source does not fit in dest 错误
}
}