1.java语言中的数组是一种引用数据类型,不属于基本数据类型。数组的父类是Object类。
2.数组实际上是一个容器,可以同时容纳多个元素。(数组是一个数据的集合),数组:字面意思是“一组数据”。
3.数组当中可以存储基本数据类型的数据,也可以存储引用数据类型的数据。
4.数组因为是引用数据类型,所以数组对象是在堆内存当中。(数组是存储在堆内存当中的)
5.对于数组当中如果存储的是java对象的话,实际上存储的是对象的引用(内存地址)。
6.数组一旦创建,在java中规定,长度不可变。(数组的长度不变)
7.数组的分类:一维数组,二维数组…(一维数组较多,二维数组偶尔使用)
8.所有的数组对象都有length属性(java自带的),用来获取数组中元素的个数。
9.java中的数组要求数组中存储的元素类型统一。比如int类型数组只能装int类型数据,Person数组只能存储Person类型数据。
10.数组在内存方面存储的时候,数组中的元素内存地址是连续的(存储的每一个元素都是有规则的挨着排列的内存地址连续),这是数组存储的 特点。数组实际上是一种简单的数据结构。
11.所有的数组都是拿“第一个小方框的地址”作为整个数组对象的内存地址。(数组中首元素的内存地址作为整个数组对象的地址)
12.数组中每一个元素都是有下标的,下标才能够0开始,以1递增,最后一个元素的下标是length-1.
下标非常重要,因为我们对数组中元素进行“获取”的时候,都是通过下标来进行的。
13.数组这种数据结构的优点和缺点是什么??
优点:查询/检索某个下标的元素时效率很高,可以说是查询效率 最高的一种数据结构。
为什么查询效率高?
第一:每一个元素的内存地址在空间存储上是连续的。
第二:每一个元素的类型相同,所以占用空间内存大小一样。
第三:知道第一个元素的内存地址,知道一个元素占用空间的大小,又知道下标,所以通过一个数学表达式就可以计算某个下标上元素的内存地址。直接通过内存地址定位元素,所以数组的检索效率最高。
缺点:
第一:由于为了保证数组中每个元素的内存地址连续,所以在数组上随机删除或者增加元素的时候,效率较低,因为增删元素会涉及到后面的元素统一向后面移位的操作。
第二:数组不能存储大数据量。为什么?因为很难在内存上找到一块大的连续内存空间。
注意:对于数组中最后一个元素的增删,是没有效率影响的。
数组满了,需要扩容。
Java中对数组的扩容:
先新建一个大容量的数组,然后将小容量的数组一个一个拷贝到大数组中。
结论:数组扩容效率较低,因为涉及到拷贝问题。所以在以后的开发中请注意:尽可能少的进行数组拷贝。可以在创建数组的时候预估一下多长合适,最好预估准确,这样可以减少数组的扩容次数,提高效率。
调用JDK System类中的arraycopy()方法,来完成数组拷贝。
数组中如果存储的元素是引用,可以拷贝吗?当然可以。
要求:
1.这个栈可以存储java中任何一个引用数据类型的数据。
2.在栈中提供push方法模拟压栈(栈满了,要有提示信息)
3.在栈中提供pop方法模拟弹栈(栈空了,也要有提示信息)
4.编写测试程序,new栈对象,调用push和pop方法类模拟压栈弹栈的动作。
public class MyStack {
//提供一个数组来存储栈中元素
//Object[]这是一个万能的口袋,这个口袋可以装任何引用数据类型的数据
//为什么选择Object类型数组?因为这个栈可以存储java中的任何引用数据类型
private Object[] elements;
//栈帧(永远指向栈顶部元素)
//每加一个元素,栈帧加一;每减一个元素,栈帧减一
//那么这个默认初始值应该是多少?注意,最初的栈是空,一个元素都没有
//如果index采用0,表示栈帧指向了顶部元素的上方
//如果index采用-1,表示栈帧指向了顶部元素
private int index;
//get和set方法也许用不上,但是必须写上,这是规矩,表示存储到数组中
//封装:第一步:属性私有化;第二步:对外提供get和set方法
public Object[] getElements() {
return elements;
}
public void setElements(Object[] elements) {
this.elements = elements;
}
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
//构造方法
//构造方法中给一维数组一个初始化容量
public MyStack() {
//默认初始化为10
this.elements = new Object[10];
//给index初始化
this.index=-1;
}
//push方法,压栈,表示栈中多一个元素,但是如果栈已满,压栈失败
public void push(Object ele){
if(index >= elements.length -1){
System.out.println("栈已满,压栈失败!");
return;
}
//程序走到这里说明栈没满
//向栈中加一个元素,栈帧向上移动一个位置
elements[++index] = ele;
System.out.println("压栈"+ele+"成功,栈帧指向"+this.index);
}
//pop方法,弹栈,表示栈中少一个元素,但是栈如果已空,弹栈失败
//弹栈方法,从数组中取元素,栈帧向下移位
public void pop(){
if (index < 0){
System.out.println("栈为空,弹栈失败!");
return;
}
System.out.println("弹栈"+elements[index]+"元素成功");
index--;
System.out.println("栈帧指向"+index);
}
}
for(int i=arr.length-1;i>0;i--){
for(int j=0;j<i;j++){
if(arr[j] > arr[j+1]){
int temp;
temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}
}
}
每一次选择这堆“参与比较的数据当中”找出最小值,拿着这个最小值和“参与比较这堆数据最前面的元素”交换位置。选择排序比冒泡排序好在:每一次的交换位置都是有意义的。
选择排序比冒泡排序效率高。高在交换位置上。
for(int i=0; i<arr.length-1;i++){
int min=i;
for(int j=i+1;j<arr.length;j++){
if(arr[j]<arr[min]){
min=j;
}
}
if(min != i){
int temp;
temp = arr[min];
arr[min] = arr[i];
arr[i] = temp;
}
}
冒泡排序和选择排序比较次数没变,交换次数减少了。
二分法查找是基于排序的基础上查找,(没有排序的数组是无法查找的)
二分法查找的终止条件:一直折半,直到中间那个元素恰好是查找的元素。
private static int binarySearch(int[] arr, int dest){
int begin=0;
int end=arr.length;
while(begin<=end){
int mid=(begin+end)/2;
if(arr[mid] == dest){
return mid;
}else if(arr[mid]<dest){
begin = mid+1;
}else{
end = mid-1;
}
}
return -1;
}
1.String表示字符串类型,属于引用数据类型,不属于基本数据类型
2.在java中随便使用双引号括起来的都是String对象,例如:“abc”,“hello”…
3.java中规定,双引号括起来的字符串,是不可变的,也就是说“abc”自出生到死亡,不可变,不会变成“abcd”。
4.在JDK当中双引号括起来的字符串,例如“abc”是直接存储在“方法区”的“字符串常量池”中。
为什么SUN公司把字符串存储在一个字符串常量池当中呢?
因为字符串在实际开发中使用太频繁,为了执行效率,所以把字符串放到了方法区的字符串常量池当中。
判断数组长度是length属性,判断字符串长度是Length()方法。
因为java中的字符串长度是不可变的,每一次拼接都会产生新的字符串,这样会占用大量的方法区内存,造成内存空间的浪费。
StringBuffer底层实际上是一个byte[]数组,往StringBuffer中放字符串,实际上是放到byte数组当中了。StringBuffer的初始化容量是16。
//创建一个初始化容量为16的byte[]数组(字符串缓冲对象)
StringBuffer strBuffer = new StringBuffer();
//拼接字符串,以后拼接字符串统一调用append()方法
//append()是追加的意思
//append()方法底层在进行追加的时候,如果byte满了,会自动扩容
strBuffer.append("a");
如何优化StringBuffer的性能?
在创建StringBuffer的时候尽可能给定一个初始化容量。最好减少底层数组的扩容次数。预估一下,给一个大一些的初始化容量。
关键点:给一个合适的初始化容量。
StringBuffer和StringBuilder的区别?
StringBuffer中的方法都有:synchronized关键字修饰,表示StringBuffer在多线程环境下运行是安全的。
StringBuilder中的方法都没有:synchronized关键字修饰,表示StringBuilder在多线程环境下运行是不安全的。
1.java为八种基本数据类型又准备了八种包装类型。八种包装类型属于引用数据类型,父类是Object。
2.为什么要再提供八种包装类型?
因为八种基本数据类型不够用,所以SUN又提供了八种包装类。
3.八种基本数据类型对应的包装类型名
基本数据类型 | 包装类型 |
---|---|
byte | java.lang.Byte(父类Number) |
short | java.lang.Short(父类Number) |
int | java.lang.Integer(父类Number) |
long | java.lang.Long(父类Number) |
float | java.lang.Float(父类Number) |
double | java.lang.Double(父类Number) |
boolean | java.lang.Boolean(父类Object) |
char | java.lang.Character(父类Object) |
以上八种包装类,重点以java.lang.Integer为代表学习,其他类照葫芦画瓢
我看过源码,String类中有一个byte[]数组,这个byte[]数组采用final修饰。因为数组一旦创建长度不可变,并且final修饰的引用,一旦指向某个对象之后,不再指向其他对象。所以String是不可变的。
我看过源码,StringBuilder/StringBuffer内部实际上是一个byte[]数组,这个byte[]数组没有被final修饰,StringBuffer/StringBuilder的初始化容量是16,当内存满了之后会进行扩容,底层调用了数组拷贝的方法,System.arraycopy()…是这样扩容,所以StringBuffer/StringBuilder时候于字符串频繁拼接的操作。
在java5之后引入的新特性。
自动装箱:基本数据类型自动转换成包装类
自动拆箱:包装类自动转换成基本数据类型。
自动装箱和自动拆箱的好处:方便编程。
“==”比较的是对象的内存地址,不会触发自动拆箱机制(只有±*/等运算的时候才会)
java中为了提高程序的效率,将[-128,127]之间的所有包装对象提前创建好,放到一个方法区的“整数常量池”当中,目的是只要用这个区间的数据就不需要new了,直接从整数型常量池中取出来。
Integer x=100;//x里面并不是保存100,保存的是100这个对象的内存地址
Integer y=100;
System.out.println(x==y);//true
Integer x=128;
Integer y=128;
System.out.println(x==y);//false
Integer类加载的时候,会初始化整数型常量池:256个对象。
池:cache,其实就是缓存机制。
缓存优点:效率高
缓存缺点:耗费内存
缓存机制要重视,大型项目中的重要优化手段就是:cache机制。、
1.获取系统当前时间(精确到毫秒的系统当前时间)
直接 调用无参数构造方法:
Date nowTime = new Date();
2.日期可以格式化吗?
将日期类型Date,按照指定的格式进行转换:Date–转换成具有一个格式日期字符串–>String
SimpleDateFormat是java.text包下的,专门负责日期格式化。
SimpleDateFormat sdf = new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss SSS”);
SimpleDateFormat sdf1 = new SimpleDateFormat(“yyyy/MM/dd HH:mm:ss SSS”);
String nowTimeStr = sdf.format(nowTime);
String nowTimeStr1 = sdf.format(nowTime);
3.将一个日期字符串String转换成Date类型
String time=“2008-08-08 08:08:08 888”;
SimpleDateFormat sdf2 = new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss SSS”);
Date dateTime = sdf2.parse(time);
4.获取自1970年1月1日 00:00:00到当前系统时间的总毫秒数
long begin = System.currentTimeMillis();
java.text.DecimalFormat专门负责数字格式化。
###,###.##表示加入千分位,保留两个小数
###,###.0000表示加入千分位,保留4个小数,不够补0
DecimalFormat df = new DecimalFormat(“数字格式化”);
BigDecimal属于大数据,精度极高。不属于基本数据类型,属于java对象(引用数据类型),这是SUN提供的一个类,专门用在财务软件当中。
创建随机数对象
Random random= new Random();
随机产生一个int类型取值范围内的数字
int num1 = random.nextInt();
产生[1-100]之间的随机数,不能产生101
nextInt翻译为:下一个int类型的数据是101,表示只能取100
int num2 = random.nextInt(101);
1.枚举也是一种引用数据类型。
2.枚举编译之后也是.class文件。
3.枚举的定义:
enum 枚举类型名{枚举值1,枚举值2…}
4.结果只有两种情况,建议使用布尔类型;结果超过两种并且可以一一枚举出来,建议使用枚举。
5.switch语句 支持枚举类型,switch也支持String、int、
低版本的JDK只支持int
高版本的JDK支持Int,String,枚举,byte,short,char因为存在自动类型转换。
1.什么是异常,java提供异常处理机制有什么用?
程序执行过程中出现的不正常情况。
2.异常的作用:增强程序的健壮性。
java语言是很完善的语言,提供了异常处理方式。程序在执行过程中出现了不正常的情况,java把该异常信息打印输出到控制台,供程序员参考。程序员看到异常信息之后,可以对程序进行修改,让程序更加健壮。
3.java语言当中异常是以什么形式存在的呢?
异常在java中以类的形式存在,每一个异常都可以创建异常对象。
4.java异常处理机制
Object下有Throwable(可抛出的)
Throwable下有两个分支:Error(不可处理,直接退出JVM)和Exception(可处理的)
Exception下有两个分支:
Exception的直接子类:编译时异常(要求程序员在编写程序阶段预先对这些异常进行处理,如果不处理编译器报错,因此得名:编译异常)
RunTimeException:运行时异常(在编写程序阶段程序员可以预先处理,也可以不管)
错误,还是异常都是可以抛出的。
所有的错误只要发生,java程序只有一个结果那就是终止程序的执行,退出JVM,错误是不能处理的。
所有的RunTimeException及其子类都属于运行时异常。运行时异常在编写阶段,你可以选择处理,也可以选择不处理。运行时异常还有另外一个名字:未受检异常或者非受控异常UnCheckedException。
所有Exception的直接子类都叫做编译时异常。编译时异常在编译阶段发生的吗?不是,编译时异常表示必须在编写程序的时候预先对这种异常进行处理,如果不处理,编译器报错。编译时异常发生概率较高。编译时异常又被称为受检异常,还有叫做受控异常CheckedException。
编译时异常和运行时异常,都是发生在运行阶段。编译阶段异常是不会发生的。
5.编译时异常因为什么而得名?
因为编译时异常必须编译(编写)阶段预选处理,如果不处理编译器报错,因此得名。所有的异常都是发生在运行阶段,因为只有运行阶段才能new对象,因为异常的发生就是在new对象。
6.编译时异常和运行时异常的区别
编译时异常一般发生的概率比较高。
运行时异常一般发生的概率比较低。
7.java语言中对异常的处理包括两种方式:
第一种方式:在方法声明的位置上,使用throws关键字,抛给上一级
谁调用我,我就抛给谁。类似于推卸责任。
第二种方式:使用try…catch语句进行异常捕捉。
这件事发生了,谁都不知道,因为我给抓住了。异常真正解决。
注意:只要异常没有捕捉,采用上报的方式,此方法的后续代码不会执行。另外需要注意,try语句块中某一行出现异常,该行后面的代码不会执行。try…catch捕捉之后,后续代码会执行。
8.在以后的开发中,处理编译时异常,应该上报还是捕捉呢?怎么选?
如果希望调用者来处理,选择throws上报。
其余捕捉。
9.异常对象的两个方法:getMessage,printStackTrace
//这里只是new了异常对象,但是没有将异常对象抛出,JVM会认为这是一个普通的java对象。
NullPointerException exception = new NullPointerException(“空指针异常!”);
获取异常简单的描述信息,这个信息实际上就是构造方法上面String参数:
String msg = exception.getMessage();
打印异常追踪的堆栈信息,java在后台打印异常堆栈追踪信息的时候,采用异步线程的方式打印:
exception.printStackTrace();
10.我们以后查看异常的追踪信息,应该怎么看可以快速的调试程序呢?
异常追踪信息从上往下一行一行看,注意的是SUN公司写的代码不需要看,主要看自己写的代码。
11.java中怎么自定义异常?
两步:
第一步:编写一个类继承Exception或者RunTimeException
第二步:提供两个构造方法,一个无参数的,一个带有String参数的。
举例:编写一个栈操作异常
public class MyStackOperatorException extends Exception{
public MyStackOperatorException(){
}
public MystackOperatorException(String s){
super(s);
}
}
//创建异常对象
MyStackOperatorException e = new MyStackOperatorException("栈已满,压栈失败!");
//**手动将异常抛出去**
throw e;//这里捕捉没有意义,自己new一个异常自己捉,没有意义。
//栈已满这个信息你需要传递出去
//将以上两段代码合并,手动抛异常
throw new MyStackOperatorException("栈已满,压栈失败!");
重点:自定义异常在开发中的应用!
1.在finally子句中的代码是最后执行的,并且一定会执行,即使try语句块中的代码出现了异常。
finally子句必须和try一起出现,不能单独编写。
2.finally语句通常使用在哪些情况下呢?
通常在finally语句块中完成资源的释放/关闭。
因为finally中的代码比较有保障,即使try语句块的代码出现异常,finally也会正常执行。
3.try和finally,没有catch可以吗?可以
try不可以单独使用,try和finally可以联合使用。
以下代码执行顺序是:
先执行try
再执行finally
最后再执行return;(return语句只要执行,方法必然结束)
try{
System.out.println();
return;
}finally{
System.out.println();
}
4.退出JVM之后,finally语句中的代码就不执行了。
System.exit(0);//退出JVM
final关键字:
final修饰的类无法继承
final修饰的方法无法覆盖
final修饰的变量不能重新赋值
finally关键字:
和try一起使用
finally语句块中的代码一定会执行
finalize标识符:
是一个Object类中的方法名,这个方法是由垃圾回收器GC负责调用的。