Java语言
八种基本数据类型及其包装类
整型:byte,short,int,long
浮点型:float,double
逻辑型:boolean
字符型:char
原始类型
原始类型所占的字节数
包装类
byte
1个字节
Byte
shot
2个字节
Short
int
4个字节
Integer
long
8个字节
Long
float
4个字节
Float
double
8个字节
Double
boolean
1个字节
Boolean
char
2个字节
Character
要注意的是基本数据的包装类很多都实现了享元模式。享元模式就是运用共享技术有效地支持大量细粒度对象的复用。用一个常见的面试题来解释
1.判断如下代码的输出,并说出原因
Integer a1 = 40;Integer a2 = 40;
System.out.println(a1 == a2);
Integer a3 = 200;
Integer a4 = 200;
System.out.println(a3 == a4);
由自动装箱和拆箱可以知道这2种写法是等价的
Integer a1 = 40;Integer a1 = Integer.valueOf(40);
看一下Integer的valueOf方法
public static Integer valueOf(int i) {// i的取值范围为[-128,127]
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
IntegerCache是Ingeter的静态内部类,默认创建了[-128,127]的对象,并放到IntegerCache内部的一个cache数组中,在[-128,127]这个范围内的整数对象,不用创建。直接从IntegerCache中的cache数组中根据下标拿就可以,超出这个范围的每次去创建新的对象。其他几种包装类型的常量池和Integer思路都差不多,源码都很相似。
所以答案如下:
Integer a1 = 40;Integer a2 = 40;
// true
System.out.println(a1 == a2);
Integer a3 = 200;
Integer a4 = 200;
// false
System.out.println(a3 == a4);
包装类缓存的范围如下
包装类
缓存范围
Byte
-128~127
Short
-128~127
Integer
-128~127
Long
-128~127
Character
0~127
2.Java一个char类型可以存储中文吗?
可以,因为Java中使用了Unicode字符,不论中文还是因为固定占用2个字节。
// 中
System.out.println(a);
3.什么是自动装箱,自动拆箱?
自动装箱就是Java自动将原始类型值转换成对应的对象,比如将int的变量转换成Integer对象,这个过程叫做装箱,反之将Integer对象转换成int类型值,这个过程叫做拆箱。因为这里的装箱和拆箱是自动进行的非人为转换,所以就称作为自动装箱和拆箱
自动装箱时编译器调用valueOf将原始类型值转换成对象,同时自动拆箱时,编译器通过调用类似intValue(),doubleValue()这类的方法将对象转换成原始类型值
// jdk1.5 之前的写法Integer tempNum1 = Integer.valueOf(5);
int num1 = tempNum1.intValue();
// jdk1.5之后的写法
Integer tempNum2 = 5;
int num2 = tempNum2;
4.为什么要需要基本数据类型的包装类?
(1)Java是面向对象的语言,很多地方需要使用的是对象而不是基本数据类型。例如,List,Map等容器类中基本 数据类型是放不进去的。
(2)包装类在原先的基本数据类型上,新增加了很多方法,如Integer.valueOf(String s)等
5.既然包装类型能完成所有功能,为啥还需要基本类型?
基本数据类型基于数值,对象类型基于引用。基本数据类型存储在栈的局部变量表中。
而对象类型的变量则存储堆中引用,实例放在堆中,因此对象类型的变量需要占用更多的内存空间。
显然,相对于基本类型的变量来说,对象类型的变量需要占用更多的内存空间。
5.写出如下代码的输出
Integer i1 = 40;Integer i2 = 40;
Integer i3 = 0;
Integer i4 = new Integer(40);
Integer i5 = new Integer(40);
Integer i6 = new Integer(0);
System.out.println(i1 == i2);
System.out.println(i1 == i2 + i3);
System.out.println(i1 == i4);
System.out.println(i4 == i5);
System.out.println(i4 == i5 + i6);
System.out.println(40 == i5 + i6);
输入如下
Integer i1 = 40;Integer i2 = 40;
Integer i3 = 0;
Integer i4 = new Integer(40);
Integer i5 = new Integer(40);
Integer i6 = new Integer(0);
// true
// Integer.valueOf()用了常量池,看上面的源码
System.out.println(i1 == i2);
// true
// + 操作会导致左右2边都转成基本数据类型
// 具体原因看下面
System.out.println(i1 == i2 + i3);
// false
// Integer.valueOf()使用常量池中的对象
// new Integer每次会创建新对象,
System.out.println(i1 == i4);
// false
// 2个不同的对象
System.out.println(i4 == i5);
// true、
// 解释在最下面
System.out.println(i4 == i5 + i6);
// true
// 解释在最下面
System.out.println(40 == i5 + i6);
语句i4 == i5 + i6,因为+这个操作符不适用于Integer对象,首先i5和i6进行自动拆箱操作,进行数值相加,即i4 == 40。然后Integer对象无法与数值进行直接比较,所以i4自动拆箱转为int值40,最终这条语句转为40 == 40进行数值比较
抽象类和接口
接口和抽象类的相似性
接口和抽象类的区别
StringBuffer和StringBuilder的区别
先来看String类的实现
public final class Stringimplements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
}
先来说一下final关键字的作用
1.final修饰类时,表明这个类不能被继承
2.final修饰方法,表明方法不能被重写
3.final修饰变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象
可以看到String类和保存变量的value数组都被final修饰,表明String类是不可变的。
StringBuffer和StringBuilder都继承自AbstractStringBuilder类,看一下AbstractStringBuilder类的定义
/**
* The value is used for character storage.
*/
char[] value;
}
看到区别了吗?value数组没有用private和final修饰,说明了StringBuffer和StringBuilder是可变的。
而StringBuilder和StringBuffer的方法是差不多的,只不过StringBuffer在方法上添加了
synchronized关键字,所以在多线程环境下我们要用StringBuffer来保证线程安全,单线程环境下用StringBuilder来获得更高的效率。
看2个类中同一个方法的定义
// StringBuffer@Override
public synchronized StringBuffer append(char[] str) {
toStringCache = null;
super.append(str);
return this;
}// StringBuilder
@Override
public StringBuilder append(char[] str) {
super.append(str);
return this;
}
因为StringBuffer和StringBuilder的实现类似,所以性能比较就落在String和StringBuilder之间了。
1.String是不可变对象,每次操作都会生成新的String对象,然后将指针指向新的对象。
2.抽象类AbstractStringBuilder内部提供了一个自动扩容机制,当发现长度不够的时候,会自动进行扩容工作(具体扩容可以看源码,很容易理解),会创建一个新的数组,并将原来数组的数据复制到新数组,不会创建新对象,拼接字符串的效率高。
用源码证实一下
// Stringpublic String substring(int beginIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
int subLen = value.length - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}// StringBuilder
@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}
介绍完毕,所以你应该知道这道题应该怎么答了
常见面试题
1.说一下String StringBuffer StringBuilder的区别?
我用例子解释一下第五条
public static void main(String[] args) {String a = "a";
String b = "b";
String c = a + b;
String d = "a" + "b" + "c";
}
反编译class文件后是这样的
public static void main(String[] args) {String a = "a";
String b = "b";
(new StringBuilder()).append(a).append(b).toString();
String d = "abc";
}
看string d,理解了吗?
同时看string c的拼接过程,先生成一个StringBuilder对象,再调用2次append方法,最后再返回一个String对象,知道String比StringBuilder慢的原因了吧
算法和数据结构
二叉树
定义
二叉树是一种树形结构,它的特点是每个节点至多只有两颗子树(即二叉树中不存在度大于2的几点),并且,二叉树的子树有左右之分,其次序不能任意颠倒
性质
遍历二叉树
先序遍历:根左右
中序遍历:左根右
后序遍历:左右根
记忆方法:左右的位置不变,先序遍历根在最前面,中序遍历根在中间,同理,后序遍历根就在最后面了
排序
冒泡排序
public class BubbleSort {//交换元素顺序
public static void swap(int[] a, int i, int j) {
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
//冒泡排序
public static void bubbleSort(int[] a) {
for (int i=0; i<a.length - 1; i++) {
for (int j=0; j<a.length - 1 - i; j++) {
if (a[i] > a[i + 1]) {
swap(a, i, i + 1);
}
}
}
}
public static void main(String[] args) {
int[] a = {1, 5, 2, 4, 7, 6};
bubbleSort(a);
//[1, 2, 4, 5, 6, 7]
System.out.println(Arrays.toString(a));
}
}
选择排序
public class SelectSort {public static void swap(int[] a, int i, int j) {
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
public static void selectSort(int[] a) {
for (int i=0; i<a.length; i++) {
int min = a[i];
int index = i;
for (int j=i; j<a.length; j++) {
if (a[j] < min) {
min = a[j];
index = j;
}
}
if (index != i) {
swap(a, i, index);
}
}
}
public static void main(String[] args) {
int[] a = {1, 5, 2, 4, 7, 6};
selectSort(a);
//[1, 2, 4, 5, 6, 7]
System.out.println(Arrays.toString(a));
}
}
快速排序
public static int sort(int[] a, int low, int high) {
int key = a[low];
while (low < high) {
//从high所指位置向前搜索找到第一个关键字小于key的记录和key互相交换
while (low < high && a[high] >= key) {
high--;
}
a[low] = a[high];
//从low所指位置向后搜索,找到第一个关键字大于key的记录和key互相交换
while (low < high && a[low] <= key) {
low++;
}
a[high] = a[low];
}
//此时low和key相等
a[low] = key;
return low;
}
public static void quickSort(int[] a, int low, int high) {
if (low < high) {
int key = sort(a, low, high);
quickSort(a, low, key - 1);
quickSort(a, key + 1, high);
}
}
public static void main(String[] args) {
int[] a = {1, 5, 2, 4, 7, 6};
quickSort(a, 0, a.length - 1);
//[1, 2, 4, 5, 6, 7]
System.out.println(Arrays.toString(a));
}
}
归并排序
假设初始序列含有n个记录,则可看成是n个有序的子序列,每个子序列的长度为1,然后两两归并,得到$ \lceil \frac{x}{2} \rceil $个长度为2或1的有序子序列,在两两归并,…,如此重复,直至得到一个长度为n的有序序列为止,这种排序方法称为2-路归并排序
public static void sort(int[] src) {
int[] temp = new int[src.length];
msort(src,temp,0,src.length-1);
}
public static void msort(int[] src,int[] dest,int left,int right) {
if (left < right) {
int mid = (left + right) / 2;
msort(src,dest,0,mid);
msort(src,dest,mid+1,right);
merge(src,dest,0,mid,right);
}
}
public static void merge(int[] src,int[] dest,int left,int mid,int right) {
int i = left;//左边数组的游标
int j = mid + 1;//右边数组的游标
int index = 0;//dest起一个中途存储的作用,这个是dest数组的游标
while (i <= mid && j <= right) {
if (src[i] <= src[j]) {
dest[index++] = src[i++];
} else {
dest[index++] = src[j++];
}
}
//复制左边剩余的数组
while (i <= mid) {
dest[index++] = src[i++];
}
//复制右边剩余的数组
while (j <= right) {
dest[index++] = src[j++];
}
index = 0;
while (left <= right) {
src[left++] = dest[index++];
}
}
public static void main(String[] args) {
int[] arr = {7,5,3,4,2,1,6,2,9,8};
sort(arr);
//[1, 2, 2, 3, 4, 5, 6, 7, 8, 9]
System.out.println(Arrays.toString(arr));
}
}
二分查找
public class Search {public static void main(String[] args) {
int test1[] = {3,4,5,1,2,7,9};
//false
System.out.println(binarySearch(test1, 10));
//true
System.out.println(binarySearch(test1, 9));
}
//从array数组中查找target的值,找到返回true,否则返回false
public static boolean binarySearch(int[] array, int target) {
//进行二分查找先进行排序
Arrays.sort(array);
int left = 0;
int right = array.length -1;
//注意是小于等于,如从123456中查找6,没等于不行
while (left <= right) {
int mid = (left + right) >> 1;
if (target == array[mid]) {
return true;
} else if (target > array[mid]) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return false;
}
}
深度优先搜索
给定图G的初始状态是所有顶点均未曾访问过,在G中任选一顶点v为初始出发点(源点或根节点),则深度优先遍历可定义如下:首先访问出发点v,并将其标记为已访问过,然后从v出发搜索v的每个未曾访问的的邻节点w,并以w为新的出发点继续进行深度优先遍历,直至图中所有和源点v有路径相通的顶点均被访问为止。若此时图中仍有未访问的顶点,则另选一个尚未访问的顶点作为新的源点重复上述过程,直至图中所有顶点均已被访问为止
##广度优先搜索
设图G的初始状态是所有顶点均未访问过。以G中任选一顶点v为起点,则广度优先搜索定义为:首先访问出发点v,接着依次访问v的所有邻接点,,,…,,然后再依次访问与,,,…,邻接的所有未曾访问过的顶点。以此类推,直至图中所有和起点v有路径相通的顶点都已访问到为止。此时从v开始的搜索过程结束。
若G是连通图,则一次就能搜索完所有节点;否则,在图G中另选一个尚未访问的顶点作为新源点继续上述搜索过程,直至G中所有顶点均已被访问为止
用户点击页面到收到结果中间发生了什么,从servlet的角度
MySQL索引优化策略
数据库事务的四个特性
MySQL为什么要用B+树实现
voliate
线程池
HashTable和ConcurrentHashMap的区别
Lock
CountDownLatch
Spring MVC执行流程
单例模式的5种写法
Spring AOP和IOC
spring ioc的用处
nio
netty
jvm
try catch
求出数据库中重复的记录