数据结构(比对篇)

ArrayList和LinkedList

作为一个纯粹的数据存储介质,当然从增删改查三个方面比较他们的性能问题!

新增性能

头部插入
List list1 = new ArrayList();
List list2 = new LinkedList();
long start11 = System.currentTimeMillis();
for (int i = 0; i < 10000000; i++) {
    list1.add(i);
}
long start12 = System.currentTimeMillis();
System.out.println(start12-start11);

long start21 = System.currentTimeMillis();
for (int i = 0; i < 10000000; i++) {
    list2.add(i);
}
long start22 = System.currentTimeMillis();
System.out.println(start22-start21);

#输出结果
    414
	1697

结论:ArrayList比LinkedList快

原因分析:按序插入时,ArrayList添加元素直接在数组上新增,Linkedlist链表添加需要执行两步:

​ 1、指定最后一个元素的末位置是新元素的head

​ 2、标记新元素为链表最后一个

指定下标插入
List list1 = new ArrayList();
List list2 = new LinkedList();
for (int i = 0; i < 10000000; i++) {
    list1.add(i);
    list2.add(i);
}

long start11 = System.currentTimeMillis();
for (int i = 0; i < 1000; i++) {
    list1.add(i + 2, i);
}
long start12 = System.currentTimeMillis();
System.out.println(start12 - start11);

long start21 = System.currentTimeMillis();
for (int i = 0; i < 1000; i++) {
    list2.add(i + 2, i);
}
long start22 = System.currentTimeMillis();
System.out.println(start22 - start21);

#输出结果
    16428
	5

结论:ArrayList比LinkedList慢太多,而且基数越大,ArrayList的耗时成指数级上升!

原因分析:

​ ArrayList需要找到插入的位置index,然后使用System.arraycopy()方法复制index前后的数据,然后让拼接成新的组合数组,然后再删除原先旧的数组

​ LinkedList只需要找到插入的位置index,断开index-1和index+1的连接,然后让index-1指向index,index指向index+1即可

删除性能

按坐标删除
List array = new ArrayList();
List linked = new LinkedList();
for (int i = 0; i < 100000; i++) {
    array.add(i+"");
    linked.add(i+"");
}

long start1 = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
    array.remove(0);
}
long end1 = System.currentTimeMillis();
System.out.println(end1 - start1);

long start2 = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
    linked.remove(0);
}
long end2 = System.currentTimeMillis();
System.out.println(end2 - start2);

#输出结果
    761
	6

结论:LinkedList比ArrayList快太多!

按元素删除
List array = new ArrayList();
List linked = new LinkedList();
for (int i = 0; i < 100000; i++) {
    array.add(i+"");
    linked.add(i+"");
}
//以下为了更好的理解,不决定引入迭代类删除,所以顶多只能删除一半数据
long start1 = System.currentTimeMillis();
for (int i = 0; i < 50000; i++) {
    array.remove(i+"");
}
long end1 = System.currentTimeMillis();
System.out.println(end1 - start1);

long start2 = System.currentTimeMillis();
for (int i = 0; i < 50000; i++) {
    linked.remove(i+"");
}
long end2 = System.currentTimeMillis();
System.out.println(end2 - start2);

#输出结果
    664
	8

结论:LinkedList比ArrayList快太多!

原因分析:根据结论,无论是按坐标删除还是按集合元素删除,LinkedList都比ArrayList快太多!原因很简单,ArrayList删除一个元素意味着这个列表中剩余的元素都会被移动,特别是从集合第一个元素开始删除时,N-1个元素都得位移,而LinkedList只需要改变指向关系即可,特别是集合第一个元素,甚至都不需要改变指向关系,只需要让index为1的标记为新的index为0的头元素

修改性能

List array = new ArrayList();
List linked = new LinkedList();
for (int i = 0; i < 100000; i++) {
    array.add(i);
    linked.add(i+"");
}
long start1 = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
    array.set(i,i+1);
}
long end1 = System.currentTimeMillis();
System.out.println(end1 - start1);

long start2 = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
    linked.set(i,i+1);
}
long end2 = System.currentTimeMillis();
System.out.println(end2 - start2);

#输出结果
    4
	9429

结论:ArrayList比LinkedList快太多!

原因分析:ArrayList对于随机的set操作,通过二分查找下标进行value替换;LinkedList需要按照顺序从列表的一端开始检查,直到另外一端!

查询性能

List list1 = new ArrayList();
List list2 = new LinkedList();
for (int i = 0; i < 100000; i++) {
    list1.add(i);
    list2.add(i);
}

long start11 = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
    list1.get(i);
}
long start12 = System.currentTimeMillis();
System.out.println(start12 - start11);

long start21 = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
    list2.get(i);
}
long start22 = System.currentTimeMillis();
System.out.println(start22 - start21);

#输出结果
    1
	5634

结论:ArrayList查询比LinkedList快太多!

原因分析:与修改性能原因分析一样!

HashMap和Hashtable

插一句,隔空问以下Hashtable命名的作者,你是认真的吗?还是觉得它的父类Dictionary已经被废弃了,就这么随意命名了啊?但这又涉及到一个先后顺序啊,未必作者还能未卜先知?

线程安全性

package com.paratera.console.dict.utils;

import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

public class Test {
    static Map map = new HashMap();
    static Map table = new Hashtable();
    public static void main(String[] args) throws InterruptedException {
        ThreadTask threadTask1 = new ThreadTask(map);
        ThreadGroup threadGroup1 = new ThreadGroup("hashMapTest");
        for (int i = 0; i < 10; i++) {
            Thread thread1 = new Thread(threadGroup1,threadTask1);
            thread1.start();
        }
        while (threadGroup1.activeCount() != 0){

        }
        System.out.println("mapSize为:" + map.size());

        ThreadTask threadTask2 = new ThreadTask(table);
        ThreadGroup threadGroup2 = new ThreadGroup("hashTableTest");
        for (int i = 0; i < 10; i++) {
            Thread thread2 = new Thread(threadGroup2,threadTask2);
            thread2.start();
        }
        while (threadGroup2.activeCount() != 0){

        }
        System.out.println("tableSize为:" + table.size());
    }
}

class ThreadTask implements Runnable {

    private Map<String, Integer> map;

    public ThreadTask(Map map) {
        this.map = map;
    }

    @Override
    public void run() {
        for(int i = 0; i < 100; i++) {
            map.put(Thread.currentThread().getName() + i, i);
        }
    }
}


#输出结果
    mapSize为:901
	tableSize为:1000

结论:HashMap线程不安全,并发操作时key会被覆盖,Hashtable线程安全!

原因分析:Hashtable的put方法被synchronized关键字修饰,实现了同步锁

HashMap线程安全加持

synchronize
class ThreadTask implements Runnable {

    private Map<String, Integer> map;

    public ThreadTask(Map map) {
        this.map = map;
    }

    @Override
    public synchronized void run() {
        for(int i = 0; i < 100; i++) {
            map.put(Thread.currentThread().getName() + i, i);
        }
    }
}

#在上面线程类run方法上加synchronized锁住执行
#输出结果
	mapSize为:1000
	tableSize为:1000

结论:HashMap线程操作实现了并发线程安全!

原因分析:其实就是synchronized+Hashtable的效果

ConcurrentHashMap
static Map map = new ConcurrentHashMap();

结论:HashMap线程操作实现了并发线程安全!

原因分析:分段锁技术,首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问----实现了线程安全,也兼容了性能!

HashMap、Hashtable,synchronize、ConcurrentHashMap
线程安全性能线程安全实现机制
HashMap
Hashtable低,等于synchronize通过synchronized对整张Hash表锁定,线程独占
synchronize低,等于Hashtable通过synchronized对整张Hash表锁定,线程独占
ConcurrentHashMap分段锁机制,整个Map分为N个Segment,允许每个Segment的修改操作并发进行,默认16个Segment

HashSet与TreeSet

性能对比

新增
Set hash = new HashSet();
Set tree = new TreeSet();
long start1 = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++) {
    hash.add(i);
}
long end1 = System.currentTimeMillis();
System.out.println(end1-start1);

long start2 = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++) {
    tree.add(i);
}
long end2 = System.currentTimeMillis();
System.out.println(end2-start2);

#输出结果
	151
	393

结论:HashSet比TreeSet快!

原因分析:HashSet使用hash值的地址机制来存储信息的(散列法),元素并没有以某种特定顺序来存放;提供一个使用树结构存储Set接口的实现(红黑树算法),对象以升序顺序存储

查询
Set hash = new HashSet();
Set tree = new TreeSet();
for (int i = 0; i < 10000000; i++) {
    hash.add(i);
    tree.add(i);
}
long start1 = System.currentTimeMillis();
Iterator iterator1 = hash.iterator();
while (iterator1.hasNext()) {
    iterator1.next();
}
long end1 = System.currentTimeMillis();
System.out.println(end1 - start1);

long start2 = System.currentTimeMillis();
Iterator iterator2 = tree.iterator();
while (iterator2.hasNext()) {
    iterator2.next();
}
long end2 = System.currentTimeMillis();
System.out.println(end2 - start2);

#输出结果
	920
	359

结论:HashSet比TreeSet慢!

原因分析:存储的性能优点同时是查询的短板,反之也成立

功能对比

  • HashSet
@Data
class Student implements Comparable<Student> {
    private String name;
    private int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public int compareTo(Student o) {
        int num = this.age - o.age;
        return num == 0 ? this.name.compareTo(o.name): num;
    }
}

public static void main(String[] args) throws InterruptedException {
    Set<Student> ts = new HashSet<Student>();
    Student s1 = new Student("xishi", 29);
    Student s2 = new Student("wangzhaojun", 28);
    Student s3 = new Student("diaochan", 30);
    Student s4 = new Student("yangyuhuan", 33);

    ts.add(s1);
    ts.add(s2);
    ts.add(s3);
    ts.add(s4);
    for (Student s : ts) {
        System.out.println(s.getName() + ", " + s.getAge());
    }
}

#输出结果
	xishi, 29
    yangyuhuan, 33
    diaochan, 30
    wangzhaojun, 28
  • TreeSet
#仅仅将set换成TreeSet
Set<Student> ts = new TreeSet<Student>();

#输出结果
	wangzhaojun, 28
    xishi, 29
    diaochan, 30
    yangyuhuan, 33

结论:TreeSet拥有HashSet所有功能外,还具有排序功能

原因分析:HashSet使用hash值的地址机制来存储信息的(散列法),元素并没有以某种特定顺序来存放;提供一个使用树结构存储Set接口的实现(红黑树算法),对象以升序顺序存储

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值