1.数组
1.1 定义
数组是相同类型数据的有序集合。数组描述的是相同类型的若干个数据,按照一定的先后次序排列组合而成。其中,每一个数据称作一个元素,每个元素可以通过一个索引(下标)来访问它们。数组的三个基本特点:
-
长度是确定的。数组一旦被创建,它的大小就是不可以改变的。
-
其元素必须是相同类型,不允许出现混合类型。
-
数组类型可以是任何数据类型,包括基本类型和引用类型。
建议
数组变量属引用类型,数组也可以看成是对象,数组中的每个元素相当于该对象的成员变量。数组本身就是对象,Java中对象是在堆中的,因此数组无论保存原始类型还是其他对象类型,数组对象本身是在堆中存储的。
1.2 数组声明
type[] arr_name; //(推荐使用这种方式)
type arr_name[];
-
声明的时候并没有实例化任何对象,只有在实例化数组对象时,JVM才分配空间,这时才与长度有关。
-
声明一个数组的时候并没有数组真正被创建。
-
构造一个数组,必须指定长度。
基本数据类型内存分配图:
引用数据类型内存分配图:
1.3 数组初始化
1.3.1 静态初始化
除了用new关键字来产生数组以外,还可以直接在定义数组的同时就为数组元素分配空间并赋值。
int[] a = { 1, 2, 3 };// 静态初始化基本类型数组;
Man[] mans = { new Man(1, 1), new Man(2, 2) };// 静态初始化引用类型数组;
1.3.2 动态初始化
数组定义与为数组元素分配空间并赋值的操作分开进行。
int[] a1 = new int[2];//动态初始化数组,先分配空间;
a1[0]=1;//给数组元素赋值;
a1[1]=2;//给数组元素赋值;
1.3.3 默认初始化
数组是引用类型,它的元素相当于类的实例变量,因此数组一经分配空间,其中的每个元素也被按照实例变量同样的方式被隐式初始化。
int a2[] = new int[2]; // 默认值:0,0
boolean[] b = new boolean[2]; // 默认值:false,false
String[] s = new String[2]; // 默认值:null, null
1.4 java.util.Arrays类
JDK提供的java.util.Arrays类,包含了常用的数组操作,方便我们日常开发。Arrays类包含了:排序、查找、填充、打印内容等常见的操作。
import java.util.Arrays;
public class Test {
public static void main(String args[]) {
int[] a = { 1, 2 };
System.out.println(a); // 打印数组引用的值;
System.out.println(Arrays.toString(a)); // 打印数组元素的值;
}
}
此处的Arrays.toString()方法是Arrays类的静态方法,不是前面讲的Object的toString()方法。
1.5 多维数组
多维数组可以看成以数组为元素的数组。可以有二维、三维、甚至更多维数组,但是实际开发中用的非常少。最多到二维数组(学习容器后,我们一般使用容器,二维数组用的都很少)。
二维数组的声明
public class Test {
public static void main(String[] args) {
// Java中多维数组的声明和初始化应按从低维到高维的顺序进行
int[][] a = new int[3][];
a[0] = new int[2];
a[1] = new int[4];
a[2] = new int[3];
// int a1[][]=new int[][4];//非法
}
}
二维数组的静态初始化
public class Test {
public static void main(String[] args) {
int[][] a = { { 1, 2, 3 }, { 3, 4 }, { 3, 5, 6, 7 } };
System.out.println(a[2][3]);
}
}
二维数组的动态初始化
import java.util.Arrays;
public class Test {
public static void main(String[] args) {
int[][] a = new int[3][];
// a[0] = {1,2,5}; //错误,没有声明类型就初始化
a[0] = new int[] { 1, 2 };
a[1] = new int[] { 2, 2 };
a[2] = new int[] { 2, 2, 3, 4 };
System.out.println(a[2][3]);
System.out.println(Arrays.toString(a[0]));
System.out.println(Arrays.toString(a[1]));
System.out.println(Arrays.toString(a[2]));
}
}
内存分配图:
获取数组长度
//获取的二维数组第一维数组的长度。
System.out.println(a.length);
//获取第二维第一个数组长度。
System.out.println(a[0].length);
1.6 冒泡排序法
-
比较相邻的元素。如果第一个比第二个大,就交换他们两个。
-
对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
-
针对所有的元素重复以上的步骤,除了最后一个。
-
持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
package it.array;
import java.util.Arrays;
/**
* 冒泡排序法
*
* @author 天然卷
*
*/
public class BubbleSort {
public static void main(String[] args) {
int[] arr = { 1, 5, 33, 555, 8, 3333, 55, 99 };
bubbleSort(arr);
}
public static void bubbleSort(int[] a) {
for (int i = 0; i < a.length - 1; i++) {
boolean flag = true;
int s = 1;
for (int j = 0; j < a.length - 1 - i; j++) {
if (a[j] > a[j + 1]) {
int temp = a[j + 1];
a[j + 1] = a[j];
a[j] = temp;
flag = false;
}
System.out.println(Arrays.toString(a));
}
if (flag) {
System.out.println("结束!!!!");
break;
}
System.out.println("========================");
}
}
}
1.7 二分查找法
package it.array;
import java.util.Arrays;
/**
* 冒泡排序法
*
* @author 天然卷
*
*/
public class BubbleSort {
public static void main(String[] args) {
int[] arr = { 1, 5, 33, 555, 8, 3333, 55, 99 };
bubbleSort(arr);
}
public static void bubbleSort(int[] a) {
for (int i = 0; i < a.length - 1; i++) {
boolean flag = true;
int s = 1;
for (int j = 0; j < a.length - 1 - i; j++) {
if (a[j] > a[j + 1]) {
int temp = a[j + 1];
a[j + 1] = a[j];
a[j] = temp;
flag = false;
}
System.out.println(Arrays.toString(a));
}
if (flag) {
System.out.println("结束!!!!");
break;
}
System.out.println("========================");
}
}
}
1.8 总结
-
数组是相同类型数据的有序集合。
-
数组的四个基本特点:
-
其长度是确定的
-
其元素必须是相同类型
-
可以存储基本数据类型和引用数据类型
-
数组变量属于引用类型
-
-
一维数组的声明方式
-
type[] arr_name; (推荐使用这种方式)
-
type arr_name[]。
-
-
数组的初始化:静态初始化、动态初始化和默认初始化。
-
数组的长度:数组名.length,下标的合法区间[0,数组名.length-1]。
-
数组拷贝:System类中的static void arraycopy(object src,int srcpos,object dest, int destpos,int length)方法。
-
数组操作的常用类java.util.Arrays类
-
打印数组:Arrays.toString(数组名);
-
数组排序:Arrays.sort(数组名);
-
二分查找:Arrays.binarySearch(数组名,查找的元素)。
-
-
二维数组的声明
2.包装类
2.1 包装类的基本知识
Java是面向对象的语言,但并不是“纯面向对象”的,因为我们经常用到的基本数据类型就不是对象。但是我们在实际应用中经常需要将基本数据转化成对象,以便于操作。比如:将基本数据类型存储到Object[]数组或集合中的操作等等。
为了解决这个不足,Java在设计类时为每个基本数据类型设计了一个对应的类进行代表,这样八个和基本数据类型对应的类统称为包装类(Wrapper Class)。
包装类均位于java.lang包,八种包装类和基本数据类型的对应关系如下表所示:
在这八个类中,除了Character和Boolean以外,其他的都是“数字型”,“数字型”都是java.lang.Number的子类。Number类是抽象类,因此它的抽象方法,所有子类都需要提供实现。Number类提供了抽象方法:intValue()、longValue()、floatValue()、doubleValue(),意味着所有的“数字型”包装类都可以互相转型。
public class WrapperClassTest {
public static void main(String[] args) {
Integer i = new Integer(10);
Integer j = new Integer(50);
}
}
2.2 包装类的用途
-
作为和基本数据类型对应的类型存在,方便涉及到对象的操作,如Object[]、集合等的操作。
-
包含每种基本数据类型的相关属性如最大值、最小值等,以及相关的操作方法(这些操作方法的作用是在基本数据类型、包装类对象、字符串之间提供相互之间的转化!)。
2.3 自动装箱和自动拆箱
自动装箱和拆箱就是将基本数据类型和包装类之间进行自动的互相转换。JDK1.5后,Java引入了自动装箱(autoboxing)/拆箱(unboxing)。
自动装箱:
基本类型的数据处于需要对象的环境中时,会自动转为“对象”。
在JDK1.5以后,Java提供了自动装箱的功能,因此只需Integer i = 5这样的语句就能实现基本数据类型转换成包装类,这是因为JVM为我们执行了Integer i = Integer.valueOf(5);这样的操作,这就是Java的自动装箱。
自动拆箱:
每当需要一个值时,对象会自动转成基本数据类型,没必要再去显式调用intValue()、doubleValue()等转型方法。
如 Integer i = 5;int j = i; 这样的过程就是自动拆箱。
我们可以用一句话总结自动装箱/拆箱:
自动装箱过程是通过调用包装类的valueOf()方法实现的,而自动拆箱过程是通过调用包装类的 xxxValue()方法实现的(xxx代表对应的基本数据类型,如intValue()、doubleValue()等)。
自动装箱与拆箱的功能事实上是编译器来帮的忙,编译器在编译时依据您所编写的语法,决定是否进行装箱或拆箱动作。
自动装箱:
Integer i = 100;//自动装箱
//相当于编译器自动为您作以下的语法编译:
Integer i = Integer.valueOf(100);//调用的是valueOf(100),而不是new Integer(100)
自动拆箱:
Integer i = 100;
int j = i;//自动拆箱
//相当于编译器自动为您作以下的语法编译:
int j = i.intValue();
2.4 包装类的缓存问题
整型、char类型所对应的包装类,在自动装箱时,对于-128~127之间的值会进行缓存处理,其目的是提高效率。
缓存处理的原理为:如果数据在-128~127这个区间,那么在类加载时就已经为该区间的每个数值创建了对象,并将这256个对象存放到一个名为cache的数组中。每当自动装箱过程发生时(或者手动调用valueOf()时),就会先判断数据是否在该区间,如果在则直接获取数组中对应的包装类对象的引用,如果不在该区间,则会通过new调用包装类的构造方法来创建对象。
public class Test3 {
public static void main(String[] args) {
Integer in1 = -128;
Integer in2 = -128;
System.out.println(in1 == in2);//true 因为123在缓存范围内
System.out.println(in1.equals(in2));//true
Integer in3 = 1234;
Integer in4 = 1234;
System.out.println(in3 == in4);//false 因为1234不在缓存范围内
System.out.println(in3.equals(in4));//true
}
}
3 String类
3.1 概述
String 类对象代表不可变的Unicode字符序列,因此我们可以将String对象称为“不可变对象”。对象内部的成员变量的值无法再改变。
String类的部分源码
我们发现字符串内容全部存储到value[]数组中,而变量value是final类型的,也就是常量(即只能被赋值一次)。 这就是“不可变对象”的典型定义方式。
字符串常量拼接时的优化
在遇到字符串常量之间的拼接时,编译器会做出优化,即在编译期间就会完成字符串的拼接。因此,在使用==进行String对象之间的比较时,我们需要特别注意。
public class TestString2 {
public static void main(String[] args) {
//编译器做了优化,直接在编译的时候将字符串进行拼接
String str1 = "hello" + " java";//相当于str1 = "hello java";
String str2 = "hello java";
System.out.println(str1 == str2);//true
String str3 = "hello";
String str4 = " java";
//编译的时候不知道变量中存储的是什么,所以没办法在编译的时候优化
String str5 = str3 + str4;
System.out.println(str2 == str5);//false
}
}
String类常用的方法:
-
String类的下述方法能创建并返回一个新的String对象: concat()、 replace()、substring()、 toLowerCase()、 toUpperCase()、trim()。
-
提供查找功能的有关方法: endsWith()、 startsWith()、 indexOf()、lastIndexOf()。
-
提供比较功能的方法: equals()、equalsIgnoreCase()、compareTo()。
-
-
其它方法: charAt() 、length()。
3.2 StringBuffer和StringBuilder
StringBuffer和StringBuilder非常类似,均代表可变的字符序列。 这两个类都是抽象类AbstractStringBuilder的子类。
AbstractStringBuilder 部分源码
abstract class AbstractStringBuilder implements Appendable, CharSequence {
/**
* The value is used for character storage.
*/
char value[];
//以下代码省略
}
显然,内部也是一个字符数组,但这个字符数组没有用final修饰,随时可以修改。因此,StringBuilder和StringBuffer称之为“可变字符序列”。
两者的区别:
-
StringBuffer JDK1.0版本提供的类,线程安全,做线程同步检查, 效率较低。
-
StringBuilder JDK1.5版本提供的类,线程不安全,不做线程同步检查,因此效率较高。 建议采用该类。
常用方法列表:
-
重载的public StringBuilder append(…)方法
可以为该StringBuilder 对象添加字符序列,仍然返回自身对象。
-
方法 public StringBuilder delete(int start,int end)
可以删除从start开始到end-1为止的一段字符序列,仍然返回自身对象。
-
方法 public StringBuilder deleteCharAt(int index)
移除此序列指定位置上的 char,仍然返回自身对象。
-
重载的public StringBuilder insert(…)方法
可以为该StringBuilder 对象在指定位置插入字符序列,仍然返回自身对象。
-
方法 public StringBuilder reverse()
用于将字符序列逆序,仍然返回自身对象。
-
方法 public String toString()
返回此序列中数据的字符串表示形式。
-
和 String 类含义类似的方法:
public int indexOf(String str)
public int indexOf(String str,int fromIndex)
public String substring(int start)
public String substring(int start,int end)
public int length()
char charAt(int index)
3.3 不可变和可变字符序列使用陷阱
String使用的陷阱
String一经初始化后,就不会再改变其内容了。对String字符串的操作实际上是对其副本(原始拷贝)的操作,原来的字符串一点都没有改变。比如:
String s ="a"; 创建了一个字符串
s = s+"b"; 实际上原来的"a"字符串对象已经丢弃了,现在又产生了另一个字符串s+"b"(也就是"ab")。 如果多次执行这些改变串内容的操作,会导致大量副本字符串对象存留在内存中,降低效率。如果这样的操作放到循环中,会极大影响程序的时间和空间性能,甚至会造成服务器的崩溃。
相反,StringBuilder和StringBuffer类是对原字符串本身操作的,可以对字符串进行修改而不产生副本拷贝或者产生少量的副本。因此可以在循环中使用。
public class TestStringDemo {
public static void main(String[] args) {
String str1 = "";
long num1 = Runtime.getRuntime().freeMemory();
long time1 = System.currentTimeMillis();
for (int i = 0; i < 5000; i++) {
str1 = str1 + i;
}
long num2 = Runtime.getRuntime().freeMemory();
long time2 = System.currentTimeMillis();
System.out.println("String占用内存空间:" + (num1 - num2));
System.out.println("String运行时间:" + (time2 - time1));
StringBuilder sb = new StringBuilder("");
long num3 = Runtime.getRuntime().freeMemory();
long time3 = System.currentTimeMillis();
for (int i = 0; i < 5000; i++) {
sb.append(i);
}
long num4 = Runtime.getRuntime().freeMemory();
long time4 = System.currentTimeMillis();
System.out.println("StringBuilder占用内存空间:" + (num3 - num4));
System.out.println("StringBuilder运行时间:" + (time4 - time3));
}
}
要点:
-
String:不可变字符序列。
-
StringBuffer:可变字符序列,并且线程安全,但是效率低。
-
StringBuilder:可变字符序列,线程不安全,但是效率高(一般用它)。
4.时间处理相关类
在计算机世界,我们把1970 年 1 月 1 日 00:00:00定为基准时间,每个度量单位是毫秒(1秒的千分之一)。
我们用long类型的变量来表示时间,从基准时间往前几亿年,往后几亿年都能表示。如果想获得现在时刻的“时刻数值”,可以使用:
long now = System.currentTimeMillis();
日期时间相关类
4.1 Date时间类(java.util.Date)
在标准Java类库中包含一个Date类。它的对象表示一个特定的瞬间,精确到毫秒。
-
Date() 分配一个Date对象,并初始化此对象为系统当前的日期和时间,可以精确到毫秒)。
-
Date(long date) 分配 Date 对象并初始化此对象,以表示自从标准基准时间(称为“历元(epoch)”,即 1970 年 1 月 1 日 00:00:00 GMT)以来的指定毫秒数。
-
boolean after(Date when) 测试此日期是否在指定日期之后。
-
booleanbefore(Date when) 测试此日期是否在指定日期之前。
-
boolean equals(Object obj) 比较两个日期的相等性。
-
long getTime() 返回自 1970 年 1 月 1 日 00:00:00 GMT 以来此 Date 对象表示的毫秒数。
-
String toString() 把此 Date 对象转换为以下形式的 String:
dow mon dd hh:mm:ss zzz yyyy
其中: dow 是一周中的某一天 (Sun、 Mon、Tue、Wed、 Thu、 Fri、 Sat)。
4.2 DateFormat类和SimpleDateFormat类
DateFormat类的作用
把时间对象转化成指定格式的字符串。反之,把指定格式的字符串转化成时间对象。
DateFormat是一个抽象类,一般使用它的的子类SimpleDateFormat类来实现。
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class TestDate {
public static void main(String[] args) throws ParseException {
Date date = new Date(200000000);
System.out.println(date);
DateFormat df1 = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
String time = df1.format(new Date());
System.out.println(time);
String time2 = "2020-02-02 3:9:10";
Date date2 = df1.parse(time2);
System.out.println(date2);
}
}
4.3 Calendar日历类
Calendar 类是一个抽象类,为我们提供了关于日期计算的相关功能,比如:年、月、日、时、分、秒的展示和计算。
GregorianCalendar 是 Calendar 的一个具体子类,提供了世界上大多数国家/地区使用的标准日历系统。
注意月份的表示,一月是0,二月是1,以此类推,12月是11。
可视化日历的编写
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Scanner;
public class TestCalendar {
public static void main(String[] args) throws ParseException {
System.out.println("请输入日期(格式:2020-02-22):");
Scanner sc = new Scanner(System.in);
String s = sc.nextLine();
System.out.println("您输入的日期为:" + s);
DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
Date d = df.parse(s);
Calendar c = new GregorianCalendar();
c.setTime(d);
int day = c.get(Calendar.DAY_OF_MONTH);
int maxDay = c.getActualMaximum(Calendar.DATE);
// System.out.println(Calendar.DATE);
System.out.println("日\t一\t二\t三\t四\t五\t六");
c.set(Calendar.DAY_OF_MONTH, 1);
int start = c.get(Calendar.DAY_OF_WEEK);
for (int i = 0; i < start - 1; i++) {
System.out.print("\t");
}
for (int i = 1; i <= maxDay; i++) {
if (c.get(Calendar.DAY_OF_MONTH) == day) {
System.out.print(c.get(Calendar.DAY_OF_MONTH) + "*\t");
} else {