本文发表于本人博客

上次说了下HashSet和HashMap之间的关系,其中HashMap这个内部有这么一句:

static final float DEFAULT_LOAD_FACTOR = 0.75f;

这一句表示一个常量,作用是当容器数量大小达到0.75%的时候就进行重新在构建一个2倍大小的数组。竟然这2个是集合,那我们今天看看其他的集合类,比如:ArrayList、Vector、LinkedList,开始咯。

首先查看下ArrayList的源码,这几个集合类都是在java.util包下;看其构造:

    private transient Object[] elementData;
public ArrayList(int initialCapacity) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
this.elementData = new Object[initialCapacity];
}
public ArrayList() {
this(10);
}

可以看到当我们new ArrayList()的时候,其实质在内部是默认初始化一个长度为10的Object对象数组。明白了内部是用数组来实现的;继续看下那如果我们add的时候又是怎么样的呢?看看其代码:

    public boolean add(E e) {
ensureCapacity(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
public void ensureCapacity(int minCapacity) {
modCount++;
int oldCapacity = elementData.length;
if (minCapacity > oldCapacity) {
Object oldData[] = elementData;
int newCapacity = (oldCapacity * 3)/2 + 1;
if (newCapacity < minCapacity)
newCapacity = minCapacity;
elementData = Arrays.copyOf(elementData, newCapacity);
}
}

这里可以看出,add对象的时候先要去判断插入的位置小标是否大于数组的长度,是的话就重新实例化一个数组赋值给elementData,重新实例化数组的时候长度时按照当前长度*3 / 2 +1来计算,那就是每次add的时候都会判断,如果一次性插入很多(几百几千上万)元素对象的话,那我们可以想象一下,10个元素的时候就会重新实例化一个数组,在16个时候又一次,在25的时候又一次,以此算下去这样的性能应该是太差了,所以我们在知道数组长度的情况可以直接调用带参数构造来提升一下性能如:

ArrayList list = new ArrayList(10000);

那接下来我们看看其查询方法:

    public boolean contains(Object o) {
return indexOf(o) >= 0;
}
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}

可以看出查询的时候是遍历整个数组的!在看下移除代码:

    public E remove(int index) {
RangeCheck(index);
modCount++;
E oldValue = (E) elementData[index];
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,numMoved);
elementData[--size] = null;
return oldValue;
}
private void RangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException("Index: "+index+", Size: "+size);
}

这移除的代码也是先要判断小标是否超出了,不然就截取数组。从这写方面我们可以看出:ArrayList在查找的时候直接使用的是下标遍历,增加删除的时候是需要重新构造一个新的数组性能消耗比较大!


现在来看下Vector这个类,先看其构造:

    protected Object[] elementData;
public Vector(int initialCapacity) {
this(initialCapacity, 0);
}
public Vector() {
this(10);
}
public Vector(int initialCapacity, int capacityIncrement) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
this.elementData = new Object[initialCapacity];
this.capacityIncrement = capacityIncrement;
}

可以看出这个Vector内部也是使用的是Object数组来实现的,initialCapacity我们可以看出是设置的数组长度,而capacityIncrement是什么呢,我们看看其注释:

    /**
* The amount by which the capacity of the vector is automatically
* incremented when its size becomes greater than its capacity. If
* the capacity increment is less than or equal to zero, the capacity
* of the vector is doubled each time it needs to grow.
*
* @serial
*/

我靠英文不好,奇葩了,有点难我们看使用的地方:

    private void ensureCapacityHelper(int minCapacity) {
int oldCapacity = elementData.length;
if (minCapacity > oldCapacity) {
Object[] oldData = elementData;
int newCapacity = (capacityIncrement > 0) ?(oldCapacity + capacityIncrement) : (oldCapacity * 2);
if (newCapacity < minCapacity) {
newCapacity = minCapacity;
}
elementData = Arrays.copyOf(elementData, newCapacity);
}
}

从这个代码中可以看到当设置了capacityIncrement>0时,增加对象的时候会在重新构造新数组的时候作为新数组的长度,否则就是原来数组长度的2倍。OK!兄弟英文不好,苦逼啊!

下面来看看其的增加删除是怎么实现的。看代码:

    public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
public synchronized E remove(int index) {
modCount++;
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
Object oldValue = elementData[index];
int numMoved = elementCount - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index, numMoved);
elementData[--elementCount] = null;
return (E)oldValue;
}

呀,这里出现了synchronized关键字了,是同步管理哦。增加Add的时候先去判断是否需要重新构造新的数组,然后赋值;删除的时候先判断下标是否超出,否则直接截取数组,这个跟ArrayList一样!到此可以看出Vector内部也是使用的是数组来实现,但是在增加删除有些方法的时候使用同步即使在多线程下也能确保正确。这个的性能会比ArrayList稍点,但是如果考虑到多线程环境下应该使用这个确保查询正确执行。


接下来我们看看LinkedList这个,看下其代码:

public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable

从这个可以看到,这个LinkedList竟然实现了Deque哦,可以看看这个的注释,是双向循环的链表。那竟然是链表大家应该知道了,链表在插入数据删除数据的时候会不数组的要快,原因是不用去移除数据直接来弄再修改指针就可以了,但是也有不好的地方就是查询啊,是要先从当前节点的位置从下或网上依次查找下次找个比较费时!

public boolean add(E e) {
addBefore(e, header);
return true;
}
private Entry<E> addBefore(E e, Entry<E> entry) {
Entry<E> newEntry = new Entry<E>(e, entry, entry.previous);
newEntry.previous.next = newEntry;
newEntry.next.previous = newEntry;
size++;
modCount++;
return newEntry;
}
private E remove(Entry<E> e) {
if (e == header)
throw new NoSuchElementException();
E result = e.element;
e.previous.next = e.next;
e.next.previous = e.previous;
e.next = e.previous = null;
e.element = null;
size--;
modCount++;
return result;
}

从上面代码可以看出,增加删除都只是对header的节点进行操作就可以了,这样大大提高性能。

    private transient Entry<E> header = new Entry<E>(null, null, null);
private transient int size = 0;
public LinkedList() {
header.next = header.previous = header;
} private static class Entry<E> {
E element;
Entry<E> next;
Entry<E> previous;
......
......
}

从这部分代码可以看出使用了header来做链接节点,找个header有previous以及next节点,一前一后构成了整个链表循环。

这次先到这里。坚持记录点点滴滴!

Java基础知识陷阱(七)的更多相关文章

  1. Java基础知识陷阱系列

    Java基础知识陷阱系列 今天抽空把Java基础知识陷阱有关的文章汇总于此,便于大家查看. Java基础知识陷阱(一) Java基础知识陷阱(二) Java基础知识陷阱(三) Java基础知识陷阱(四 ...

  2. Java基础知识陷阱(九)

    本文发表于本人博客. 今天我来说说关于JAVA多线程知识,有错误请指出.大家都知道JAVA在服务端上处理也有很大优势,很多公司也有在服务器跑JAVA进程,这说明JAVA在处理这个多线程以及并发下也有一 ...

  3. Java基础知识陷阱(十)

    本文发表于本人博客. 上个星期由于时间比较紧所以未能继续写下去,今天再接再厉,专心 + 坚持这样离目标就越来越近了!废话少说说正题,今天我们还是来说说java中比较基础的知识,大家知道编写java程序 ...

  4. Java基础知识陷阱(二)

    本文发表于本人博客. 上次说了一些关于字符串的知识,都是比较基础的,那这次也说下关于对象地址问题,比如传参.先看下面代码: public void changeInt(int a){ a = ; } ...

  5. Java基础知识陷阱(六)

    本文发表于本人博客. 上次说了下equals跟==的问题,今天再来认识一下这个equals()跟hasCode().上次的代码如下: class Person{ public String name; ...

  6. Java基础知识陷阱(四)

    本文发表于本人博客. 今天我们来说说关于java继承以及反射有关的问题,大家先看下下面代码,试问可以编译通过不,为什么具体说说原因? public class Test{ public static ...

  7. Java基础知识陷阱(三)

    本文发表于本人博客. 之前都讲了有关字符串的陷阱,那今天来说下关于静态这个东西,这分为静态变量.静态方法,先看下面的代码请问结果输出是什么?: class Person01{ private stat ...

  8. Java基础知识陷阱(一)

    本文发表于本人博客. 事隔好多年了,重新拿起来Java这门语言,看似熟悉其实还很陌生,想想应该梳理下顺便提高下自己.这次先来看看Java里面的String这个对象. 经典的先看下面一段代码,请问最终创 ...

  9. JAVA基础知识(七)存根类

    存根类是一个类,它实现了一个接口,它的作用是:如果一个接口有很多方法,如果要实现这个接口,就要实现所有的方法.但是一个类从业务来说,可能只需要其中一两个方法.如果直接去实现这个接口,除了实现所需的方法 ...

随机推荐

  1. 【BZOJ】1646: [Usaco2007 Open]Catch That Cow 抓住那只牛(bfs)

    http://www.lydsy.com/JudgeOnline/problem.php?id=1646 这一题开始想到的是dfs啊,,但是本机测样例都已经re了... 那么考虑bfs...很巧妙? ...

  2. jQuery实现瀑布流布局详解(PC和移动端)

    首先我们将如下样式的若干个单元写进body中,并将“box”向左浮动: <div class="box">  <img class="img" ...

  3. Asp.net中的Cache--HttpRuntim.Cache 和 HttpContext.Current.Cache

    在ASP.NET中有两个类都提供缓存支持, 一个是HttpRuntime类的Cache属性, 另一个是HttpContext类的Cache属性. 通过查看这两个属性的类型可以发现其实这两个属性都是Sy ...

  4. Python爬虫(七)

    源码: import requests import re from my_mysql import MysqlConnect # 获取详情页链接和电影名称 def get_urls(page): u ...

  5. c#后台读写Cookie

    public class BaseCookies { #region Cookies public static void SetCookieValue(string key, string valu ...

  6. 概览C++之const

    1.C语言中const与 C++中的const void main() { const int a = 10; int *p = (int*)&a; *p = 20; printf(" ...

  7. 复习及总结--.Net线程篇(1)

    老是没耐心写这些东西,最近想想也工作两年了,该对自己的东西做个整理了,不知道这次能坚持写几篇,总得来说尽量督促自己吧 言归正传,.net中的多线程主要可以使用两种方法进行调用 1,异步调用 2,Thr ...

  8. 利用hugo生成静态站点

    动机 使用Markdown撰写博客,并以静态页面形式发布. 选择hugo 现在jekyll似乎更加流行,但是jekyll是基于Ruby的,在windows下安装很繁琐. 而hugo是用go写的,win ...

  9. 40 个顶级 jQuery 图片、内容滑块和幻灯片

    在这个快速发展的网络世界中,我们使用图片.内容滑块和幻灯片来给网站实现良好.有吸引力的外观.你可以吸引浏览者借助图像滑块让网站更加具有活力.使用 JavaScript 可以轻松实现轻量级的图片和内容滑 ...

  10. std::mutex

    构造函数,std::mutex不允许拷贝构造,也不允许 move 拷贝,最初产生的 mutex 对象是处于 unlocked 状态的. lock(),调用线程将锁住该互斥量.线程调用该函数会发生下面 ...