天天看点

J2SE(1)之数组、String和StringBuilder1、数组2、String类3、StringBuilder与StringBuffer

1、数组

1.1、数组概述

数组是相同类型数据的有序集合。数组描述的是相同类型的若干个数据,按照一定的先后次序排列组合而成。其中,每一个数据称作一个数组元素,每个数组元素可以通过一个下标来访问它们。

1.2、数组的四个基本特点

    1.    其长度是确定的。数组一旦被创建,它的大小就是不可以改变的。 

    2.    其元素必须是相同类型,不允许出现混合类型。

    3.    数组中的元素可以是任何数据类型,包括基本类型和引用类型。

    4.    数组变量属引用类型,数组也可以看成是对象,数组中的每个元素相当于该对象的成员变量。数组本身就是对象,Java中对象是在堆中的,因此数组无论保存原始类型还是其他对象类型,数组对象本身是在堆中的。

1.3、声明数组和初始化

1.3.1  声明

一维数组的声明方式有两种:

type[]   arr_name;(推荐使用这中方式)

type   arr_name[]; 

注:

    1.    声明的时候并没有实例化任何对象,只有在实例化数组对象时,JVM才分配空间,这时才与长度有关。因此,声明数组时不能指定其长度(数组中元素的个数),例如: int a[5]; //非法

    2.    声明一个数组的时候并没有数组被真正的创建。

    3.    构造一个数组,必须指定长度。

1.3.2 初始化

1.3.2.1 静态初始化

除了用new关键字来产生数组以外,还可以直接在定义数组的同时就为数组元素分配空间并赋值。

int[] a = {1,2,3};//基本数据类型数组
Man[] mans = {//引用数据类型数组
   new Man(1,1),
   new Man(2,2)
};
           

1.3.2.2 动态初始化

数组定义与为数组元素分配空间并赋值的操作分开进行。

int[] a = new int[2];
a[0]=1;
a[1]=2;
           

1.3.2.3 数组的默认初始化

数组是引用类型,它的元素相当于类的实例变量,因此数组一经分配空间,其中的每个元素也被按照实例变量同样的方式被隐式初始化。

int a[] = new int[2];  //0,0
boolean [] b = new boolean[2];  //false,false
String[] s = new String[2];     //null, null
           

1.4 数组的拷贝

       System类里也包含了一个static void arraycopy(object src,int srcpos,object dest, int destpos,int length)方法,该方法可以将src数组里的元素值赋给dest数组的元素,其中srcpos指定从src数组的第几个元素开始赋值,length参数指定将src数组的多少个元素赋给dest数组的元素。

1.5 数组的打印

Arrays里面提供了一个方法toString,可以打印数组信息:

int[] a = {1,2};
System.out.println(a);   //[[email protected]
System.out.println(Arrays.toString(a));  //[1, 2]
           

1.6 数组的排序

Arrays里面提供了一个方法sort,可以把数组进行排序。

int[] a = {1,2,323,23,543,12,59};
System.out.println(Arrays.toString(a));
Arrays.sort(a);
System.out.println(Arrays.toString(a));
结果:
[1, 2, 323, 23, 543, 12, 59]
[1, 2, 12, 23, 59, 323, 543]
           

2、String类

String 类对象保存不可修改的Unicode字符序列 ,部分String的源码如下:

public final class String  {
    /** The value is used for character storage. */
    private final char value[];
    public String() {
        this.value = new char[0];
    }
    public String(String original) {
        this.value = original.value;
    }
    public String(char value[]) {
        this.value = Arrays.copyOf(value, value.length);
    }
}
           

从源码可以看出:

1、String类是被final修饰的,所以它不可以被继承;

2、String类其实维护的是一个字符数组 private final char value[];,并且也是有final修饰的,所以不可改变;

3、StringBuilder与StringBuffer

StringBuilder可变字符串,线程不太安全,但效率高,推荐使用。

StringBuffer可变字符串,线程安全,但效率低,不推荐使用。

String 是不可变字符串,根据实时情况使用,在循环当中不要使用,因为会产生好多没用的字符串。

3.1 StringBuilder扩容原理

我们都一直说的是String是不可变的Unicode字符序列,而StringBuilder是可变的字符序列,那么它到底为什么是可变的呢?查看StringBuilder的源码:

public final class StringBuilder extends AbstractStringBuilder implements java.io.Serializable, CharSequence
{
    public StringBuilder() {
        super(16);
    }

    public StringBuilder(int capacity) {
        super(capacity);
    }

    public StringBuilder(String str) {
        super(str.length() + 16);
        append(str);
    }
}
           

从构造方法可以看出,都是调用的父类构造器进行初始化,那么我们看看父类:AbstractStringBuilder 

abstract class AbstractStringBuilder implements Appendable, CharSequence {

    char[] value;

    int count;

    AbstractStringBuilder() {
    }

    AbstractStringBuilder(int capacity) {
        value = new char[capacity];
    }
}
           

可以看出里面也是维护的一个字符出租char[] values;,没有用final修饰,也就是说它可以被修改的。

那么它是怎么扩容的呢?

看看StringBuilder的append方法:

@Override
    public StringBuilder append(String str) {
        super.append(str);
        return this;
    }
           

当调用StringBuilder的append方法,进行字符串的追加的时候,发现也是调用的父类方法append方法,如下:

AbstractStringBuilder的append方法:
           
public AbstractStringBuilder append(String str) {
        if (str == null)
            return appendNull();
        int len = str.length();
        ensureCapacityInternal(count + len);//把添加的字符串长度和初始化StringBuilder的长度传入
        str.getChars(0, len, value, count);//把str放到value中
        count += len;
        return this;
    }
    
    private void ensureCapacityInternal(int minimumCapacity) {
        // overflow-conscious code
        if (minimumCapacity - value.length > 0) {
            //如果传入的长度大于 字符数组value的长度,那么就调用Arrays的copyOf进行扩容,然后把value指向新的字符数组
            value = Arrays.copyOf(value,
                    newCapacity(minimumCapacity));
        }
    }

    //新数组的容量计算,把原来的长度乘以2再加上2得到新数据的长度
    private int newCapacity(int minCapacity) {
        // overflow-conscious code
        int newCapacity = (value.length << 1) + 2;
        if (newCapacity - minCapacity < 0) {
            newCapacity = minCapacity;
        }
        return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
            ? hugeCapacity(minCapacity)
            : newCapacity;
    }
           

Arrays.copyOf如下:

public static char[] copyOf(char[] original, int newLength) {
        char[] copy = new char[newLength];
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;//返回一个新的数组
    }
           

所以,当我们append方法里面传入的字符串加上之前已经有的字符串长度大于初始化长度的时候,就会按照乘以2+2的方式进行长度的扩容,然后把老数组的值拷贝到新数组,并最终改变老数组value的指向,指向的是新数组。这就是扩容原理。具体的举例如下:

//从StringBuilder的构造方法中可以知道,初始化的字符数组value的长度是16
StringBuilder sb = new StringBuilder();//初始化长度是16
sb.append("1234567890");//添加了长度为10的字符串
sb.append("abcdefghij");//当再次往里面添加长度为10的字符串的时候,因为刚才的长度已经有10,那么此时传入父类的append的方法的长度是10+10=20,
//此时已经大于16了,所以会进行扩容,16*2+2 = 34,所以新数组的长度是34,会把旧数组的数据拷贝到新数组中,然后sb对象的value指向新的数组。
           

3.2 StringBuffer

 StringBuffer可变字符串,线程安全,但效率低,不推荐使用。