数据类型
Java有哪些数据类型
- 基本数据类型
- 数值型(long、int、short、byte)
- 浮点类型(float、double)
- 字符型(char)
- 布尔型(boolean)
- 字符型
基础语法
switch 是否能作用在 byte 上,是否能作用在 long 上,是否能作用在 String 上
在Jdk1.5之前,switch(expr)中,expr只能是byte、short、char、int
在Jdk5之后,Java中引入了枚举类,expr也支持枚举
在Jdk7之后,expr也支持String,但是长整型long目前在所有版本中都无法实现。
Math.round(11.5) 等于多少?Math.round(-11.5)等于多少
Math.round(11.5) 为12,Math.round(-11.5)为-11,四舍五入的原理是在参数上家0.5然后再向下取整。
float f=3.4;是否正确
不正确
因为所有默认的浮点型都是double类型,即双精度类型,所以双精度double向下转为float单精度会丢失精度。因此需要进行强转类型float f=(float)3.4 或者是float f = 3.4F
short s1 = 1; s1 = s1 + 1;有错吗?short s1 = 1; s1 += 1;有错吗
short s1 = 1; s1 = s1 + 1 有错, 1为int类型 s1+1运算结果为int类型,需要强制转换才能赋值给short
short s1 = 1; s1 += 1; 是对的,因为 +=中有隐含类型转换。
两个对象的 hashCode()相同,则 equals()也一定为 true,对吗?
两个对象equals相等,则它们的hashcode必须相等,反之则不一定。
两个对象==相等,则其hashcode一定相等,反之不一定成立。
String str1 = "通话";
String str2 = "重地";
System.out.println("str1 is :"+str1.hashCode());
System.out.println("str2 is :"+str2.hashCode());
System.out.println(str1.equals(str2));
=========================
//运行结果
str1 is :1179395
str2 is :1179395
false
因为在散列表中,hashCode()相等即两个键值对的哈希值相等,然而哈希值相等,并不一定能得出键值对相等。
&和&&的区别
&&为逻辑与,与&按位与一样的是两边都为true才为true,但是逻辑与如果左边是false那么就不会再判断右边的情况,也称之为短路与
||逻辑或与|按位或的区别也是如此
String、StringBuffer、StringBuilder的区别
String 底层为final修饰的char数组
private final char value[];
StringBuffer 和StringBuilder底层为transient 修饰的char数组,无法进行序列化
而StringBuffer底层的append()方法采用的是CAS即线程安全,而StringBuilder为线程不安全的,但效率高于StringBuffer
private transient char[] toStringCache;
String str="i"与 String str=new String(“i”)一样吗?
String str1 = "abc"直接str1存储的是常量池中的地址值
String str2 = new String(“abc”) str2存储的是在堆中的 new String()的地址值
new String()存放的是跟str1相同指向的常量池的地址值。
**str1 == str2 为false 因为String类型的 ==比较的的是引用的地址是否是同一片地址
str1.equals(str2) 为true ,因为 equals比较的是两个引用的值是否相等**
new String(“abc”)创建了几个对象
如果常量池中没有“abc”对象,那么在常量池中创建一个“abc”对象
还有一个就是new 一个String的构造器,将"abc"对象传进去并获取到对应的hash
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
访问修饰符
public,private,protected,以及不写(默认)时的区别
修饰符 | 当前类 | 同包下 | 子类 | 其他包 |
public | √ | √ | √ | √ |
protected | √ | √ | √ | × |
default | √ | √ | × | × |
private | √ | × | × | × |
break ,continue ,return 的区别及作用
break 跳出总上一层循环,不再执行循环(结束当前的循环体)
continue 跳出本次循环,继续执行下次循环(结束正在执行的循环 进入下一个循环条件)
return 程序返回,不再执行下面的代码(结束当前的方法 直接返回)
面向对象
什么是面向对象编程,什么是面向过程编程
面向对象是模型化的,你只需要抽象一个类就行了,这个类会帮你完成需要完成的事情,当你需要的时候去使用这个类就可以,至于这个类如何实现不需要去管。
面向过程是具体化的,每一个问题,没一件需要做的事情都要一步一步的去实现。
面向对象优点
易维护、易扩展、易复用,实现系统的低耦合。
缺点
性能相较于面向过程低
面向过程编程优点
性能比面向对象高
缺点
没有面向对象易维护、易复用、易扩展
面向对象的三大特点
封装: 把一个对象私有化,并且提供一些可以被外界访问的属性的方法。
继承: 使用已有的类作为基础新建一个依赖于这个类的新类的技术,新建的类可以自定义一些方法以及变量,也可以使用父类中非私有的方法及变量,但不能选择性的继承父类 。
多态: 程序中定义的引用变量无法在编程时确定所指向的具体的类型和不确定通过该引用调用的方法,一切都是在运行的时候才会确定。
面向对象的五大原则
- 单一职责原则
就一个类而言,应该仅有一个引起它的变化的原因,即一个类只有一个功能 - 里氏替换原则
一个软件实体如果使用的是一个父类的话,那么一定适用于其子类,而且它察觉不出父类对象和子类对象的区别。也就是说,在软件里面,把父类都替换成它的子类,程序的行为没有变化。 - 开放封闭原则
对扩展开放,对修改封闭 - 依赖倒置原则
抽象不应依赖于细节,细节应该依赖于抽象 - 接口分离原则
设计时采用多个与特定客户类有关的接口比采用一个通用的接口要好,多个专用接口优于一个单一的通用接口。
类与接口
抽象类与接口的对比
抽象类是用来捕捉子类的通用特性的,是抽象方法的集合。
从设计层面上来说,抽象类是对类的抽象,是一种设计模板,接口是行为的规范。
相同点
接口和抽象类都不能实例化。
都位于继承的顶端,用于被其他方法去实现或者继承。
都包含抽象方法,都需要子类去实现这些抽象方法.
不同点
抽象类 | 接口 | |
声明 | abstract关键字声明 | interface关键字声明 |
实现 | 子类通过extends关键字继承于实现类,子类如果是非抽象类那么需要实现抽象类所有方法 | 子类通过implement实现接口,并且需要实现接口所有方法 |
构造器 | 抽象类可以有构造器 | 没有构造器 |
访问修饰符 | 可以设置任何修饰符 | 默认支持public,不支持private 或者 protected |
多继承 | 只能继承一个抽象类 | 可以实现多个接口 |
声明字段 | 可以声明任何字段 | 字段默认static和final |
Java8接口中引入默认方法可以不被子类实现和静态方法,以此减少抽象类与接口的差异
对象引用
hashCode的作用
hashCode方法可以这样理解:它返回的就是根据对象的内存地址换算出的一个值。这样一来,当集合要添加新的元素时,先调用这个元素的hashCode方法,就一下子能定位到它应该放置的物理位置上。如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;如果这个位置上已经有元素了,就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址。这样一来实际调用equals方法的次数就大大降低了,几乎只需要一两次。
对象赋值
深拷贝和浅拷贝
- 浅拷贝:被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象。即都是引用指向的都是同一块堆地址
- 深拷贝:被复制对象的所有变量都含有与原来的对象相同的值,而那些引用其他对象的变量将指向被复制过的新对象.,而不再是原有的那些被引用的对象。换言之.深拷贝把要复制的对象所引用的对象都复制了一遍。即新建了一个一模一样的引用地址和引用。
IO流
IO流都分哪几种
- 按照流的流向,可以分为输入流和输出流
- 按照操作单元划分,可以分为字符流和字节流
- 按照流的角色划分,节点流和处理流
Java Io 流共涉及 40 多个类,这些类看上去很杂乱,但实际上很有规则,而且彼此之间存在非常紧密的联系, Java I0 流的 40 多个类都是从如下 4 个抽象类基类中派生出来的。
- InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
- OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。
BIO、NIO、AIO的区别
- BIO:Block IO同步式阻塞IO,就是我们平时使用的传统IO,它的特点是模式简单方便,处理并发能力弱。
- NIO:Non IO同步式非阻塞IO,是传统IO的升级,客户端和服务器端通过Channel(通道)通讯,实现了多路复用。
- AIO:Asynchronous IO是NIO的升级,也叫NIO2,实现了异步非阻塞IO,异步操作基于事件与回调机制。
容器
常用容器
Java容器分为Collection和Map两大类,Collection集合的实现类有List、Set、Queue。
List和Set和Map的区别
- List一个有序容器,存入和取出的顺序是一样的,元素可以重复,可以为多个null值,元素都有索引。
- Set 一个无序容器,存入和取出的顺序可能不一样,元素不可以重复,只能存一个null值。
-Map是一个键值对的组合,存储建、值之间的映射。Key无序且唯一,Value可重复,且无要求有序。
Collection子类
怎么确保一个集合不能被修改
可以使用 Collections. unmodifiableCollection(Collection c) 方法来创建一个只读集合,这样改变集合的任何操作都会抛出 Java. lang. UnsupportedOperationException 异常。
示例代码如下:
List<String> list = new ArrayList<>();
list. add("x");
Collection<String> clist = Collections. unmodifiableCollection(list);
clist. add("y"); // 运行时此行报错 java.lang.UnsupportedOperationException
System. out. println(list. size());
List
- ArrayList:Object数组
- Vector:Object数组
- LinkedList:双向循环列表
RandomAccess接口
RandomAccess接口是一个空接口,没有抽象方法,其中ArrayList实现了该接口,而LinkedList没有实现该接口。
public static <T>
int binarySearch(List<? extends Comparable<? super T>> list, T key) {
if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD)
return Collections.indexedBinarySearch(list, key);
else
return Collections.iteratorBinarySearch(list, key);
}
根据源码可以看到回去判断一个list是否有实现RandomAccess类来区分调用哪种
实现RandomAccess的List集合会调用For循环,而未实现的会通过迭代器进行循环。
通过代码验证得知有实现接口的List进行For循环的效率远高于迭代器
而没有实现接口的For循环的效率远低于迭代器循环。
结论
RandomAccess接口这个空架子的存在,是为了能够更好地判断集合是否ArrayList或者LinkedList,从而能够更好选择更优的遍历方式,提高性能!
具体代码实现可以看:
ArrayList
ArrayList的优缺点
优点
- ArrayList底层是由数组形式实现,是一种随机访问模式。实现了RandomAccess接口,查询的时候非常快。
- ArrayList在队尾添加一个元素的时候非常方便。
缺点
- 删除元素的时候,需要做一次元素复制操作,如果要复制的元素很多,会很费性能
- 添加元素的时候,也需要做一次元素赋值操作,理由同上。
ArrayList和LinkedList区别
- 数据结构:ArrayList是动态数组结构,LinkedList是双向链表数据结构
- 随机访问效率:ArrayList随机访问效率要比LinKendList效率高,因为LinkedList是线性数据存储的,需要指针从前往后依次查找。
- 增加和删除:非队首或队尾,LinkedList要比ArrayList效率高,因为ArrayList删除或增加会影响到其他元素下标。
- 内存占用空间:LinkedList比ArrayList更占用内存,因为LinkedList节点不光存储元素数据,还存储了前后节点的指针引用。
ArrayList 和 Vector 的区别是什么?
这两个类都实现了 List 接口(List 接口继承了 Collection 接口),他们都是有序集合
线程安全:Vector 使用了 Synchronized 来实现线程同步,是线程安全的,而 ArrayList 是非线程安全的。
性能:ArrayList 在性能方面要优于 Vector。
扩容:ArrayList 和 Vector 都会根据实际的需要动态的调整容量,只不过在 Vector 扩容每次会增加 1 倍,而 ArrayList 只会增加 50%。
Map接口
HashMap的实现原理
HashMap是基于Hash算法实现的。
当我们put元素的时候,它会利用键对象的hashCode()方法计算hashCode,通过hashCode找到对应的存储地址,如果对应地址没有值的话,放到该地址。
如果对应的地址有值的话,通过equals()方法判断key是否相等,相等则直接覆盖对应的value,不相等的话,将当前的key-value存入链表中
JDK7和JDK8中HashMap的不同
JDK8的实现如此
不同 | JDK7 | JDK8 |
存储结构 | 数组+链表 | 数组+链表+红黑树 |
初始化方式 | 单独函数:inflateTable() | 直接集成到resize()函数中 |
hash值的计算/扰动处理 | 9次扰动=4次位运算+5次异或运算 | 2次扰动= 1次位运算+1次异或于运算 |
插入数据方式 | 头插入(先将原位置的数据后移一位,再插入数据到该位置;会出现逆序或环形链表死循环问题) | 尾插入(直接插入到红黑树/链表尾部,不会出现逆序或环形链表死循环) |
扩容后存储位置的计算方式 | 全部按照原来方法进行计算(即hashCode ->> 扰动函数 ->> (h&length-1)) | 按照扩容后的规律计算(即扩容后的位置=原位置 or 原位置 + 旧容量) |
插入与扩容 | 先扩容后插入(会出现无效扩容) | 先插入再扩容 |
补充
- 扩容后数据存储位置的计算方式也不一样:1. 在JDK1.7的时候是直接用hash值和需要扩容的二进制数进行&(这里就是为什么扩容的时候为啥一定必须是2的多少次幂的原因所在,因为如果只有2的n次幂的情况时最后一位二进制数才一定是1,这样能最大程度减少hash碰撞)(hash值 & length-1)
- 什么是扰动处理
加大哈希码低位的随机性,使得分布更均匀,从而提高对应数组存储下标位置的随机性 & 均匀性,最终减少Hash冲突
HashMap插入或更新流程
HashMap问题总结
哈希表如何解决Hash冲突
为什么HashMap具备下述特点:键-值(key-value)都允许为空、线程不安全、不保证有序、存储位置随时间变化
为什么 HashMap 中 String、Integer 这样的包装类适合作为 key 键
补充
- hashcode不可变;
- String内部会维护一个hash缓存,在第一次使用时才会计算,从而避免每次计算;
- 对于Integer的hashcode是返回他自身的int值,重复概率高,并且低位特性不随机,所以不推荐使用Integer类型.
HashMap和HashTable的区别
不同 | HashMap | HashTable |
线程安全 | 非线程安全 | 线程安全 |
效率 | 高于HashTable | 低于HashMap(基本被淘汰,减少使用) |
key值是否为null | 可以存在一个key值为null,但只能有一个 | 一旦key值为null,就会报空指针异常 |
底层数据结构 | JDK1.8的时候底层链表大于阈值(默认8)转为红黑树 | 没有此类机制 |
ConcurrentHashMap 实现原理
数据结构:JDK1.7的 ConcurrentHashMap 底层采用 分段的数组+链表 实现,JDK1.8 采用的数据结构跟HashMap1.8的结构一样,数组+链表/红黑二叉树
实现线程安全方法:在JDK1.7的时候,ConcurrentHashMap(分段锁) 对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。(默认分配16个Segment,比Hashtable效率提高16倍。) 到了 JDK1.8 的时候已经摒弃了Segment的概念,而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作