数组和字符串
一. 数组
1. 数组声明
-
一个数组是相同数据类型的元素按一定顺序排序的集合
-
数组中各元素的类型相同,通过下标来访问数组中的元素,下标都可以从0开始
-
在Java中,数组是对象。
-
数组元素可以是基本数据类型,也可以是类类型和接口类型,还可以是数组
-
数组在使用之前必须先声明
-
// 类型 数组名[] int intArray[]; // 基本数据类型 Date dateArray[]; // 类类型
- 类型是数组元素的类型
- 数组名为合法的标识符
- [ ]指明定义的是一个数组类型变量
-
-
在数组定义时并不会为数组分配内存,因此方括号 [ ] 中不需要指出数组元素的个数,即数组长度
-
还可以使用另外一种格式声明数组
-
// 类型[] 数组名 int[] num; Date[] dateArray; int intArray[5]; // java中没有静态的数组定义,这种写法是错误的
-
2. 创建数组
-
初始化过程
-
初始化过程就是数组的创建过程
-
数组声明只是定义了一个数组引用,并没有为数组分配任何内存
-
必须经过数组初始化,才能使用数组的元素
-
-
数组的初始化分为两种
-
静态初始化
-
在定义数组的同时给数组元素赋初始值
-
使用一对大括号把初值括起来,每个元素对应一个引用
int intArray[] = {1,2,3,4}; // 定义了一个含有4个元素的int型数组 char charArray[] = {'a','b','c'}; // 定义了一个含有3个元素的char型数组 String strArray[] = {"张三","李四","王五"}; // 定义了一个含有3个元素的字符串数组
-
-
动态初始化
-
以上述最后一个字符串数组为例,等价于下述语句
String strArray[]; strArray = new String[3]; strArray[0] = "张三"; strArray[1] = "李四"; strArray[2] = "王五";
- 此处进行的是动态初始化,使用运算符new为数组分配空间
- 数组声明的方括号中表示数组元素的个数
-
-
基本数据类型,其创建格式
-
类型 数组名[] = new 类型[数组大小] 类型[] 数组名 = new 类型[数组大小]
-
-
类类型的数组创建
-
使用new运算符只是为数组本身分配空间,并没有对数组的元素进行初始化
-
所以对于类类型的数组,空间分配需要经过两步
-
先创建数组本身
-
类型 数组名[] = new 类型[数组大小]
-
-
分别创建各个数组元素
-
数组名[0] = new 类型[初值表]
-
-
假设有一个类Point
public class Point { int x,y; Point(int x1,int y1){ x = x1; y = y1; } Point() { this(0,0); } public static void main(String[] args) { Point[] points = new Point[100]; points[0] = new Point(); points[1] = new Point(); points[2] = new Point(); } }
-
语句Point[] points = new Point[100]; 只创建了100个Point型变量的数组,它并没有创建100个Point对象
-
因为Point型是类类型,所以这些对象必须再单独创建
-
points[0] = new Point(); points[1] = new Point(); points[2] = new Point(); ........
-
-
Point型数组声明与数组创建之间的关系
- 执行Point[ ] points; 后,系统为变量points分配了一个引用空间如图A所示
- 执行语句points = new Point[100]; 后,系统在内存中分配一个含100个原宿的数组对象,并把数组的首地址赋值给了points,如图B所示
- 执行语句 points[1] = new Point(); 后,表示数组中有一个元素初始化了,数组的状态由图B转换图C
-
-
-
数组变量的类型可以不同于所指向的数组类
-
Object[] Point; points = new Point[100];
- points是Object类型的数组,第二行创建的数组时Point型的
- Point派生于公共父类Object,反行之,是不被允许的
-
3. 访问数组元素
-
数组中的元素个数length是数组类中唯一的数据成员变量
-
数组下标从0开始,使用new创建数组时系统自动给length赋值,数组一旦创建完毕,其大小就固定了
-
程序运行时可以使用length进行数组边界检查,如果发生越界,则会抛出异常
-
创建一个有10个int型量的数组list,然后顺序分为访问每个元素
-
int list[] = new int[10]; for (int i = 0; i < list.length; i++) { System.out.println(list[i]); }
- 循环中使用的是 list.length ,而不是常数10,如此一来,不会引起数组下标越界异常
-
当定义了一个数组,并且new分配了内存空间后,就可以引用数组中的每一个元素了
-
数组名加上下标就可以表示数组元素
-
其中下标的访问可以是整型常数或者表达式
// 数组名 [下标] list[i];
-
-
当创建一个数组时,每个元素都被初始化
Point[] points = new Point[100];
-
它的每个值都被初始化为0,而数组points每个值都被初始化为null,表明还没有指向真正的Point对象,需要执行赋值语句
-
points[0] = new Point;
-
-
-
任何变量都不能在没有初始化的状态下使用,编译器并不能检查数组的元素初始化情况,需要自己注意
-
例子:给定一组整型数,求他们的平均值以及最大值
public class Calculator { public static double calculatorAverage(int[] number) { int sun = 0; for (int i = 0; i < number.length; i++) { sun += number[i]; } return sun / (double) number.length; } public static int findMaximun(int[] number) { int max = number[0]; for (int i = 0; i < number.length; i++) { if (number[i] > max) { max = number[i]; } } return max; } } class Test{ public static void main(String[] args) { int number[] = {23,33,54,98,3,5,29,22,72}; System.out.println("平均值:"+Calculator.calculatorAverage(number)); System.out.println("最大值:"+Calculator.findMaximun(number)); } } 控制台: 平均值:37.666666666666664 最大值:98
4. 多维数组
-
多维数组的定义
-
一般来说,n维数组就是n-1维数组的数组,例如 int[ ] [ ]是类型,它表示二维数组,每个数组是int类型的
-
以二维数组为例,定义格式如下
-
与一维数组一样,二维数组定义时对数组元素没有分配内存空间
-
同样需要进行初始化之后,才可以访问每个元素
// 类型 数组名[][] int intArray1[] []; // 另外两种方式 int[] [] intArray2; int[] intArray3[];
-
-
-
多维数组的初始化
-
与一维数组一样,也分为动态和静态两种初始化方式
-
静态初始化时,在定义数组的同时称为数组赋初值
-
int intArray[] [] = {{1,2},{3,4},{5,6}};
- 无需指出数组每一维的大小,系统会根据初始化时给出的初值的个数自动计算每一维的大小
- 外层括号所含各元素是数组第一维的各元素
- 内层括号对应于数组第二维的元素
- 上面定义的数组 intArray 是一个3行2列的数组
- 使用两个下标可以访问数组中的对应元素,如 intArray[1] [1] 表示该数组第二行第二列的元素5
- 数组各维的下标均从0开始
-
-
对二维数组进行动态初始化时,有两种分配内存空间的办法
-
直接分配
-
就是直接为每一维分配空间,声明数组时,给出维度的大小
-
// 类型 数组名 [] [] = new 类型[第一维大小][第二维大小]; int a[] [] = new int[2][3]; // 声明了一个2行3列的二维数组
-
-
按维分配
-
必须从最高维起,分别为每一维分配内存
-
// 类型 数组名 [] [] = new 类型[第一维大小][; int a[] [] = new int[4][]; // 声明了一个2行的字符串数组 a[0] = new int[5]; // 表明了第一行有5列 a[1] = new int[5]; // 表明了第二行有5列 套娃即可 a[2] = new int[5]; a[3] = new int[5]; a[0][1] = new int[2] // 给第一行第二列赋值1
- 第一行的声明语句调用new了一个数组对象,指定第一维的大小4,此时数组的4个元素中各含有一个null引用
- 后续的4个声明语句分别让着4个元素指向各含有5个元素的一维数组
- 构成一个4行5列的二维数组
- 创建数组时,第二维的大小是可以不一样的,创建一个非矩阵数组
-
-
维数声明应从高维到低维
-
数组的维数指定只能出现在new运算符之后
-
-
-
多维数组的引用
-
在定义并初始化多维数组之后,可以使用多维数组中的每个元素,以二维数组为例,引用方式如下:
-
数组名[第一维下标] [第二维下标]
-
数组下标也称为索引,都是从0开始。第一维称为行,第二维称为列,如下:
-
int myTable[][] = new int[4][3];
- 如果要访问 myTable 的元素,只需要指定相应的行,列下标即可
-
-
-
二维数组使用示例
public static void main(String[] args) { int myTable[][] = { {23,45,34,65,78,54,12}, {2,5,4,6,77,55,32}, {21,49,37,25,18,54,12}, {20,85,94,26,88,44,99}, }; for (int row = 0; row < 4; row++) { for (int col = 0; col < 7; col++) { System.out.print(myTable[row][col]+" "); } System.out.println(); } } 控制台: 23 45 34 65 78 54 12 2 5 4 6 77 55 32 21 49 37 25 18 54 12 20 85 94 26 88 44 99
-
二维数组也有length属性,但只表示第一维的长度,例如
-
ages.length 的值是4,而不是28,也不是7
-
int[][] ages = new int[4][7]; int[] firstArray = ages[0]; System.out.println(ages.length); // 4 System.out.println(firstArray.length); // 7
-
-
在Java中,数组时用来表示同以类型数据的数据结构,初始化之后,数组的大小不会再动态变化
-
数组变量是一个指向数组对象实例的引用
-
虽然不能再改变它的大小,但是可以使用同一个引用变量指向另一个全新的数组,如下:
-
int elements[] = new int[6]; elements = new int[10];
-
执行这两段语句后,elements指向了第二个数组
-
第一个数组实际上丢了,除非还有其他引用指向它
-
-
-
System类中提供了一个arraycopy( )方法,可以高效实现数组之间的复制
-
数组复制例子
-
数组element 和 hold 作为方法 arraycopy 的参数使用,当数组作为函数参数时,时将数组引用穿给方法,方法中对数组内容的改变都会对方法外有影响
public static void main(String[] args) { int element[] = {1,2,3,4,5,6}; // 初始数组 int hold[] = {10,9,8,7,6,5,4,3,2,1}; // 更大的另一个数组 // 把element数组中的所有元素复制到hold数组中,下标从0开始 System.arraycopy(element,1,hold,2,4); }
- element是一个含6个int型的数组,hold含有10个int型
- 最后一行的含义是:把element中下标1开始的4个元素,依此放到hold下标从2开始的位置
- 以element中的2,3,4,5 替换 hold中的 8,7,6,5
-
-
Java.util.Arrays中为数组提供了一系列静态方法
-
equals(type[ ],type[ ])
- 可用来判定type类型的两个数组的值是否相同
- type可以是基本类型,也可以是类类型,如果相等,则返回true
-
如果数组元素为对象,可以调用对象的equals方法来判断是否相同
-
sort(type[ ])将type类型的数组按照升序排序
-
如果数组元素为对象,可以调用对象的compareTo( )来得到比较结果
-
二. 字符串类型
1.字符串的声明
-
Java.lang中封装了String 和StringBuffer类,可以方便的处理字符串
- String处理不变字符串
- StringBuffer类用处理可变字符串
-
字符串是内存中连续排列的0个或者多个字符
-
不变字符串是指字符串一旦创建,其内容就不可改变
-
比如:对String类的实例进行查找,比较,连接等操作,既不能输入新字符,又不能改变字符串的长度
-
Java中字符串分为常量和变量
-
字符串常量是用双引号括起来的一串字符
-
系统为程序中出现的字符串常量自动创建一个String对象
-
System.out.println("Hello World");
- 这个"Hello World"对象,这个创建过程是隐含的
-
-
-
对于字符串变量,在使用之前要显式声明,并进行初始化
- StringBuffer 类不能使用字符串常量来创建
- char是单字符 String是字符串
public static void main(String[] args) { String s1; StringBuffer sb1; // 也可以创建一个空的字符串 String s2 = new String(); StringBuffer sb2 = new StringBuffer(); // 此外还可以由字符串数组创建字符串 char是单字符 String是字符串 char chars[] = {'a','b','c'}; String s3 = new String(chars); // 直接使用字符串常量来初始化一个字符串 String s4 = "Hello World"; // StringBuffer类不能使用字符串常量来创建 StringBuffer sb3 = "Hello World"; // 报错 // 可以使用String类的变量来创建,比如使用已经定义了的s4 StringBuffer sb4 = new StringBuffer(s4); }
2. 字符串的操作
-
String类的对象实例时不可改变的,一旦创建就确定下来
-
对字符串施加操作后并不会改变字符串本身,而是又生成了另外一个实例
-
StringBuffer类处理可变字符串,当修改一个StringBuffer类的字符串时,不是再创建一个新的字符串对象而是直接操作原字符串
-
String 类和 StringBuffer 类共有的常用方法如下
-
length(); // 返回字符串的长度,即字符个数 charAt(int index); // 返回字符串中index位置的字符 subString(int beginIndex); // 截取当前字符串中从beginIndex开始到末尾的子串 replace(char oldChar,char newChar); // 将当前字符串中出现的所有oldChar转换为newChar toLowerCase(); // 将当前字符串中所有的字符转换为小写 toUpperCase(); // 将当前字符串中所有的字符转换为大写 startsWith(String prefix); // 测试prefix是否是当前字符串的前缀 concat(String str); // 将str连接在当前字符串的尾部 trim(); // 去掉字符串前面以及后面的空白 valueOf(type value); // 将type类型的参数value转换为字符串形式
-
-
StringBuffer 类中常用方法如下
-
append(String str); // 将参数str表示的字符串添加到当前串最后 replace(int start, int end, String str); // 使用给定的str代替从start到end之间的子串 capacity(); // 返回当前的容量
-
-
String类的字符串连接还可以使用运算符+来实现
-
系统为String类对象分配内存的时候,按照对象中所含字符的实际个数等量分配
-
而StringBuffer,除去字符所占空间之外,另加16个字符大小的缓冲区
-
对于StringBuffer类对象,length( )方法获取的是字符串的长度
-
capacity( ) 方法返回当前的容量,即字符串长度加上缓冲区的大小
-
字符串操作实例:
-
String s = "Hello World hi lcy"; System.out.println("没有变化前:s="+s); // s=Hello World hi lcy String t = s.toLowerCase(); // 转换为小写 System.out.println("变化后:t="+t); // t=hello world hi lcy StringBuffer strb = new StringBuffer(s); System.out.println("字符串的长度,s="+s.length()); // s=18 System.out.println("StringBuffer字符串的长度,strb="+strb.length()); // strb=18 t = s.replace('l','t'); System.out.println("转换后t="+t); // t=Hetto Wortd hi tcy StringBuffer strb2 = strb.replace(0, 4, "goods"); System.out.println("替换之后:strb="+strb2); // strb=goodso World hi lcy System.out.println("返回当前的容量:strb="+strb.capacity()); // 34
-
-
Java中可以使用关系运算符 == 来判断两个字符串是否相等,与equals( ) 方法不同的是,== 判断两个字符串对象是否是同一个实例,就是它们在内存中的存储空间是否相等
-
字符串比较例子:
String s1 = "Hello"; String s2 = "Hello"; String s3 = new String("Hello"); String s4 = new String(s1); String s5 = s1; System.out.println(s1.equals(s2)); // true System.out.println(s1==s2); // true System.out.println(s1.equals(s3)); // true System.out.println(s1==s3); // true System.out.println(s1.equals(s4)); // true System.out.println(s1==s4); // false System.out.println(s1.equals(s5)); // true System.out.println(s1 == s5); // true
-
如上程序中,s1和s2使用相同的字符串常量来定义,s1与s2都指向这同一个常量,所以使用 == 或者equals方法来判定,结果都是为true的
-
而s3是使用字符串常量创建的另一个实例,虽然与s1的字符串相同,但却不是相同的实例,使用 == 判断内存地址的时候是不相等的,与s4一样,都是另外一个实例
-
而s5和s1指向同一个实例,所以在两种方式下比较都是相同的,指向同一个实例的两个引用互称为别名
-
内存地址相同说明这俩是同一个东西,所以hashcode一定相同。hashcode相同,两个对象不一定相同
- 在hash类的存储结构中,先计算hashcode决定其在数组中的位置,然后遍历数组上的链表,对比各个元素hashcode是否有重复,如果有重复则调用equals方法对比元素是否相同,如果hashcode和equals都相同,则认为元素相同,则不再添加或更新原来的
-
equals在未被重写的时候默认调用Object里的equals,这时的equals其实就是==
-
-
三. Vector类
1. 概述
-
数组只能保存固定数目的元素,数组空间一经过申请就不可再改变,为了解决这个问题**,引入了Vector可变数组(向量)**
-
Vector类似于数组,可以使用整数下标来访问各个元素,比数组功能更加强大
- Vector的长度可以根据需要来改变
- 保存的元素类型也可以不一样
-
当需要处理数目不定,类型不同或者是需要频繁地在对象序列中增删查操作时,通过使用Vector来代替数组
- Vector类的实例中只能保存对象类型,不能是基本数据类型
-
Vector类包含的成员变量有三个
-
protected Object[] elementData; // 增量的大小,如果值为0,则缓冲区大小每次倍增
-
protected int elementCount; // Vector对象中元素的数量
-
protected int capacityIncrement; // 元素存储的数组缓冲区
- 系统内部会记录Vector类实例的容量capacity,实际保存的元素个数由 elementCount 来记录,这个值不能大于容量值
- 当有元素加入到向量时,elementCount 会相应增大,当向量中添加的元素超过了它的容量之后,向量的存储空间以容量增值 capacityIncrement 的大小为单位增长,为新的元素加入做好准备。
- 元素保存在数组 elementData 中
-
2. Vector类的方法
- 使用Vector一定要先创建再使用
- 如果不先使用new运算符利用构造方法创建对象,可能造成堆栈移除或者使用空指针移除
a. 构造方法
-
有3个常用的构造方法
-
public Vector() { // 构造一个空向量 this(10); // 标准容量为10 }
-
public Vector(int initialCapacity) { // 指定的初始容量构造一个空向量 this(initialCapacity, 0); }
-
public Vector(int initialCapacity, int capacityIncrement) { super(); if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); this.elementData = new Object[initialCapacity]; this.capacityIncrement = capacityIncrement; }
- 以指定的存储容量 initialCapacity 和 容量增量 capacityIncrement 构造一个空向量
-
-
创建Vector实例时,要指明其中保存的元素类型,如下:
Vector<String> myVector = new Vector<>(100,50);
- 创建的 myVector 向量序列初始有100个字符串空间
- 以后一旦用尽以50为单位递增,使序列中能够容纳元素个数为150,200…
b. 添加方法
-
往Vector类对象中添加元素的常用方法如下
-
public synchronized void addElement(E obj) { // 将新元素obj添加到序列尾部 modCount++; ensureCapacityHelper(elementCount + 1); elementData[elementCount++] = obj; }
-
public synchronized void insertElementAt(E obj, int index) { modCount++; if (index > elementCount) { throw new ArrayIndexOutOfBoundsException(index + " > " + elementCount)java; } ensureCapacityHelper(elementCount + 1); System.arraycopy(elementData, index, elementData, index + 1, elementCount - index); elementData[index] = obj; elementCount++; }
- 将指定对象obj插入到指定位置index处
-
public void add(int index, E element) { // 在向量的指定位置index插入指定的元素obj insertElementAt(element, index); }
-
public synchronized void insertElementAt(E obj, int index) { modCount++; if (index > elementCount) { throw new ArrayIndexOutOfBoundsException(index + " > " + elementCount)java; } ensureCapacityHelper(elementCount + 1); System.arraycopy(elementData, index, elementData, index + 1, elementCount - index); elementData[index] = obj; elementCount++; }
- 将指定对象obj插入到指定位置index处
-
public void add(int index, E element) { // 在向量的指定位置index插入指定的元素obj insertElementAt(element, index); }
-