1.java跨平台原理:
java具有一次编译,到处运行的特点,
.java文件经过编译后生成.class文件(字节码文件),字节码文件再通过java虚拟机(jvm)解释为机器码运行,
当然c/c++也可以跨平台运行,但是却需要在平台上用对应编译器重新编译,而java不用;
java虚拟机的作用:将字节码文件转化为对应平台的机器码并执行;
java语言的执行需要编译和解释,其中解释是由java虚拟机中的解释器执行的。
ps:当跨平台运行时:
由于跨平台原理,java相比c的运行要慢一步,因为java需要先将class文件转化为机器码再执行,而c经过二次编译后可以直接执行
2.string对象,StringBuffer,StringBuilder区别简述
string是final的,不可变的;
StringBuffer是可变类,线程安全但效率低;
StringBuilder是可变的,线程不安全但是效率高;
3.继承,封装,多态
继承:子类可以从父类中继承所有的非私有化属性和方法;
封装:隐藏实现细节,将属性私有化,提供公有方法(setget)访问私有属性,可以
提高程序安全性,隐藏代码细节,提高系统可维护性
多态:多态性是对象多种表现形式的体现,
比如A extends B
可以有A a = new A();
也可以有B b = new A();//父类的引用指向子类的类型
对象能执行哪些方法和左边类型有关
4.java的安全性
并且,在强制转换方面,只有当符合转换规则下才能强转成功;
5.J2EE,J2SE 和J2ME
J2ME现在已经不怎么使用了,规范名称是java2平台下的微缩版(Micro Edition
J2SE是大家最先接触的,标准版,
J2EE(企业版),可以理解为J2SE的进阶: J2SE包含于J2EE中
6.JVM,JRE和JDK
JVM:
import java.text.ParseException; import java.util.Calendar; public class Test { public static void main(String[] args) throws ParseException { Calendar cal = Calendar.getInstance(); cal.add(Calendar.DATE, 0); System.out.println(cal.getTime()); } }
打印昨天的当前时刻:
import java.text.ParseException; import java.util.Calendar; public class Test { public static void main(String[] args) throws ParseException { Calendar cal = Calendar.getInstance(); cal.add(Calendar.DATE, -1); System.out.println(cal.getTime()); } }
8.&和&& , |和|| 的区别和联系
&和 | 分别称为按位与和按位或,对应还有&&(逻辑与) 和 || (逻辑或)
一般来说,逻辑与,逻辑或都是用来判断true 或者 false,而按位操作可以得出具体的值;
另外,逻辑与操作有个坑:&& 如果符号左边为假则右侧表达式不会继续执行。
9.排序方法小结
排序方法 | 最优时间 | 平均时间 | 最坏时间 | 空间复杂度 | 稳定性 |
直接插入 | O(n) | O(n²) | O(n²) | O(1) | 稳定 |
二分插入 | O(n) | O(n²) | O(n²) | O(1) | 稳定 |
冒泡排序 | O(n) | O(n²) | O(n²) | O(1) | 稳定 |
快速排序 | O(nlogn) | O(nlogn) | O(n²) | O(logn) | 不稳定 |
希尔排序 | O(n1.25) | O(1) | 不稳定 | ||
直接选择 | O(n²) | O(n²) | O(²) | O(1) | 不稳定 |
堆排序 | O(nlogn) | O(nlogn) |
O(nlogn) |
不稳定 | |
归并排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(n) | 稳定 |
基数排序 | O(d(rd+n)) | O(d(rd+n)) | O(d(rd+n)) | O(rd+n) | 稳定 |
10.关于冒泡排序
大体思想就是对于一队数字,相邻数字通过比较两两交换,实现从小到大或者从大到小排列。利用两个for循环实现,执行内层for循环可以理解为进行一趟比较,每次比较都可以从无序序列中将一个max或者min值交换到首或者尾(想从大到小排列,就每次比较的时候将较小的[i]交换至[i+1]处,反之同理),外层for循环就是执行数趟,直到全部比较结束(添加flag做标志的话就可以减少一些比较次数,在数列第一次基本有序时就跳出循环~)
import java.util.Arrays; public class MaoPao { public static void main(String[] args) { MaoPao maoPao = new MaoPao(); int[] a= {23,46,1,89,43,22,23,44,3}; //maopaosort(a); int [] maopaosort = maoPao.maopaosort(a); //System.out.println(Arrays.toString(maopaosort));//可以直接输出数组 //或者用循环 for (int num: maopaosort) { System.out.print(num+"\t"); } } public int [] maopaosort(int [] b) { int temp = 0; for (int i = 0; i < b.length - 1; i++) { int flag = 0;//标记无序 for (int j = 0; j < b.length - 1 - i; j++) { if (b[j] >= b[j + 1]) {//若左大右小,则data交换 temp = b[j]; b[j] = b[j + 1]; b[j + 1] = temp; flag = 1;//标记有序 } } if (flag == 0) {//完成某趟排序后发现已经基本有序,就跳出,可以减少一些比较次数 break; } } return b; } }
11.关于简单选择排序
思想:每一趟都能找出一个当前最小值,比如第一趟找到的最小值放在a[0],第二趟找到的次小值就放在a[1],以此类推,在数列中有相同关键字有序的时候很不稳定,因为
交换后会破坏原有关键字的前后顺序,比如1,3,3,2, ,3和2比较,如果有多个3就只能把第一个3换过去,排序后:1,2,3,3 但是原来的 3,3变为3,3,相对位置改变了,现在前后颠倒(虽然看起来一样hh)
public class SelectSort{ public int[] sort(int arr[]) { int temp = 0; for (int i = 0; i < arr.length - 1; i++) { // 认为目前的数就是最小的, 记录最小数的下标 int minIndex = i; for (int j = i + 1; j < arr.length; j++) { if (arr[minIndex] > arr[j]) { // 修改最小值的下标 minIndex = j; } } // 当退出for就找到这次的最小值,就需要交换位置了 if(i != minIndex) { temp = arr[i]; arr[i] = arr[minIndex]; arr[minIndex] = temp; } } return arr; } public static void main(String[] args) { SelectSort selectSort = new SelectSort(); int[] array = {2,5,1,6,4,9,8,5,3,11,2,0}; int[] sort = selectSort.sort(array); for (int num:sort){ System.out.print(num+"\t"); } } }
12.关于直接插入排序
思想:其实并不涉及手动插入,而是像整理扑克牌一样,一般我们都习惯将牌升序或者降序排列来方便出牌(也有人会用别的方法,我就是举个栗子)
发牌后发现基本都是乱序,假设我们从左开始理牌,想要让牌从小到大排列。依然是比较思想,先用第二张和第一张比较,然后就是第三张和前两张比较,看起来和选择排序有些像,
用例子说明:
发牌后手中牌:2,6,3,1,13,7,2
第一趟:2,6,3,1,13,7,2 无交换
第二趟:2,3,6,1,13,7,2 3和6交换
第三趟:1,2,3,6,13,7,2 1先和6交换,然后发现1还可以和3交换,交换后,又双叒发现能和2交换,于是就是第三趟排序后就是前面那样子;
第四趟:1,2,3,6,13,7,2 无交换
第五趟:1,2,3,6,7,13,2 7和13交换
第六趟:1,2,2,3,6,7,13 2和13交换,然后2又和7,交换,....交换............最后和3交换,完成排序
所以,和选择排序还是很有区别的,选择排序是只找当前最小值,然后不断放在序列头部,直接插入虽然也是头部有序,但并不一定是每一趟都能固定好关键字的位置,
简单选择每趟排序都能确定一个关键字的位置,直到最后一趟结合素其位置都不会改变,这点在数据结构中好像有过考察。
public class InsertSort { private int[] sort(int[] arr) { //如果传入的数组为空或者只有一个值,就直接返回 if (arr == null || arr.length < 2) { return arr; }//不为空则进循环判断 // 外层循环控制总数量 for (int i = 1; i < arr.length; i++) { //内层循环依次减少并提出结果 for (int j = i; j > 0; j--) { //如果当前数字小于前一个,则交换,否则不变 if (arr[j] < arr[j - 1]) { int temp = arr[j]; arr[j] = arr[j - 1]; arr[j - 1] = temp; } else { break; } } } return arr; } public static void main(String[] args) { InsertSort insertSort = new InsertSort(); int[] array = {2, 5, 1, 6, 4, 9, 8, 5, 3, 1, 2, 0}; int[] sort = insertSort.sort(array); for (int num : sort) { System.out.print(num + "\t"); } } }
13.面向对象与面向过程
这个问题和实际编程其实没什么关系,初学者也许知道含义但是又不太好解释出来。
C,是最常见的面向过程编程语言,(其实还有Fortran语言),c++,java,.net都是面向对象编程语言
1.面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了,
也就是说 面向过程是以实现功能的函数开发为主
2.面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。
其他显著区别:
个人理解:面向过程就是那种很细腻的实现功能,环环相扣,很多底层细节都处理的很好,而面向对象更适合于处理宏观上的复杂关系。
14.重载overload与重写(覆盖)override
重载:同一个类中有一些方法,其方法名相同,但是参数个数,参数类型不同
重载规则:
- 被重载的方法必须改变参数列表(参数个数或类型不一样);
- 被重载的方法可以改变返回类型;
- 被重载的方法可以改变访问修饰符;
- 被重载的方法可以声明新的或更广的检查异常;
- 方法能够在同一个类中或者在一个子类中被重载。
- 无法以返回值类型作为重载函数的区分标准。
和重写的主要区别:重载范围是一个类内,重写存在于是父子类关系之中
重写(override):重写需要类与类有继承关系,比如A extends B,而且是针对非静态的方法
我们都知道,子类可以继承父类的属性,方法(注意私有类型除外),倘若我们想在父类方法基础上做一些修改,添加,那就叫重写,字面意思,子类可以对父类的方法进行修改(重写后如果还想用父类的原方法就需要super关键字,
举例:(注意都在一个包下)
父类Animal
public class Animal {
public void run(){
System.out.println("动物都会跑");
}
}
子类Cat
public class Cat extends Animal {
public void run(){
super.run();//自己重写父类方法后还想再调用父类的那个方法就用super关键字
System.out.println("猫会跑");
System.out.println("猫跑起来静悄悄");
}
}
测试类
public class demo4 {
public static void main(String[] args) {
//重写后都是输出子类方法
Cat cat = new Cat();
Animal animal = new Cat();//向上转型
animal.run();
}
}
输出结果:
动物都会跑
猫会跑
猫跑起来静悄悄
如果不用super调用 ,那就只会输出后两句:
猫会跑
猫跑起来静悄悄
总结:方法的重写(Overriding)和重载(Overloading)是java多态性的不同表现,重写是父类与子类之间多态性的一种表现,重载可以理解成多态的具体表现形式。
15.this与super
16.static关键字
字面意思,static(静态修饰)
简单来说:
static变量是属于整个类的,也称为类变量,所以非静态的方法我们想引用时必须用new对象才行,再用对象来调用。
public class equals { public static void main(String[] args) { StringBuffer a = new StringBuffer("aa"); StringBuffer b = new StringBuffer("aa"); System.out.println(a.equals(b));//false System.out.println(a==b);//false System.out.println("+++++分界线+++++"); String s1,s2,s3 = "abc", s4 ="abc" ; s1 = new String("abc"); s2 = new String("abc"); System.out.println(s1==s2);//false 两个变量的内存地址不一样,指向的对象不一样, System.out.println( s1.equals(s2));//true //两个变量的所包含的内容是abc,故相等。 } }
代码输出如下:
false
false
+++++分界线+++++
false
true
分析:
前两个sout打印false是因为StringBuffer类中没有重写过equals方法,所以本质还是比较对象,而对象不同所以分配的内存地址也不一样的,所以是false
- 无论是 put 还是 get 的时候,都需要得到key的哈希值,去定位key的数组下标;
- 在 get 的时候,需要调用equals方法比较是否有相等的key存储过
第二点解释了为什么我们需要重写equals,==只是比较地址,并不能确认是否有相同的key,不重写的话上一个 话题也提到了,会返回false,所以需要重写equals
那么为什么重写equals后还要重写hashcode?
由于在hashMap中在put 时,散列函数根据它的哈希值找到对应的位置,如果该位置有元素,首先会使用hashCode方法判断,如果没有重写hashCode方法,那么即使俩个对象属性相同hashCode方法也会认为他们是不同的元素(另外key值相同却被认为是不同,所以会出现相同的key,而map规定不应该出现重复key),又因为Set是不可以有重复的,所以这会产生矛盾,那么就需要重写hashCode方法
一句话,如果不重写hashcode方法,任何对象的hashcode值都不相等(即使逻辑上key相同,我们希望它显示相等时,结果依然是不等)
由于没有重写hashCode方法,所以put操作时,key(hashcode1)–>hash–>indexFor–>最终索引位置 ,而通过key取出value的时候 key(hashcode1)–>hash–>indexFor–>最终索引位置,由于hashcode1不等于hashcode2,导致没有定位到一个数组位置而返回逻辑上错误的值null(也有可能碰巧定位到一个数组位置,但是也会判断其entry的hash值是否相等,上面get方法中有提到。)
此段引用:https://www.cnblogs.com/chengxiao/p/6059914.html
所以,在重写equals的方法的时候,必须注意重写hashCode方法,同时还要保证通过equals判断相等的两个对象,调用hashCode方法要返回同样的整数值。而如果equals判断不相等的两个对象,其hashCode可以相同(只不过会发生哈希冲突,应尽量避免)
To be continued......