您的位置:首页 > 编程语言 > Java开发

20.JAVA编程思想——实施解决方案

2016-04-17 09:24 471 查看
 20.JAVA编程思想——实施解决方案

实际上只有三个集合组件:Map,List 和Set。而且每个接口只有两种或三种实施方案。若需使用由一个特定的接口提供的功能,如何才能决定到底采取哪一种方案?

为理解这个问题,必须认识到每种不同的实施方案都有自己的特点、优点和缺点. Hashtable,Vector 和Stack的“特点”是它们都属于“传统”类,所以不会干扰原有的代码。但在另一方面,应尽量避免为新的(Java 1.2)代码使用它们。

其他集合间的差异通常都可归纳为它们具体是由什么“后推”的。换言之,取决于物理意义上用于实施目标接口的数据结构是什么。例如,ArrayList,LinkedList 以及Vector(大致等价于ArrayList)都实现了List 接口,所以无论选用哪一个,我们的程序都会得到类似的结果。然而,ArrayList(以及Vector)是由一个数组后推得到的;而LinkedList 是根据常规的双重链接列表方式实现的,因为每个单独的对象都包含了数据以及指向列表内前后元素的句柄。正是由于这个原因,假如想在一个列表中部进行大量插入和删除操作,那么LinkedList
无疑是最恰当的选择(LinkedList 还有一些额外的功能,建立于AbstractSequentialList 中)。若非如此,就情愿选择ArrayList,它的速度可能要快一些。

Set 既可作为一个ArraySet 实现,亦可作为HashSet 实现。ArraySet 是由一个ArrayList后推得到的,设计成只支持少量元素,特别适合要求创建和删除大量Set 对象的场合使用。然而,一旦需要在自己的Set 中容纳大量元素,ArraySet 的性能就会大打折扣。写一个需要Set 的程序时,应默认选择HashSet。而且只有在某些特殊情况下(对性能的提升有迫切的需求),才应切换到ArraySet。

1     决定使用何种List

为体会各种List 实施方案间的差异,最简便的方法就是进行一次性能测验。下述代码的作用是建立一个内部基础类,将其作为一个测试床使用。然后为每次测验都创建一个匿名内部类。每个这样的内部类都由一个test()方法调用。利用这种方法,可以方便添加和删除测试项目。

1.1     代码

import java.util.*;

 

public
class
ListPerformance {

    private
staticfinalintREPS
= 100;

 

    private
abstractstaticclass
Tester {

        String name;

        int
size; // Test quantity

 

        Tester(String
name, int
size) {

            this.name =
name;

            this.size =
size;

        }

 

        abstract
void
test(List
a);

    }

 

    private
static
Tester[] tests= {
newTester("get",300) {

        void test(List
a) {

            for (int
i = 0; i <
REPS
;
i++) {

                for (int
j = 0; j <
a.size(); j++)

                    a.get(j);

            }

        }

    }, new Tester("iteration", 300) {

        void test(List
a) {

            for (int
i = 0; i <
REPS
;
i++) {

                Iterator
it = a.iterator();

                while (it.hasNext())

                    it.next();

            }

        }

    }, new Tester("insert", 1000) {

        void test(List
a) {

            int
half = a.size() / 2;

            String s =
"test";

            ListIterator
it = a.listIterator(half);

            for (int
i = 0; i <
size * 10; i++)

                it.add(s);

        }

    }, new Tester("remove", 5000) {

        void test(List
a) {

            ListIterator
it = a.listIterator(3);

            while (it.hasNext()) {

                it.next();

                it.remove();

            }

        }

    }, };

 

    public
staticvoid
test(List
a){

        // A trick to print out the class name:

        System.out.println("Testing " +
a.getClass().getName());

        for (int
i = 0; i <
tests.length;
i++) {

            Collection1.fill(a,
tests[i].size);

            System.out.print(tests[i].name);

            long
t1 = System.currentTimeMillis();

            tests[i].test(a);

            long
t2 = System.currentTimeMillis();

            System.out.println(": " + (t2 -
t1));

        }

    }

 

    public
staticvoid
main(String[]
args){

        test(new
ArrayList());

        test(new
LinkedList());

    }

} /// :~

1.2     执行结果

Testingjava.util.ArrayList

get:3

iteration:6

insert:6

remove:28

Testingjava.util.LinkedList

get:14

iteration:3

insert:2

remove:3

1.3     代码解释

内部类Tester 是一个抽象类,用于为特定的测试提供一个基础类。它包含了一个要在测试开始时打印的字串、一个用于计算测试次数或元素数量的size 参数、用于初始化字段的一个构建器以及一个抽象方法test()。test()做的是最实际的测试工作。各种类型的测试都集中到一个地方:tests 数组。我们用继承于Tester 的不同匿名内部类来初始化该数组。为添加或删除一个测试项目,只需在数组里简单地添加或移去一个内部类定义即可,其他所有工作都是自动进行的。首先用元素填充传递给test()的List,然后对tests数组中的测试计时。由于测试用机器的不同,结果当然也会有所区别。这个程序的宗旨是揭示出不同集合类型的相对性能比较。

在ArrayList 中进行随机访问(即get())以及循环反复是最划得来的;但对于LinkedList 却是

一个不小的开销。但另一方面,在列表中部进行插入和删除操作对于LinkedList来说却比ArrayList 划算得多。我们最好的做法也许是先选择一个ArrayList 作为自己的默认起点。以后若发现由于大量的插入和删除造成了性能的降低,再考虑换成LinkedList 不迟。

2     决定使用何种Set

可在ArraySet 以及HashSet 间作出选择,具体取决于Set 的大小

2.1     代码

import java.util.*;

public
class
SetPerformance {

    private
staticfinalintREPS
= 200;

    private
abstractstaticclass
Tester {

        String name;

        Tester(String
name) {

            this.name =
name;

        }

        abstract
void
test(Set
s, int
size);

    }

    private
static
Tester[] tests= {
newTester("add"){

        void test(Set
s, int
size) {

            for (int
i = 0; i <
REPS
;
i++) {

                s.clear();

                Collection1.fill(s,
size);

            }

        }

    }, new Tester("contains") {

        void test(Set
s, int
size) {

            for (int
i = 0; i <
REPS
;
i++)

                for (int
j = 0; j <
size; j++)

                    s.contains(Integer.toString(j));

        }

    }, new Tester("iteration") {

        void test(Set
s, int
size) {

            for (int
i = 0; i <
REPS
* 10;
i++) {

                Iterator
it = s.iterator();

                while (it.hasNext())

                    it.next();

            }

        }

    }, };

 

    public
staticvoid
test(Set
s,intsize){

        // A trick to print out the class name:

        System.out.println("Testing " +
s.getClass().getName() +" size "+
size);

        Collection1.fill(s,
size);

        for (int
i = 0; i <
tests.length;
i++) {

            System.out.print(tests[i].name);

            long
t1 = System.currentTimeMillis();

            tests[i].test(s,
size);

            long
t2 = System.currentTimeMillis();

            System.out.println(": " + ((double) (t2
- t1) / (double)
size));

        }

    }

 

    public
staticvoid
main(String[]
args){

        // Small:

        test(new
TreeSet(), 10);

        test(new
HashSet(), 10);

        // Medium:

        test(new
TreeSet(), 100);

        test(new
HashSet(), 100);

        // Large:

        test(new
HashSet(), 1000);

        test(new
TreeSet(), 1000);

    }

}/// :

2.2     执行如下

Testingjava.util.TreeSet size 10

add:0.5

contains:0.2

iteration:0.6

Testingjava.util.HashSet size 10

add:0.3

contains:0.1

iteration:1.0

Testingjava.util.TreeSet size 100

add:0.16

contains:0.1

iteration:0.11

Testingjava.util.HashSet size 100

add:0.15

contains:0.08

iteration:0.33

Testingjava.util.HashSet size 1000

add:0.057

contains:0.04

iteration:0.055

Testingjava.util.TreeSet size 1000

add:0.188

contains:0.05

iteration:0.024

2.3     代码解释

进行add()以及contains()操作时,HashSet 显然要比ArraySet 出色得多,而且性能明显与元素的多寡关系不大。一般编写程序的时候,几乎永远用不着使用ArraySet。

3     决定使用何种Map

选择不同的Map 实施方案时,注意Map 的大小对于性能的影响是最大的

3.1     代码如下

import java.util.*;

public
class
MapPerformance {

    private
staticfinalintREPS
= 200;

    public
static
Mapfill(Map
m,intsize){

        for (int
i = 0; i <
size; i++) {

            String x = Integer.toString(i);

            m.put(x,
x)
;

        }

        returnm;

    }

    private
abstractstaticclass
Tester {

        String name;

        Tester(String
name) {

            this.name =
name;

        }

        abstract
void
test(Map
m, int
size);

    }

    private
static
Tester[] tests= {
newTester("put"){

        void test(Map
m, int
size) {

            for (int
i = 0; i <
REPS
;
i++) {

                m.clear();

                fill(m,
size);

            }

        }

    }, new Tester("get") {

        void test(Map
m, int
size) {

            for (int
i = 0; i <
REPS
;
i++)

                for (int
j = 0; j <
size; j++)

                    m.get(Integer.toString(j));

        }

    }, new Tester("iteration") {

        void test(Map
m, int
size) {

            for (int
i = 0; i <
REPS
* 10;
i++) {

                Iterator
it = m.entrySet().iterator();

                while (it.hasNext())

                    it.next();

            }

        }

    }, };

    public
staticvoid
test(Map
m,intsize){

        // A trick to print out the class name:

        System.out.println("Testing " +
m.getClass().getName() +" size "+
size);

        fill(m,
size);

        for (int
i = 0; i <
tests.length;
i++) {

            System.out.print(tests[i].name);

            long
t1 = System.currentTimeMillis();

            tests[i].test(m,
size);

            long
t2 = System.currentTimeMillis();

            System.out.println(": " + ((double) (t2
- t1) / (double)
size));

        }

    }

    public
staticvoid
main(String[]
args){

        // Small:

        test(new
Hashtable(), 10);

        test(new
HashMap(), 10);

        test(new
TreeMap(), 10);

        // Medium:

        test(new
Hashtable(), 100);

        test(new
HashMap(), 100);

        test(new
TreeMap(), 100);

        // Large:

        test(new
HashMap(), 1000);

        test(new
Hashtable(), 1000);

        test(new
TreeMap(), 1000);

    }

}/// :~

3.2     执行如下:

Testingjava.util.Hashtable size 10

put:0.3

get:0.1

iteration:1.1

Testingjava.util.HashMap size 10

put:0.4

get:1.1

iteration:0.6

Testingjava.util.TreeMap size 10

put:0.8

get:0.4

iteration:0.9

Testingjava.util.Hashtable size 100

put:0.08

get:0.06

iteration:0.09

Testingjava.util.HashMap size 100

put:0.06

get:0.06

iteration:0.26

Testingjava.util.TreeMap size 100

put:0.15

get:0.09

iteration:0.09

Testingjava.util.HashMap size 1000

put:0.053

get:0.266

iteration:0.05

Testingjava.util.Hashtable size 1000

put:0.037

get:0.047

iteration:0.048

Testingjava.util.TreeMap size 1000

put:0.073

get:0.04

iteration:0.037

3.3     代码描述

由于Map 的大小是最严重的问题,所以程序的计时测试按Map 的大小(或容量)来分割时间,以便得到令人信服的测试结果。

即使大小为10,ArrayMap的性能也要比HashMap 差——除反复循环时以外。而在使用Map时,反复的作用通常并不重要(get()通常是我们时间花得最多的地方)。TreeMap 提供了出色的put()以及反复时间,但get()的性能并不佳。但是,为什么仍然需要使用TreeMap 呢?这样一来,我们可以不把它作为Map 使用,而作为创建顺序列表的一种途径。树的本质在于它总是顺序排列的,不必特别进行排序(它的排序方式马上就要讲到)。一旦填充了一个TreeMap,就可以调用keySet()来获得键的一个Set“景象”。然后用toArray()产生包含了那些键的一个数组。随后,可用static
方法Array.binarySearch()快速查找排好序的数组中的内容。当然,也许只有在HashMap 的行为不可接受的时候,才需要采用这种做法。因为HashMap 的设计宗旨就是进行快速的检索操作。最后,当我们使用Map 时,首要的选择应该是HashMap。只有在极少数情况下才需要考虑其他方法。

3.4     代码2

import java.util.*;

public
class
MapCreation {

    public
staticvoid
main(String[]
args){

        final
long
REPS = 100000;

        long
t1 = System.currentTimeMillis();

        System.out.print("Hashtable");

        for (long
i = 0; i <
REPS; i++)

            new
Hashtable();

        long
t2 = System.currentTimeMillis();

        System.out.println(": " + (t2 -
t1));

        t1 = System.currentTimeMillis();

        System.out.print("TreeMap");

        for (long
i = 0; i <
REPS; i++)

            new
TreeMap();

        t2 = System.currentTimeMillis();

        System.out.println(": " + (t2 -
t1));

        t1 = System.currentTimeMillis();

        System.out.print("HashMap");

        for (long
i = 0; i <
REPS; i++)

            new
HashMap();

        t2 = System.currentTimeMillis();

        System.out.println(": " + (t2 -
t1));

    }

}/// :~

3.5     执行结果

Hashtable:17

TreeMap:4

HashMap:7

3.6     代码解释

TreeMap 的创建速度比其他两种类型明显快得多。考虑到这方面的原因,同时由于前述TreeMap 出色的put()性能,所以如果需要创建大量Map,而且只有在以后才需要涉及大量检索操作,那么最佳的策略就是:创建和填充TreeMap;以后检索量增大的时候,再将重要的TreeMap 转换成HashMap——使用HashMap(Map)构建器。同样地,只有在事实证明确实存在性能瓶颈后,才应关心这些方面的问题——先用起来,再根据需要加快速度。

 

 

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: