数组

第16章 数组

在之前的学习中,我们学过了对数组基本运用:创建并初始化一个数组,通过使用整型索引值访问它们的元素,并且数组一旦创建,尺寸将无法改变。本章将更加深入地思考数组。

16.1 数组为什么特殊

数组可以持有某种具体类型和任意基本类型。而在泛型和自动包装机制之前之前,容器类在处理对象时,都将它们视为Object处理,并且无法持有基本类型。

在有了泛型和自动包装机制后,数组与其他种类的容器之间本质的优势在于效率:在Java中,数组是一种效率最高的存储和随机访问对象引用序列的方式。数组就是一个简单的线性序列,这使得元素访问非常迅速。但其缺点是数组大小在其初始化时就被固定,并无法改变。 ArrayList通过创建一个新实例,然后将旧实例中所有引用移到新实例中,从而实现更多空间的自动分配,这也大大增加了开销。因此ArrayList的效率比数组低很多。

下面是将数组与泛型容器进行比较的示例:

class BerylliumSphere {
    private static long counter;
    private final long id = counter++;
    public String toString() { return "Sphere " + id; }
}

public class ContainerComparison {
    public static void main(String[] args) {
        BerylliumSphere[] spheres = new BerylliumSphere[10];
        for (int i = 0; i < 5; i++) 
            spheres[i] = new BerylliumSphere();
        print(Arrays.toString(spheres));
        print(spheres[4]);
        
        List<BerylliumSphere>  sphereList = new ArrayList<BerylliumSphere>();
        for (int i = 0; i < 5; i++) 
            sphereList.add(new BerylliumSphere());
        print(sphereList);
        print(sphereList.get(4));
            
        int[] integers = { 0,1,2,3,4,5 };
        print(Arrays.toString(integers));
        print(integers[4]);
        
        List<Integer> intList = new ArrayList<Integer>(Arrays.asList(0,1,2,3,4,5));
        intList.add(66);
        print(intList);
        print(intList.get(4));
    }
}

随着泛型的出现,容器类和数组都是类型检查型的了。唯一明显的差异就是访问元素的方式有所不同。并且,随着自动包装机制的出现,数组仅存的优点就是效率了。但容器比数组明显具有更多功能。

16.2 数组是第一级对象

对象数组和基本类型数组在使用上几乎相同,唯一的区别就是对象数组保存的是引用,基本类型数组直接保存基本类型的值:

public class ArraysOptions {
    public static void main(String[] args) {
        BerylliumSphere[] a;
        BerylliumSphere[] b = new BerylliumSphere[5];
        print("b: " + Arrays.toString(b));
        BerylliumSphere[] c = new BerylliumSphere[4];
        for (int i = 0; i < c.length; i++) 
            if(c[i] == null)
                c[i] = new BerylliumSphere();
        BerylliumSphere[] d = {
                new BerylliumSphere(),new BerylliumSphere(),new BerylliumSphere()
        };
        a = new BerylliumSphere[] {
                new BerylliumSphere(),new BerylliumSphere(),new BerylliumSphere()
        };
        print("a.length = " + a.length);
        print("b.length = " + b.length);
        print("c.length = " + c.length);
        print("d.length = " + d.length);
        a = d; 
        print("a.length = " + a.length);
        
        int[] e;
        int[] f = new int[5];
        print("f: " + Arrays.toString(f));
        int[] g = new int[4];
        for (int i = 0; i < g.length; i++) 
            g[i] = i*i;
        int[] h = { 11,66,99 };
        print("f.length = " + f.length);
        print("g.length = " + g.length);
        print("h.length = " + h.length);
        e = h ;
        print("e.length = " + e.length);
        e = new int[]{ 1, 2 };
        print("e.length = " + e.length);
    }
}

可以看到,length表示的是数组能够容纳元素的个数,而不是实际保存的元素个数。新生成一个对象数组时,其中所有的引用被初始化为null;而基本类型的数组会根据具体类型具有不同初始化值:数值型0,字符型char(0),布尔型false。

16.3 返回一个数组

如果希望一个方法的返回值是一组值,则可以直接返回一个数组,并且无需担心数组的生命周期,只要需要它,它就会一直存在,当使用完后,垃圾回收器会清理掉它:

public class IceCream {
    private static Random rand = new Random(66);
    static final String[] FLAVORS = {
        "Chocolate","Strawberry","Vanilla Fudge Swirl",
        "Mint Chip","Mocha Almond Fudge","Rum Raisin",
        "Praline Cream","Mud Pie"
    };
    public static String[] flavorSet(int n) {
        if( n > FLAVORS.length)
            throw new IllegalArgumentException("Set too big");
        String[] results = new String[n];
        boolean[] picked = new boolean[FLAVORS.length];
        for (int i = 0; i < n; i++) {
            int t;
            do
                t = rand.nextInt(FLAVORS.length);
            while(picked[t]);
            results[i] = FLAVORS[t];
            picked[t] = true;
        }
        return results;
    }
    public static void main(String[] args) {
        for (int i = 0; i < 7; i++) 
            System.out.println(Arrays.toString(flavorSet(3)));
    }
}

16.4 多维数组

创建多维数组,可以通过使用花括号将每个向量分隔开,每对花括号括起来的集合都会把你带到下一级数组:

public class MultidimensionalPrimitiveArray {
    public static void main(String[] args) {
        int[][] a = {
            { 1, 2, 3},
            { 4, 5, 6}
        };
        System.out.println(Arrays.deepToString(a));
    }
}

Java SE5的Arrays.deepToString()方法,可以将多维数组转换为多个String。并且,我们还可以使用new来分配数组:

public class ThreeDWithNew {
    public static void main(String[] args) {
        int[][][] a = new int[2][2][4];
        System.out.println(Arrays.deepToString(a));
    }
}

基本类型数组的值在不进行显式初始化的情况下,会被自动初始化为该类型的0值。对象数组会被初始化为null。

粗糙数组:数组中构成矩阵的每个向量都可以具有任意的长度。

public class RaggedArray {
    public static void main(String[] args) {
        Random rand = new Random(66);
        int[][][] a = new int[rand.nextInt(7)][][];
        for (int i = 0; i < a.length; i++) {
            a[i] = new int[rand.nextInt(5)][];
            for (int j = 0; j < a[i].length; j++) 
                a[i][j] = new int[rand.nextInt(5)];
        }
        System.out.println(Arrays.deepToString(a));
    }
}

对于对象数组,仍然可以使用花括号进行创建,并且可以声明为粗糙数组:

public class MulitidimensionalObjectArrays {
    public static void main(String[] args) {
        BerylliumSphere[][] spheres = {
            { new BerylliumSphere(), new BerylliumSphere() },
            { new BerylliumSphere(), new BerylliumSphere(),
              new BerylliumSphere(), new BerylliumSphere() },
            { new BerylliumSphere(), new BerylliumSphere(),
              new BerylliumSphere(), new BerylliumSphere(),
              new BerylliumSphere(), new BerylliumSphere() }
        };
        System.out.println(Arrays.deepToString(spheres));
    }
}

自动包装机制对数组初始化器也起作用:

public class AutoboxingArrays {
    public static void main(String[] args) {
        Integer[][] a = {
                { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 },
                { 21, 22, 23, 24, 25, 26, 27, 28, 29, 30 },
                { 52, 52, 53, 54, 55, 56, 57, 58, 59, 60 }
        };
        System.out.println(Arrays.deepToString(a));
    }
}

下面的示例展示了如何逐个地、部分地构建一个对象数组:

public class AssemblingMultidimensionalArrays {
    public static void main(String[] args) {
        Integer[][] a;
        a = new Integer[3][];
        for (int i = 0; i < a.length; i++) {
            a[i] = new Integer[3];
            for (int j = 0; j < a[i].length; j++) 
                a[i][j] = i * j;
        }
        System.out.println(Arrays.deepToString(a));
    }
}

16.5 数组与泛型

泛型的擦除会移除参数的类型信息,而数组必须知道它们所持有的确切类型,以强制保证类型安全。所以无法直接实例化具有参数化类型的数组。但是可以参数化数组本身的类型:

class ClassParameter<T> {
    public T[] f(T[] arg) { return arg;}
}
class MethodParameter {
    public static <T> T[] f(T[] arg) { return arg; }
}

public class ParameterizedArrayType {
    public static void main(String[] args) {
        Integer[] ints = { 1, 2, 3, 4, 5 };
        Double[] doubles = { 1.1, 2.2, 3.3, 4.4, 5.5 };
        Integer[] ints2 = new ClassParameter<Integer>().f(ints);
        Double[] doubles2 = new ClassParameter<Double>().f(doubles);
        ints2 = MethodParameter.f(ints);
        doubles2 = MethodParameter.f(doubles2);
    }
}

虽然无法实例化泛型数组,但可以创建该类数组的引用,然后将非泛型的数组进行转型:

public class ArrayOfGenerics {
    public static void main(String[] args) {
        List<String>[] ls;
        List[] la = new List[10];
        ls = (List<String>[])la;
        ls[0] = new ArrayList<String>();
        Object[] objects = ls;
        objects[1] = new ArrayList<Integer>();
        List<BerylliumSphere>[] spheres = (List<BerylliumSphere>[])new List[10];
        for (int i = 0; i < spheres.length; i++) 
            spheres[i] = new ArrayList<BerylliumSphere>();
    }
}

一般情况下,泛型在类或方法的边界处很有效,但在类或方法的内部,擦除通常会使泛型变得不适用。例如,无法创建泛型数组:

public class ArrayOfGenericType<T> {
    T[] array;
    public ArrayOfGenericType(int size) { 
        //! array = new T[size];
        array = (T[]) new Object[size];
    }
}

擦除使得该数组的类型是未知的,使得我们无法创建泛型数组,只能创建Object数组。

16.6 创建测试数据

本节将介绍使用工具通过数值或对象来填充数组。

16.6.1 Arrays.fill()

Java标准类库中的Arrays.fill()方法:使用一个值填充数组,对于对象而言,就是复制同一个引用进行填充。其重载方法可以设置填充区域的起始索引和结束索引

public class FillingArrays {
    public static void main(String[] args) {
        int size = 6;
        boolean[] a1 = new boolean[size];
        byte[] a2 = new byte[size];
        String[] a3 = new String[size];
        
        Arrays.fill(a1, true);
        Arrays.fill(a2, (byte)11);
        Arrays.fill(a3, "Hello");
        Arrays.fill(a3, 3, 5, "World");
        
        System.out.println("a1 = " + Arrays.toString(a1));
        System.out.println("a2 = " + Arrays.toString(a2));
        System.out.println("a3 = " + Arrays.toString(a3));
        System.out.println("a3 = " + Arrays.toString(a3));
    }
}

16.6.2 数据生成器

我们在第15章中引入了Generator概念,下面,我们以内部类的形式,实现了所有基本类型的包装器类型,以及String类型的Generator。从而构建了一组最基本的计数生成器

public class CountingGenerator {
    public static class Boolean implements Generator<java.lang.Boolean> {
        private boolean value = false;
        public java.lang.Boolean next() {
            value = !value;
            return value;
        }
    }
    
    public static class Byte implements Generator<java.lang.Byte> {
        private byte value = 0;
        public java.lang.Byte next() { return value++; }
    }
    
    static char[] chars = ("abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ").toCharArray();
    public static class Character implements Generator<java.lang.Character> {
        private int index = -1;
        public java.lang.Character next() { 
            index = (index + 1) % chars.length;
            return chars[index];
        }
    } 
    
    public static class String implements Generator<java.lang.String> {
        private int length = 7;
        Generator<java.lang.Character> cg = new Character();
        public String() {}
        public String(int length) { this.length = length; }
        public java.lang.String next() {
            char[] buf = new char[length];
            for (int i = 0; i < buf.length; i++) 
                buf[i] = cg.next();
            return  new java.lang.String(buf);
        }
    }
    
    public static class Short implements Generator<java.lang.Short> {
        private short value = 0;
        public java.lang.Short next() { return value++; }
    }
    
    public static class Integer implements Generator<java.lang.Integer> {
        private int value = 0;
        public java.lang.Integer next() { return value++; }
    }
    
    public static class Long implements Generator<java.lang.Long> {
        private long value = 0;
        public java.lang.Long next() { return value++; }
    }
    
    public static class Float implements Generator<java.lang.Float> {
        private float value = 0;
        public java.lang.Float next() {
            float result = value;
            value +=1.0;
            return result;
        }
    }
    
    public static class Double implements Generator<java.lang.Double> {
        private double value = 0.0;
        public java.lang.Double next() {
            double result = value;
            value +=1.0;
            return result;
        }
    }
}

下面是通过反射测试上例:

public class GeneratorTest {
    public static int size = 10;
    public static void test(Class<?> surroundingClass) { 
        for (Class<?> type : surroundingClass.getClasses()) {
            System.out.print(type.getSimpleName() + ": ");
            try {
                Generator<?> g = (Generator<?>) type.newInstance();
                for (int i = 0; i < size; i++) 
                    System.out.printf(g.next() + " ");
                System.out.println();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }
    public static void main(String[] args) {
        test(CountingGenerator.class);
    }
}

下面是一组随机数生成器:

public class RandomGenerator {
    private static Random rand = new Random(66);
    
    public static class Boolean implements Generator<java.lang.Boolean> {
        public java.lang.Boolean next() {
            return rand.nextBoolean();
        }
    }
    
    public static class Byte implements Generator<java.lang.Byte> {
        public java.lang.Byte next() {
            return (byte) rand.nextInt();
        }
    }

    public static class Character implements Generator<java.lang.Character> {
        public java.lang.Character next() {
            return CountingGenerator.chars[rand.nextInt(CountingGenerator.chars.length)];
        }
    }

    public static class String extends CountingGenerator.String {
            {
                cg = new Character();
            }
        public String(int length) { super(length); }
    }

    public static class Integer implements Generator<java.lang.Integer> {
        private int mod = 10000;
        public Integer(int modulo) { mod = modulo; } 
        public java.lang.Integer next() { return rand.nextInt(mod); }
    }
    
    public static class Long implements Generator<java.lang.Long> {
        private int mod = 10000;
        public Long(int modulo) { mod = modulo; } 
        public java.lang.Long next() { return new java.lang.Long(rand.nextInt(mod)); }
    }
    
    public static class Float implements Generator<java.lang.Float> {
        public java.lang.Float next() { 
                int trimmed = Math.round(rand.nextFloat() * 100);
                return ((float) trimmed) /100 ;
            }
    }
    
    public static class Double implements Generator<java.lang.Double> {
        public java.lang.Double next() { 
                long trimmed = Math.round(rand.nextDouble() * 100);
                return ((double) trimmed) /100 ;
            }
    }
}

通过复用GeneratorTest来测试RandomGenerator:

public class RandomGeneratorsTest {
    public static void main(String[] args) {
        GeneratorTest.test(RandomGenerator.class);
    }
}

16.6.3 从Generator中创建数组

为了接收Generator并产生数组,我们需要两个转换工具:

  • 使用任意的Generator来产生Object子类型的数组。
  • 接收任意基本类型的包装器类型数组,并产生相应的基本类型数组。
public class Generated {
    public static<T> T[] array(T[] a,Generator<T> gen) {
        return new CollectionData<T>(gen, a.length).toArray(a);
    }
    
    public static <T> T[] array(Class<T> type,Generator<T> gen,int size) {
        T[] a = (T[]) java.lang.reflect.Array.newInstance(type, size);
        return new CollectionData<T>(gen, size).toArray(a);
    }
}

上述代码中的CollectionData类在第17章中定义:它创建一个Collection对象,并通过Generator填充该集合。下面是通过前一节所定义的计数生成器集合CountingGenerator中的某个生成器来测试Generated:

public class TestGenerated {
    public static void main(String[] args) {
        Integer[] a = { 9, 8, 7, 6 };
        System.out.println(Arrays.toString(a));
        a = Generated.array(a, new CountingGenerator.Integer());
        System.out.println(Arrays.toString(a));
        Integer[] b = Generated.array(Integer.class, new CountingGenerator.Integer(), 15);
        System.out.println(Arrays.toString(b));
    }
}

由于泛型无法应用于基本类型,我们如果希望生成器来填充基本类型数组,则需要创建一个转换器,它接收任意的包装器对象数组,并将其转换为相应的基本类型数组:

public class ConvertTo {
    public static boolean[] primitive(Boolean[] in) {
        boolean[] result = new boolean[in.length];
        for (int i = 0; i < result.length; i++) 
            result[i] = in[i];
        return result;
    }
    public static char[] primitive(Character[] in) {
        char[] result = new char[in.length];
        for (int i = 0; i < result.length; i++) 
            result[i] = in[i];
        return result;
    }
    public static byte[] primitive(Byte[] in) {
        byte[] result = new byte[in.length];
        for (int i = 0; i < result.length; i++) 
            result[i] = in[i];
        return result;
    }
    public static short[] primitive(Short[] in) {
        short[] result = new short[in.length];
        for (int i = 0; i < result.length; i++) 
            result[i] = in[i];
        return result;
    }
    public static int[] primitive(Integer[] in) {
        int[] result = new int[in.length];
        for (int i = 0; i < result.length; i++) 
            result[i] = in[i];
        return result;
    }
    public static long[] primitive(Long[] in) {
        long[] result = new long[in.length];
        for (int i = 0; i < result.length; i++) 
            result[i] = in[i];
        return result;
    }
    public static float[] primitive(Float[] in) {
        float[] result = new float[in.length];
        for (int i = 0; i < result.length; i++) 
            result[i] = in[i];
        return result;
    }
    public static double[] primitive(Double[] in) {
        double[] result = new double[in.length];
        for (int i = 0; i < result.length; i++) 
            result[i] = in[i];
        return result;
    }
}

下面的示例展示了如何将ConvertTo应用于两个版本的Generated.array()上:

public class PrimitiveConversionDemonstration {
    public static void main(String[] args) {
        Integer[] a = Generated.array(Integer.class, new CountingGenerator.Integer(),15);
        int[] b = ConvertTo.primitive(a);
        System.out.println(Arrays.toString(b));
        boolean[] c = ConvertTo.primitive(Generated.array(Boolean.class, new CountingGenerator.Boolean(),7));
        System.out.println(Arrays.toString(c));
    }
}

最后,我们使用随机数生成器集合RandomGenerator中的类来进行测试:

public class TestArrayGeneration {
    public static void main(String[] args) {
        int size = 6;
        boolean[] a1 = ConvertTo.primitive(Generated.array(Boolean.class,new RandomGenerator.Boolean(),size));
        System.out.println("a1 = " + Arrays.toString(a1));
        byte[] a2 = ConvertTo.primitive(Generated.array(Byte.class,new RandomGenerator.Byte(),size));
        System.out.println("a2 = " + Arrays.toString(a2));
        double[] a3 = ConvertTo.primitive(Generated.array(Double.class,new RandomGenerator.Double(),size));
        System.out.println("a3 = " + Arrays.toString(a3));
    }
}

16.7 Arrays实用功能

Arrays位于java.util的类库中,它有一套用于数组的静态方法,其中有6个基本方法:

  • equals():比较两个数组是否相等。
  • fill():使用一个值填充数组。
  • sort():对数组进行排序。
  • binarySearch():在已经排序的数组中查找元素。
  • toString():将数组用字符串表示。
  • hashCode():产生数组的散列码。(在第17章中学习)

此外,在第11章中,我们使用过Arrays.asList():接收任意的序列或数组作为参数,并将其转变为List容器。

16.7.1 复制数组

Java标准类库的静态方法System.arraycopy():用于数组的复制,其效率比使用for循环遍历复制高很多,并且针对所有类型做了重载。 下面是用来处理int数组的示例:

public class CopyArrays {
    public static void main(String[] args) {
        int[] i = new int[7];
        int[] j = new int[10];
        Arrays.fill(i, 66);
        Arrays.fill(j, 99);
        System.out.println("i = " + Arrays.toString(i));
        System.out.println("j = " + Arrays.toString(j));
        System.arraycopy(i, 0, j, 0, i.length);
        System.out.println("j = " + Arrays.toString(j));
        int[] k = new int[5];
        Arrays.fill(k, 13);
        System.out.println("k = " + Arrays.toString(k));
        System.arraycopy(i, 0, k, 0, k.length);
        System.out.println("k = " + Arrays.toString(k));
    }
}

该方法有5个参数:

  • Object src:源数组。
  • int srcPos:从源数组中开始复制的索引。
  • Object dest:目标数组。
  • int destPos:从目标数组中开始复制的索引。
  • int length:需要复制的目标个数。

16.7.2 数组的比较

Array.equals():用于比较整个数组。数组相等的条件为:元素个数相等,且对应位置元素相等。对于基本类型的比较使用操作符,对于对象的比较使用equals()方法:

public class ComparingArrays {
    public static void main(String[] args) {
        int[] a1 = new int[10];
        int[] a2 = new int[10];
        Arrays.fill(a1, 66);
        Arrays.fill(a2, 66);
        System.out.println(Arrays.equals(a1, a2));
        a2[3] = 11;
        System.out.println(Arrays.equals(a1, a2));
        Integer as = 19;
    }
}

16.7.3 数组元素的比较

排序必须根据对象的实际类型执行比较操作。天然的解决方案是为每种不同的类型各编写一个不同的排序方法,但这样的代码将难以被新的类型复用。

而使用策略设计模式,我们可以将保持不变的事物与会发生变化的事物相分离。该情况下,不变的是通用的排序算法,变化的是各种对象相互比较的方式。因此,我们可以使用策略设计模式:使用不同的对象来表示不同的比较方式,然后将它们传递给相同的排序代码。

Java提供了两个接口来提供比较功能:

  • Comparable:用于比较的类型可直接实现该接口,重写比较的方式。
  • Comparator:实现该接口的类仅用于表示一种比较方式,可用于策略设计模式。

下面的类实现了Comparable接口,并使用Arrays.sort()方法演示了比较的效果:

public class CompType implements Comparable<CompType> {
    int i;
    int j;
    private static int count = 1;
    public CompType(int n1,int n2) {
        i = n1;
        j = n2;
    }
    
    public String toString() {
        String result = "[i=" + i + ", j=" + j + "]";
        if(count++ % 3 == 0)
            result += "\n";
        return result;
    }
    
    public int compareTo(CompType rv) {
        return (i < rv.i ? -1 : (i == rv.i ? 0 : 1));
    }
    
    private static Random rand = new Random(66);
    public static Generator<CompType> generator() {
        return new Generator<CompType>() {
            public CompType next() {
                return new CompType(rand.nextInt(100), rand.nextInt(100));
            }
        };
    }

    public static void main(String[] args) {
        CompType[] a = Generated.array(new CompType[12],generator());
        System.out.println("before sorting:");
        System.out.println(Arrays.toString(a));
        Arrays.sort(a);
        System.out.println("after sorting:");
        System.out.println(Arrays.toString(a));
    }
}

如果我们希望一个单独的类仅存在比较功能,则可以将该类实现Comparator接口,然后通过策略设计模式应用该类。

Collections类包含一个reverseOrder()方法,该方法产生一个Comparator:

public class Reverse {
    public static void main(String[] args) {
        CompType[] a = Generated.array(new CompType[12], CompType.generator());
        System.out.println("before sorting");
        System.out.println(Arrays.toString(a));
        Arrays.sort(a, Collections.reverseOrder());
        System.out.println("after sorting");
        System.out.println(Arrays.toString(a));
    }
}

下面,我们通过自己编写Comparator来进行测试:

class CompTypeComparator implements Comparator<CompType> {
    public int compare(CompType o1, CompType o2) {
        return (o1.j < o2.j ? -1 : (o1.j == o2.j ? 0 : 1));
    }
}
public class ComparatorTest {
    public static void main(String[] args) {
        CompType[] a = Generated.array(new CompType[12], CompType.generator());
        System.out.println("before sorting");
        System.out.println(Arrays.toString(a));
        Arrays.sort(a, new CompTypeComparator());
        System.out.println("after sorting");
        System.out.println(Arrays.toString(a));
    }
}

16.7.4 数组排序

Arrays.sort():可用于任何基本类型数组和对象数组的排序。需要该对象实现了Comparable接口或具有相关联的Comparator。

下面的例子对随机生成的String数组进行排序,String类实现了Comparable接口:

public class StringSorting {
    public static void main(String[] args) {
        String[] sa = Generated.array(new String[20], new RandomGenerator.String(5));
        System.out.println("Before sort: " + Arrays.toString(sa));
        Arrays.sort(sa);
        System.out.println("After sort: " + Arrays.toString(sa));
        Arrays.sort(sa, Collections.reverseOrder());
        System.out.println("Reverse sort: " + Arrays.toString(sa));
        Arrays.sort(sa, String.CASE_INSENSITIVE_ORDER);
        System.out.println("Case-insensitive sort: " + Arrays.toString(sa));
    }
}

String类默认的排序算法是依据词典排序顺序,即大写字母开头的词排在前面。而String.CASEINSENSITIVEORDER则会忽略大小写字母。

java标准类库中的排序算法针对正排序的特殊类型进行了优化:

  • 针对基本类型设计的快速排序。
  • 针对对象设计的稳定归并排序。

16.7.5 在已排序的数组中查找

在排序后的数组中,可以使用Array.binarySearch()执行快速查找。而将该方法作用于未排序的数组,则会产生不可预料的结果。

下面的对一个由随机数生成器填充的int数组查询由该生成器生成的随机数:

public class ArraySearching {
    public static void main(String[] args) {
        Generator<Integer> gen = new RandomGenerator.Integer(1000);
        int[] a = ConvertTo.primitive(Generated.array(new Integer[25], gen));
        Arrays.sort(a);
        System.out.println("Sorted array: " + Arrays.toString(a));
        while(true) {
            int rand = gen.next();
            int location = Arrays.binarySearch(a, rand);
            if(location > 0) {
                System.out.println("Location of " + rand + " is " + location + 
                        ", a[" + location + "] = " + a[location]);
                break;
            }
        }
    }
}

如果找到目标,Arrays.binarySearch()产生的返回值大于或等于0。否则,它产生一个负返回值:表示若要保持数组的排序状态,此目标元素应该插入的位置。其计算方式为:

-(插入点) -1

插入点是指:第一个大于查找对象的元素在数组中的位置。如果数组中所有元素都小于要查找的对象,插入点就等于array.size()。

如果使用Comparator排序了某个对象数组,则在使用binarySearch()时必须提供同样的Comparator。例如:

public class AlphabeticSearch {
    public static void main(String[] args) {
        String[] sa = Generated.array(new String[30], new RandomGenerator.String(5));
        Arrays.sort(sa, String.CASE_INSENSITIVE_ORDER);
        System.out.println(Arrays.toString(sa));
        int index = Arrays.binarySearch(sa, sa[10],String.CASE_INSENSITIVE_ORDER);
        System.out.println("Index: " + index + "\n" + sa[index]);
    }
}

16.8 总结

Java中的数组,更多强调的是性能而不是灵活性。并且随着自动包装机制和泛型的加入,数组除了性能已经毫无优势了。

在大多数情况下,我们应该优先选用容器,只有在已证明性能成为问题时,才应该将程序重构为使用数组。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值