目录
- 基本常识
- 标识符的命名规则
- 八种基本数据类型的大小,以及他们的封装类
- 3*0.1==0.3返回值是什么
- short s1 = 1; s1 = s1 + 1;有什么错? short s1 = 1; s1 += 1;有什么错?
- 简述&&与&的区别?
- 简述break与continue、return的区别?
- Arrays类的常用方法有哪些?
- 面向对象
- 面向过程与面向对象的区别
- 简述局部变量与成员变量的区别?
- super()和this()的区别?
- final有哪些用法?
- 使用final关键字修饰一个变量时,是引用不能变,还是引用的对象不能变?
- 重载(overload)和重写(override)的区别
- 在Java中,子类可以从父类中继承哪些?
- 简述static关键字的作用?
- 静态方法能否问非静态成员,为什么?
- 简述基本数据类型传参与引用类型传参的区别?
- 简述抽象类(abstract)与接口(interface)的区别?
- 作用域public,private,protected,以及不写时的区别 ?
- 静态变量和实例变量的区别?
- 简述工厂模式?
- String s = new String(“xyz”);创建了几个String Object
- 什么是深拷贝?什么是浅拷贝?
- 内部类的作用?内部类的作用?
- throws,throw,try,catch,finally 分别代表什么意义?
- throw与throws的区别?
- try catch finally,try里有return,finally还执行么?
- 简述Java中的异常处理机制的简单原理和应用?
- final, finally, finalize 的区别?
- 简述Error与Exception的区别?
- Excption与Error包结构
- instanceof 关键字的作用
- Java的四种引用,强弱软虚
- Java创建对象有几种方式?
- JavaAPI
- equals与==的区别
- ArrayList和linkedList的区别
- ArrayList的扩容机制
- HashMap和HashTable的区别
- 说说List,Set,Map三者的区别?
- Object 有哪些常用方法?大致说一下每个方法的含义
- 泛型常用特点
- HashMap
- HashMap的实现原理?
- HashMap插入数据时的思路?
- 有没有可能两个不相等的对象有相同的Hashcode / HashMap如何解决哈希冲突
- HashMap 中的 key 我们可以使用任何类作为 key 吗?
- HashMap 的长度为什么是 2 的 N 次方呢
- HashMap 与 ConcurrentHashMap 的异同
- 红黑树有哪几个特征
- Hashcode的作用
- Collection包结构,与Collections的区别
- Java自动装箱与拆箱
- 代码阅读1
- 代码阅读2
- String类型常用方法有哪些(至少写出5个)?
- String、String StringBuffer 和 StringBuilder 的区别是什么?
- 如何将字符串反转?如何将字符串反转?
- 如何获取某个日期是当月的最后一天?
- 简述序列化和反序列化的区别?
- OOM你遇到过哪些情况,SOF你遇到过哪些情况
- Java 序列化中如果有些字段不想进行序列化,怎么办?
- 说说Java 中 IO 流
- Java IO与 NIO的区别
- java反射的作用于原理
基本常识
标识符的命名规则
- 标识符的含义: 是指在程序中,我们自己定义的内容,譬如,类的名字,方法名称以及变量名称等等,都是标识符。
- 命名规则:(硬性要求) 标识符可以包含英文字母,0-9的数字,$以及_ 标识符不能以数字开头 标识符不是关键字
- 命名规范:(非硬性要求) 类名规范:首字符大写,后面每个单词首字母大写(大驼峰式)。 变量名规范:首字母小写,后面每个单词首字母大写(小驼峰式)。 方法名规范:同变量名# 面向对象和面向过程的区别
- 面向过程:是分析解决问题的步骤,然后用函数把这些步骤一步一步地实现,然后在使用的时候一
一调用则可。性能较高,所以单片机、嵌入式开发等一般采用面向过程开发 - 面向对象:是把构成问题的事务分解成各个对象,而建立对象的目的也不是为了完成一个个步骤,
而是为了描述某个事物在解决整个问题的过程中所发生的行为。面向对象有封装、继承、多态的特
性,所以易维护、易复用、易扩展。可以设计出低耦合的系统。 但是性能上来说,比面向过程要
低
八种基本数据类型的大小,以及他们的封装类
基本类型 | 大小(字节) | 默认值 | 封装类 |
---|---|---|---|
byte | 1 | (byte) 0 | Byte |
short | 2 | (short) 0 | Short |
int | 4 | 0 | Integer |
long | 8 | 0L | Long |
float | 4 | 0.0f | Float |
double | 8 | 0.0d | Double |
boolean | - | false | Boolean |
char | 2 | \u0000(null) | Character |
解释
- int是基本数据类型,Integer是int的封装类,是引用类型。int默认值是0,而Integer默认值是null,所以Integer能区分出0和null的情况。一旦java看到null,就知道这个引用还没有指向某个对象,再任何引用使用前,必须为其指定一个对象,否则会报错。
- 基本数据类型在声明时系统会自动给它分配空间,而引用类型声明时只是分配了引用空间,必须通过实例化开辟数据空间之后才可以赋值。数组对象也是一个引用对象,将一个数组赋值给另一个数组时只是复制了一个引用,所以通过某一个数组所做的修改在另一个数组中也看的见。
- 虽然定义了boolean这种数据类型,但是只对它提供了非常有限的支持。在Java虚拟机中没有任何供boolean值专用的字节码指令,Java语言表达式所操作的boolean值,在编译之后都使用Java虚拟机中的int数据类型来代替,而boolean数组将会被编码成Java虚拟机的byte数组,每个元素boolean元素占8位。这样我们可以得出boolean类型占了单独使用是4个字节,在数组中又是1个字节。使用int的原因是,对于当下32位的处理器(CPU)来说,一次处理数据是32位(这里不是指的是32/64位系统,而是指CPU硬件层面),具有高效存取的特点。
3*0.1==0.3返回值是什么
false,因为有些浮点数不能完全精确的表示出来.
short s1 = 1; s1 = s1 + 1;有什么错? short s1 = 1; s1 += 1;有什么错?
- short s1 = 1; s1 = s1 + 1; ( s1+1 运算结果是 int 型,需要强制转换类型)
- short s1 = 1; s1 += 1; (可以正确编译)
- (byte、char同理)
简述&&与&的区别?
- &&是逻辑操作符,而&是位操作符
- &和&&都可以用作逻辑与的运算符,表示逻辑与(and),当运算符两边的表达式的结果都为true时,整个运算结果才为true,否则,只要有一方为false,则结果为false。
- &&还具有短路的功能,即如果第一个表达式为false,则不再计算第二个表达式
- &还可以用作位运算符,例如0&1=0,1&1=1
简述break与continue、return的区别?
- break用于完全结束一个循环,跳出循环体,开始执行循环之后的代码
- continue的功能和break有点类似,区别是continue只是中止本次循环,然后开始下一次循环。而break则是完全中止循环 。
- return的功能是结束一个方法。 在循环体内执行到一个return语句,return语句将会结束该方法,循环也随之结束。与continue和break不同的是,return直接结束整个方法。
Arrays类的常用方法有哪些?
方法名称 | 说明 |
---|---|
boolean equals(array1,array2) | 比较array1和array2两个数组是否相等 |
sort(array) | 对数组array的元素进行升序排列 |
String toString(array) | 将一个数组array转换成一个字符串 |
void fill(array,val) | 把数组array所有元素都赋值为val |
copyOf(array,length) | 把数组array复制成一个长度为length的新数组,返回类型与复制的数组一致 |
int binarySearch(array, val) | 查询元素值val在数组array中的下标(要求数组中元素已经按升序排列) |
面向对象
面向过程与面向对象的区别
- 面向过程:是分析解决问题的步骤,然后用函数把这些步骤一步一步地实现,然后在使用的时候一一调用则可。性能较高,所以单片机、嵌入式开发等一般采用面向过程开发
- 面向对象:是把构成问题的事务分解成各个对象,而建立对象的目的也不是为了完成一个个步骤,而是为了描述某个事物在解决整个问题的过程中所发生的行为。面向对象有封装、继承、多态的特性,所以易维护、易复用、易扩展。可以设计出低耦合的系统。 但是性能上来说,比面向过程要低。
简述局部变量与成员变量的区别?
- 书写位置:
成员变量是独立于方法外的变量,局部变量是类的方法中的变量; - 存储位置与访问权限:
成员变量存储在堆,局部变量存储在栈。局部变量的作用域仅限于定义它的方法,在该方法的外部无法访问它。成员变量的作用域在整个类内部都是可见的,所有成员方法都可以使用它。如果访问权限允许,还可以在类的外部使用成员变量。 - 生存周期与初始值:
- 局部变量的生存周期与方法的执行期相同。当方法执行到定义局部变量的语句时,局部变量被创建;执行到它所在的作用域的最后一条语句时,局部变量被销毁。类的成员变量,如果是实例成员变量,它和对象的生存期相同。而静态成员变量的生存期是整个程序运行期。
- 成员变量在累加载或实例被创建时,系统自动分配内存空间,并在分配空间后自动为成员变量指定初始化值,初始化值为默认值,基本类型的默认值为0,复合类型的默认值为null。(被final修饰且没有static的必须显式赋值),局部变量在定义后必须经过显式初始化后才能使用,系统不会为局部变量执行初始化。
- 优先级:
局部变量可以和成员变量 同名,且在使用时,局部变量具有更高的优先级,直接使用同名访问,访问的是局部变量,如需要访问成员变量可以用 this.变量名 访问。
super()和this()的区别?
this():当前类的对象,super父类对象。
super():在子类访问父类成员和行为,必须承受继承规则的约束,而this代表当前对象,所有的资源都可以访问。在构造函数中,如果第一行没有写super(),编译器会自动插入,但是如果弗雷没有无参构造函数,或这个函数被私有化了,此时必须加入对父类的实例化构造。而this没有此类要求,它本身就进行了实例化的构造
final有哪些用法?
- 被final修饰的类不可以被继承
- 被final修饰的方法不可以被重写
- 被final修饰的变量不可以被改变.如果修饰引用,那么表示引用不可变,引用指向的内容可变.
- 被final修饰的方法,JVM会尝试将其内联,以提高运行效率
- 被final修饰的常量,在编译阶段会存入常量池中
除此之外,编译器对final域要遵守的两个重排序规则更好:
在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序 初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能重排序
使用final关键字修饰一个变量时,是引用不能变,还是引用的对象不能变?
使用final关键字修饰一个变量时,是指引用变量不能变,引用变量所指向的对象中的内容还是可以改变的
重载(overload)和重写(override)的区别
前提 | 方法名 | 返回值 | 参数项 | 异常 | 访问修饰符 | |
---|---|---|---|---|---|---|
重载 | 同一个类中 | 相同 | 无关 | 必须不同 | 无关 | 无关 |
重写 | 子类继承父类 | 相同 | 相同或是其子类 | 相同或是其子类 | 不能更多 | 访问权限不能更小 |
在Java中,子类可以从父类中继承哪些?
- 继承public和protected修饰的属性和方法,不管子类和父类是否在同一个包里。
- 继承默认权限修饰符修饰的属性和方法,但子类和父类必须在同一个包里。
- 无法继承private修饰的属性和方法。
- 无法继承父类的构造方法。
简述static关键字的作用?
- static是一个修饰符,用于修饰成员
- static修饰的成员被所有的对象所共享,是类级别的。
- static优先于对象存在,因为static的成员随着类的加载就已经存在了
- static修饰的成员多了一种调用方式,即可以直接被类名调用。类名.静态变量
- static修饰的数据是共享数据,对象中的存储的是特有数据
静态方法能否问非静态成员,为什么?
不能。静态方法属于类,不需要生成对象就存在了,而非静态变量需要生成对象才能产生,所以静态方法不能直接访问非静态成员
简述基本数据类型传参与引用类型传参的区别?
- 基本数据类型作为方法参数,传递的是变量副本,形参改变,实际值不会改变,所以原样输出。
- 引用型数据类型作为方法的形参传递,传递的是引用地址,形参改变,实际值也会跟着改变。
- 最后一种就是字符串,虽然属于引用型数据类型,但是因为字符串常量池的原因,字符串一旦被创建就不可改变的概念,给这个形参字符串赋值就相当于又创建了一个新的字符串,与原来的字符串已经没有啥关系了,所以原来的字符串指向的还是字符串A,最终根据作用域输出的还是A
简述抽象类(abstract)与接口(interface)的区别?
interface无构造方法,abstract类可以有构造方法;
abstract类中可以有普通成员变量,interface 只有抽象方法;
abstract类的访问类型可以是public或是protected,但interface默认的访问类型就是public abstract
一个类可以实现多个interface,但只能继承一个abstract类;
interface主要是应用在模块通信上,abstract类主要是用在代码的重写
作用域public,private,protected,以及不写时的区别 ?
作用域 | 当前类 | 同一package | 子孙类 | 其他package |
---|---|---|---|---|
public | √ | √ | √ | √ |
protected | √ | √ | √ | × |
friendly | √ | √ | × | × |
private | √ | × | × | × |
静态变量和实例变量的区别?
- 静态变量是被static修饰符修饰的变量,也称为类变量,它属于类,不属于类的任何一个对象,一个类不管创建多少个对象,静态变量在内存中有且仅有一个拷贝;
- 实例变量必须依存于某一实例,需要先创建对象然后通过对象才能访问到它。静态变量可以实现让多个对象共享内存。
简述工厂模式?
工厂模式:工厂模式是一种经常被使用到的模式,根据工厂模式实现的类可以根据提供的数据生成一组类中某一个类的实例,通常这一组类有一个公共的抽象父类并且实现了相同的方法,但是这些方法针对不同的数据进行了不同的操作。
首先需要定义一个基类,该类的子类通过不同的方法实现了基类中的方法。
然后需要定义一个工厂类,工厂类可以根据条件生成不同的子类实例。
当得到子类的实例后,开发人员可以调用基类中的方法而不必考虑到底返回的是哪一个子类的实例。
String s = new String(“xyz”);创建了几个String Object
两个,一个字符对象,一个字符对象引用对象
什么是深拷贝?什么是浅拷贝?
如果一个类拥有资源(堆或者是其他系统资源),当这个类的对象发生复制过程时,资源重新分配,这个过程就是深拷贝;反之对象存在资源,但复制过程并未复制资源的情况视为浅拷贝。
例如,在某些状况下,类内成员变量需要动态开辟堆内存,如果实行位复制,也就是把对象里的值完全复制给另一个对象,如A=B,这时,如果类B中有一个成员变量指针已经申请了内存,那么类A中的那个成员变量也指向同一块内存。这就出现了问题:当B把内存释放 了,如通过析构函数,这时A内的指针就变成野指针了,导致运行错误。
内部类的作用?内部类的作用?
- 内部类可以很好的实现隐藏.一般的非内部类,是不允许有private与protected权限的,但内部类可以
- 内部类拥有外围类的所有元素的访问权限
- 可是实现多重继承
- 可以避免修改接口而实现同一个类中两种同名方法的调用
throws,throw,try,catch,finally 分别代表什么意义?
try:尝试执行代码
catch:捕捉异常
finally:无论是否发生异常最后都会在return之前执行的代码
throw:抛出异常
throws:声明异常
throw与throws的区别?
throw 是语句抛出异常(仅一个);throws 是方法声明异常(可声明多个);
throws可以单独使用,但throw不能;
throw要么和try-catch-finally语句配套使用,要么与throws配套使用。但throws可以单独使用,然后再由处理异常的方法捕获。
try catch finally,try里有return,finally还执行么?
执行,并且finally的执行早于try里面的return
- 不管有木有出现异常,finally块中代码都会执行;
- 当try和catch中有return时,finally仍然会执行;
- finally是在return后面的表达式运算后执行的(此时并没有返回运算后的值,而是先把要返回的值保存起来,管finally中的代码怎么样,返回的值都不会改变,任然是之前保存的值),所以函数返回值是在finally执行前确定的;
- finally中最好不要包含return,否则程序会提前退出,返回值不是try或catch中保存的返回值
简述Java中的异常处理机制的简单原理和应用?
当 JAVA 程序违反了 JAVA 的语义规则时, JAVA 虚拟机就会将发生的错误表示为一个
异常。
违反语义规则包括 2 种情况。一种是 JAVA 类库内置的语义检查。例如数组下标越界 ,会引发 IndexOutOfBoundsException; 访问 null 的对象时会引发 NullPointerException 。
另一种情况就是 JAVA 允许程序员扩展这种语义检查,程序员可以创建自己的异常,并自由选择在何时用 throw 关键字引发异常。所有的异常都是 java.lang.Throwable 的子类。
final, finally, finalize 的区别?
inal 用于声明属性,方法和类,分别表示属性不可变,方法不可覆盖,类不可继承。
finally 是异常处理语句结构的一部分,表示总是执行。
finalize 是 Object 类的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法 ,
可以覆盖此方法提供垃圾收集时的其他资源回收,例如关闭文件等
简述Error与Exception的区别?
Exception和Error都继承自Throwable,在Java中只有Throwable类型的实例才可以被抛出或捕获。
Error指正常情况下不太可能出现的情况,绝大部分的Error或导致程序崩溃,处于非正常的不可恢复的状态,如OutOfMemoryError、StackOverflowError。是程序中不应该试图捕获的严重问题。
Exception是程序正常运行中可以预料的意外情况,可以捕获并处理。
Excption与Error包结构
Java可抛出(Throwable)的结构分为三种类型:被检查的异常(CheckedException),运行时异常
(RuntimeException),错误(Error)。
- 运行时异常
- 定义:RuntimeException及其子类都被称为运行时异常。
- 特点:Java编译器不会检查它。也就是说,当程序中可能出现这类异常时,倘若既"没有通过throws声明抛出它",也"没有用try-catch语句捕获它",还是会编译通过。例如,除数为零时产生的ArithmeticException异常,数组越界时产生的IndexOutOfBoundsException异常,fail-fast机制产生的ConcurrentModificationException异常(java.util包下面的所有的集合类都是快速失败的,“快速失败”也就是fail-fast,它是Java集合的一种错误检测机制。+ 当多个线程对集合进行结构上的改变的操时,有可能会产生fail-fast机制。记住是有可能,而不是一定。例如:假设存在两个线程(线程1、线程2),线程1通过Iterator在遍历集合A中的元素,在某个时候线程2修改了集合A的结构(是结构上面的修改,而不是简单的修改集合元素的内容),那么这个时候程序就会抛出ConcurrentModificationException 异常,从而产生fail-fast机制,这个错叫并发修改异常Fail-safe,java.util.concurrent包下面的所有的类都是安全失败的,在遍历过程中,如果已经遍历的数组上的内容变化了,迭代器不会抛出ConcurrentModificationException异常。如果未遍历的数组上的内容发生了变化,则有可能反映到迭代过程中。这就是ConcurrentHashMap迭代器弱一致的表现。ConcurrentHashMap的弱一致性主要是为了提升效率,是一致性与效率之间的一种权衡。要成为强一致性,就得到处使用锁,甚至是全局锁,这就与Hashtable和同步的HashMap一样了。)等,都属于运行时异常。常见的五种运行时异常:
- ClassCastException(类转换异常)
- IndexOutOfBoundsException(数组越界)
- NullPointerException(空指针异常)
- ArrayStoreException(数据存储异常,操作数组是类型不一致)
- BufferOverflowException
- 被检查异常
- 定义:Exception类本身,以及Exception的子类中除了"运行时异常"之外的其它子类都属于被检查异常。
- 特点 : Java编译器会检查它。 此类异常,要么通过throws进行声明抛出,要么通过try-catch进行捕获处理,否则不能通过编译。例如,CloneNotSupportedException就属于被检查异常。当通过clone()接口去克隆一个对象,而该对象对应的类没有实现Cloneable接口,就会抛出CloneNotSupportedException异常。被检查异常通常都是可以恢复的。 如:
- IOException
- FileNotFoundException
- SQLException
- 被检查的异常适用于那些不是因程序引起的错误情况,比如:读取文件时文件不存在引发的
FileNotFoundException 。然而,不被检查的异常通常都是由于糟糕的编程引起的,比如:在对象引用时没有确保对象非空而引起的 NullPointerException 。
- 错误
- 定义 : Error类及其子类。
- 特点 : 和运行时异常一样,编译器也不会对错误进行检查。当资源不足、约束失败、或是其它程序无法继续运行的条件发生时,就产生错误。程序本身无法修
复这些错误的。例如,VirtualMachineError就属于错误。出现这种错误会导致程序终止运行。OutOfMemoryError、ThreadDeath。Java虚拟机规范规定JVM的内存分为了好几块,比如堆,栈,程序计数器,方法区等
instanceof 关键字的作用
instanceof 严格来说是Java中的一个双目运算符,用来测试一个对象是否为一个类的实例
boolean result = obj instanceof Class
- 其中 obj 为一个对象,Class 表示一个类或者一个接口,当 obj 为 Class 的对象,或者是其直接或间接子类,或者是其接口的实现类,结果result 都返回 true,否则返回false。
- 注意:编译器会检查 obj 是否能转换成右边的class类型,如果不能转换则直接报错,如果不能确定类型,则通过编译,具体看运行时定。
int i = 0;
System.out.println(i instanceof Integer);//编译不通过 i必须是引用类型,不能是基本类型
System.out.println(i instanceof Object);//编译不通过Integer integer = new Integer(1);
System.out.println(integer instanceof Integer);//true//false ,在 JavaSE规范 中对 instanceof 运算符的规定就是:如果 obj 为 null,那么将返回 false。
System.out.println(null instanceof Object);
Java的四种引用,强弱软虚
-
强引用:强引用是平常中使用最多的引用,强引用在程序内存不足(OOM)的时候也不会被回收,使用方式:
String str = new String("str"); System.out.println(str);
-
弱引用:弱引用就是只要JVM垃圾回收器发现了它,就会将之回收,使用方式:
WeakReference<String> wrf = new WeakReference<String>(str);
可用场景: Java源码中的 java.util.WeakHashMap 中的 key 就是使用弱引用,我的理解就是,一旦我不需要某个引用,JVM会自动帮我处理它,这样我就不需要做其它操作。
-
软引用:软引用在程序内存不足时,会被回收,使用方式:
// 注意:wrf这个引用也是强引用,它是指向SoftReference这个对象的, // 这里的软引用指的是指向new String("str")的引用,也就是SoftReference类中T SoftReference<String> wrf = new SoftReference<String>(new String("str"));
-
虚引用:虚引用的回收机制跟弱引用差不多,但是它被回收之前,会被放入 ReferenceQueue 中。注意:其它引用是被JVM回收后才被传入 ReferenceQueue 中的。由于这个机制,所以虚引用大多被用于引用销毁前的处理工作。还有就是,虚引用创建的时候,必须带有 ReferenceQueue ,使用例子:
PhantomReference<String> prf = new PhantomReference<String>(new String("str"), new ReferenceQueue<>());
可用场景: 对象销毁前的一些操作,比如说资源释放等。 Object.finalize() 虽然也可以做这类动作,但是这个方式即不安全又低效
Java创建对象有几种方式?
- new创建新对象
- 通过反射机制
- 采用clone机制
- 通过序列化机制
JavaAPI
equals与==的区别
- Object类中的equals方法中返回的就是
==
运算。也就是说任何一个类如果没有重写Object中的equals方法,那么equals的判断就是==
。 - String类中重写了equals方法,比较的是两个字符串的内容是否相同;而==比较的是两个字符串在内存中的地址是否是同一个
ArrayList和linkedList的区别
- Array(数组)是基于索引(index)的数据结构,它使用索引在数组中搜索和读取数据是很快的。
- LinkList是一个双链表,在添加和删除元素时具有比ArrayList更好的性能.但在get与set方面弱于
ArrayList.当然,这些对比都是指数据量很大或者操作很频繁。
理解上:举例子
- ArrayList是排队,每个人有自己的号,根据号找人很快,但是如果有人插队,那么插入的位置往后的人肯定都不爽,因为都要往后移动,很费事
- 链表是车链子,链子长了随便去掉一块,短了随便加上一段。但是等你加好了,转了几圈,你还能找到你加的那一段在什么位置吗?找是能找到,但是也很费事
ArrayList的扩容机制
ArrayList在添加元素时,会根据需要自动进行扩容,以容纳更多的元素。
- 初始容量:
- 在创建ArrayList时,可以指定初始容量。如果没有指定初始容量,则默认为10。
- 容量增长策略:
- 当ArrayList的当前容量不足以容纳新元素时,会触发扩容操作。
- 扩容时,ArrayList会创建一个新的更大的数组,并将原始数组中的元素复制到新数组中。
- 默认情况下,ArrayList的容量会以1.5倍增长(newCapacity = oldCapacity + (oldCapacity >> 1)),即每次扩容后的容量是当前容量的1.5倍。
- 在Java 7之前,容量增长策略是以当前容量的两倍进行扩容。
- 扩容开销:
- 扩容操作涉及创建新数组、复制元素等操作,因此会有一定的开销。
- 由于扩容操作可能比较耗时,建议在添加大量元素时,事先估计所需容量,通过构造函数指定初始容量,以减少扩容次数,提高性能。
- 扩容的时间复杂度:
- 平均情况下,ArrayList的扩容操作的时间复杂度为O(n),其中n为当前元素数量。因为需要将所有元素从旧数组复制到新数组。
- 在最坏情况下,当每次扩容时都需要复制元素,添加n个元素的时间复杂度为O(n^2)。这种情况很少发生,因为ArrayList的扩容策略会尽量减少扩容次数。
总结:ArrayList通过自动扩容机制,可以动态地增加容量以容纳更多的元素。它通过创建更大的数组,并将原始数组中的元素复制到新数组中来实现扩容。在实际使用中,我们可以通过指定初始容量和适当的预估来优化扩容操作,以提高性能。
HashMap和HashTable的区别
- 两者父类不同
HashMap是继承自AbstractMap类,而Hashtable是继承自Dictionary类。不过它们都实现了同时实现了map、Cloneable(可复制)、Serializable(可序列化)这三个接口。 - 对外提供的接口不同
Hashtable比HashMap多提供了elments() 和contains() 两个方法。 elments() 方法继承自Hashtable的父类Dictionnary。elements() 方法用于返回此Hashtable中的value的枚举。contains()方法判断该Hashtable是否包含传入的value。它的作用与containsValue()一致。事实上,contansValue() 就只是调用了一下contains() 方法。 - 对null的支持不同
Hashtable:key和value都不能为null。
HashMap:key可以为null,但是这样的key只能有一个,因为必须保证key的唯一性;可以有多个key值对应的value为null。 - 安全性不同
HashMap是线程不安全的,在多线程并发的环境下,可能会产生死锁等问题,因此需要开发人员自己处理多线程的安全问题。
Hashtable是线程安全的,它的每个方法上都有synchronized 关键字,因此可直接用于多线程中。
虽然HashMap是线程不安全的,但是它的效率远远高于Hashtable,这样设计是合理的,因为大部分的使用场景都是单线程。当需要多线程操作的时候可以使用线程安全的ConcurrentHashMap。
ConcurrentHashMap虽然也是线程安全的,但是它的效率比Hashtable要高好多倍。因为ConcurrentHashMap使用了分段锁,并不对整个数据进行锁定。 - 初始容量大小和每次扩充容量大小不同
创建时如果不指定容量初始值,Hashtable 默认的初始大小为 11,之后每次扩充,容量变为原来的 2n+1。HashMap 默认的初始化大小为 16。之后每次扩充,容量变为原来的 2 倍
创建时如果给定了容量初始值,那么 Hashtable 会直接使用你给定的大小,而 HashMap 会将其扩充为 2 的幂次方大小(HashMap 中的 tableSizeFor()方法保证,下面给出了源代码)。也就是说 HashMap 总是使用 2 的幂作为哈希表的大小,后面会介绍到为什么是 2 的幂次方。 - 计算hash值的方法不同
在HashMap中,首先使用Objects.hashCode(Object)方法来获取对象的哈希码,如果对象为null,则返回0。然后可以自定义哈希函数(如hash(int hashCode))来进一步处理哈希码。
在Hashtable中,直接使用对象的hashCode()方法获取哈希码
说说List,Set,Map三者的区别?
- List(对付顺序的好帮手): List接口存储一组不唯一(可以有多个元素引用相同的对象),有序的对象
- Set(注重独一无二的性质): 不允许重复的集合。不会有多个元素引用相同的对象。
- Map(用Key来搜索的专家): 使用键值对存储。Map会维护与Key有关联的值。两个Key可以引用相同的对象,但Key不能重复,典型的Key是String类型,但也可以是任何对象
Object 有哪些常用方法?大致说一下每个方法的含义
- clone 方法
保护方法,实现对象的浅复制,只有实现了 Cloneable 接口才可以调用该方法,否则抛出CloneNotSupportedException 异常,深拷贝也需要实现 Cloneable,同时其成员变量为引用类型的也需要实现 Cloneable,然后重写 clone 方法。 - finalize 方法
该方法和垃圾收集器有关系,判断一个对象是否可以被回收的最后一步就是判断是否重写了此方法。 - equals 方法
该方法使用频率非常高。一般 equals 和 == 是不一样的,但是在 Object 中两者是一样的。子类一般都要重写这个方法。 - hashCode 方法
该方法用于哈希查找,重写了 equals 方法一般都要重写 hashCode 方法,这个方法在一些具有哈希功能的 Collection 中用到。一般必须满足 obj1.equals(obj2)==true 。可以推出obj1.hashCode()==obj2.hashCode() ,但是hashCode 相等不一定就满足 equals。不过为了提高效率,应该尽量使上面两个条件接近等价。JDK 1.6、1.7 默认是返回随机数;JDK 1.8 默认是通过和当前线程有关的一个随机数 + 三个确定值,运用 Marsaglia’s xorshiftscheme 随机数算法得到的一个随机数。 - wait 方法
配合 synchronized 使用,wait 方法就是使当前线程等待该对象的锁,当前线程必须是该对象的拥有者,也就是具有该对象的锁。wait() 方法一直等待,直到获得锁或者被中断。wait(long timeout)设定一个超时间隔,如果在规定时间内没有获得锁就返回。调用该方法后当前线程进入睡眠状态,直到以下事件发生。- 其他线程调用了该对象的 notify 方法;
- 其他线程调用了该对象的 notifyAll 方法;
- 其他线程调用了 interrupt 中断该线程;
- 时间间隔到了。
此时该线程就可以被调度了,如果是被中断的话就抛出一个 InterruptedException 异常。
- notify 方法
配合 synchronized 使用,该方法唤醒在该对象上等待队列中的某个线程(同步队列中的线程是给抢占 CPU 的线程,等待队列中的线程指的是等待唤醒的线程)。 - notifyAll 方法
配合 synchronized 使用,该方法唤醒在该对象上等待队列中的所有线程。
泛型常用特点
“泛型”,顾名思义,“泛指的类型”。我们提供了泛指的概念,但具体执行的时候却可以有具体的规则来约束,比如我们用的非常多的ArrayList就是个泛型类,ArrayList作为集合可以存放各种元素,如Integer, String,自定义的各种类型等,但在我们使用的时候通过具体的规则来约束,如我们可以约束集合中只存放Integer类型的元素,如:List<Integer> iniData = new ArrayList<>()
,使用泛型的好处:
以集合来举例,使用泛型的好处是我们不必因为添加元素类型的不同而定义不同类型的集合,如整型集合类,浮点型集合类,字符串集合类,我们可以定义一个集合来存放整型、浮点型,字符串型数据,而这并不是最重要的,因为我们只要把底层存储设置了Object即可,添加的数据全部都可向上转型为Object。 更重要的是我们可以通过规则按照自己的想法控制存储的数据类型。
HashMap
HashMap的实现原理?
HashMap 在底层将 key-value 当成一个整体进行处理,这个整体就是一个 Entry 对象。HashMap 底层采用一个 Entry[] 数组来保存所有的 key-value 对,当需要存储一个 Entry 对象时,会根据hash算法来决定其在数组中的存储位置,在根据equals方法决定其在该数组位置上的链表中的存储位置;当需要取出一个Entry时,也会根据hash算法找到其在数组中的存储位置,再根据equals方法从该位置上的链表中取出该Entry。
HashMap插入数据时的思路?
数组 ---- 没元素,直接放;
key是不是同一个,如果是,替换value;
key不是同一个,且数组有元素,判断是链表或是树 --> 是链表(是否达到树的要求) 没达到就挂链表,达到了就变成树;链表长度达到==8这个数字的时候,会转成红黑树;
key不是同一个,且数组有元素,判断是链表或是树 --> 是红黑树,挂到红黑树后面的节点去;
当数组被使用的长度达到数组原始长度的四分之三之后,会开始进行扩容机制,数组长度会翻倍。
这时候,会建立一个新的长度为原长度两倍的数组,然后遍历原数组:
如果只有一个元素,就直接取模搬过来
如果数组下是一个链表,就遍历链表之后取模复制过来
如果是一个树,每个节点再计算一次hash,然后把树打散,一个元素一个元素去拿
有没有可能两个不相等的对象有相同的Hashcode / HashMap如何解决哈希冲突
有可能.在产生hash冲突时,两个不相等的对象就会有相同的 hashcode 值.当hash冲突产生时,一般有以下几种方式来处理:
- 拉链法:每个哈希表节点都有一个next指针,多个哈希表节点可以用next指针构成一个单向链表,被分配到同一个索引上的多个节点可以用这个单向链表进行存储.
- 开放定址法:一旦发生了冲突,就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到,并将记录存入List iniData = new ArrayList<>()
- 再哈希:又叫双哈希法,有多个不同的Hash函数.当发生冲突时,使用第二个,第三个….等哈希函数计算地址,直到无冲突.
HashMap 中的 key 我们可以使用任何类作为 key 吗?
平时可能大家使用的最多的就是使用 String 作为 HashMap 的 key,但是现在我们想使用某个自定
义类作为 HashMap 的 key,那就需要注意以下几点:
- 如果类重写了 equals 方法,它也应该重写 hashCode 方法。
- 类的所有实例需要遵循与 equals 和 hashCode 相关的规则。
- 如果一个类没有使用 equals,你不应该在 hashCode 中使用它。
- 咱们自定义 key 类的最佳实践是使之为不可变的,这样,hashCode 值可以被缓存起来,拥有更好的性能。不可变的类也可以确保 hashCode 和 equals 在未来不会改变,这样就会解决与可变相关的问题了。
HashMap 的长度为什么是 2 的 N 次方呢
为了能让 HashMap 存数据和取数据的效率高,尽可能地减少 hash 值的碰撞,也就是说尽量把数据能均匀的分配,每个链表或者红黑树长度尽量相等。我们首先可能会想到 % 取模的操作来实现:
取余(%)操作中如果除数是 2 的幂次,则等价于与其除数减一的与(&)操作(也就是说
hash % length == hash &(length - 1) 的前提是 length 是 2 的 n 次方)。并且,采用二进
制位操作 & ,相对于 % 能够提高运算效率
HashMap 与 ConcurrentHashMap 的异同
- 都是 key-value 形式的存储数据;
- HashMap 是线程不安全的,ConcurrentHashMap 是 JUC 下的线程安全的;
- HashMap 底层数据结构是数组 + 链表(JDK 1.8 之前)。JDK 1.8 之后是数组 + 链表 + 红黑树。当链表中元素个数达到 8 的时候,链表的查询速度不如红黑树快,链表会转为红黑树,红黑树查询速度快;
- HashMap 初始数组大小为 16(默认),当出现扩容的时候,以 0.75 * 数组大小的方式进行扩容;
- ConcurrentHashMap 在 JDK 1.8 之前是采用分段锁来现实的 Segment + HashEntry,Segment 数组大小默认是 16,2 的 n 次方;JDK 1.8 之后,采用 Node + CAS + Synchronized来保证并发安全进行实现。
红黑树有哪几个特征
Hashcode的作用
- java的集合有两类,一类是List,还有一类是Set。前者有序可重复,后者无序不重复。当我们在set中插入的时候怎么判断是否已经存在该元素呢,可以通过equals方法。但是如果元素太多,用这样的方法就会比较满。
- 于是有人发明了哈希算法来提高集合中查找元素的效率。 这种方式将集合分成若干个存储区域,每个对象可以计算出一个哈希码,可以将哈希码分组,每组分别对应某个存储区域,根据一个对象的哈希码就可以确定该对象应该存储的那个区域。
- hashCode方法可以这样理解:它返回的就是根据对象的内存地址换算出的一个值。这样一来,当集合要添加新的元素时,先调用这个元素的hashCode方法,就一下子能定位到它应该放置的物理位置上。如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;如果这个位置上已经有元素了,就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址。这样一来实际调用equals方法的次数就大大降低了,几乎只需要一两次
Collection包结构,与Collections的区别
- Collection是集合类的上级接口,子接口有 Set、List、LinkedList、ArrayList、Vector、Stack、
Set; - Collections是集合类的一个帮助类, 它包含有各种有关集合操作的静态多态方法,用于实现对各种集合的搜索、排序、线程安全化等操作。此类不能实例化,就像一个工具类,服务于Java的Collection框架。
Java自动装箱与拆箱
- 装箱:就是自动将基本数据类型转换为包装器类型(int–>Integer);调用方法:Integer的valueOf(int) 方法
- 拆箱:就是自动将包装器类型转换为基本数据类型(Integer–>int)。调用方法:Integer的intValue方法
代码阅读1
public static void main(String[] args) {Integer i1 = 100;Integer i2 = 100;Integer i3 = 200;Integer i4 = 200;System.out.println(i1==i2);//trueSystem.out.println(i3==i4);//false
}
- 在通过valueOf方法创建Integer对象的时候,如果数值在[-128,127]之间,便返回指向IntegerCache.cache中已经存在的对象的引用;否则创建一个新的Integer对象。
- 上面的代码中i1和i2的数值为100,因此会直接从cache中取已经存在的对象,所以i1和i2指向的是
同一个对象,而i3和i4则是分别指向不同的对象。
代码阅读2
public static void main(String[] args) {Double i1 = 100.0;Double i2 = 100.0;Double i3 = 200.0;Double i4 = 200.0;System.out.println(i1==i2);//falseSystem.out.println(i3==i4);//false
}
原因: 在某个范围内的整型数值的个数是有限的,而浮点数却不是。
String类型常用方法有哪些(至少写出5个)?
- 获取字符串长度length()
- 获取位置上的某个字符charAt()
- 获取字符的位置indexOf()
- 判断是否包含某个字符contains()
- 判断字符串中是否有内容isEmpty()
- 判断字符串是否一指定字符开头结尾startsWith(),endsWith()
- 判断字符串内容是否相同equals()
8.切割split()
…
String、String StringBuffer 和 StringBuilder 的区别是什么?
String | StringBuffer | StringBuilder |
---|---|---|
String是把数据存放在了字符串常量池中,所有的String都是常量的方式来保存的,并且它是被final修饰的所以是线程安全的。每一个String对象在被创建好后就不会再发生任何改变,如果进行值的重新指向,会在池中寻找或者创建一个新的对象。 他是支持查找不支持修改的,所以执行效率最低。 适合使用在少量字符串操作的情况下。 | StringBuilder是执行效率最高的,底层和StringBuffer一样通过char[]数组来实现,支持查询修改操作,效率高但是不安全 在进行多线程处理的时候,多个线程对同一个变量进行修改查询操作的时候会出现数据的混乱,执行错误逻辑,它没有对线程安全进行考虑,是线程不安全的。 适合使用在单线程大象字符串操作情况下。 | StringBuffer效率虽然没有StringBuilder高,但是比String效率高的多。 效率低的原因:在考虑线程安全的情况下,在进行字符串操作的时候使用了 synchronized 关键字的进行加锁操作来保证线程的安全性。 适合使用在多线程对多字符串操作的情况下。 |
-
String是不可变的,如果尝试去修改,会新成个字符串对象,StringBuffer和StringBuilder是 可变的
-
StringBuffer是线程安全的,StringBuilder是线程不安全的,所以在单线程环境下StringBuilder效 率会更⾼
String | StringBuilder | StringBuffer | |
---|---|---|---|
执行速度 | 最差 | 最高 | 其次 |
线程安全 | 线程安全 | 线程不安全 | 线程安全 |
使用场景 | 少量字符串操作 | 多线程环境下的大量操作 | 单线程环境下的大量操作 |
如何将字符串反转?如何将字符串反转?
1.使用StringBuilder的reverse()方法
2.使用字符串数组,实现从尾部开始逐个逆序放入字符串
3.使用String的CharAt方法,调用StringBuilder的insert方法进行操作
4.使用递归
如何获取某个日期是当月的最后一天?
第一种,使用Calendar的roll方法,在限制某个日期字段不改变的形式下,改变其他日期字段的值。
第二种,使用Calendar的getActualMaximum方法,获得指定日期字段的最大值。
第三种,使用Calendar的set和add方法,从下个月的第一天计算得到当前月的最后一天。
第四种,循环使用Calendar的add方法,加到本月的最后一天
简述序列化和反序列化的区别?
把对象转换为字节序列的过程称为对象的序列化。把字节序列恢复为对象的过程称为对象的反序列化。
对象的序列化主要有两种用途:
- 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;
- 在网络上传送对象的字节序列
OOM你遇到过哪些情况,SOF你遇到过哪些情况
- OOM:
- OutOfMemoryError异常:
除了程序计数器外,虚拟机内存的其他几个运行时区域都有发生OutOfMemoryError(OOM)异常的可能。
Java Heap 溢出:- 一般的异常信息:java.lang.OutOfMemoryError:Java heap spacess。
- java堆用于存储对象实例,我们只要不断的创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象,就会在对象数量达到最大堆容量限制后产生内存溢出异常。出现这种异常,一般手段是先通过内存映像分析工具(如Eclipse Memory Analyzer)对dump出来的堆转存快照进行分析,重点是确认内存中的对象是否是必要的,先分清是因为内存泄漏(MemoryLeak)还是内存溢出(Memory Overflow)。
- 如果是内存泄漏,可进一步通过工具查看泄漏对象到GCRoots的引用链。于是就能找到泄漏对象是通过怎样的路径与GC Roots相关联并导致垃圾收集器无法自动回收。
- 如果不存在泄漏,那就应该检查虚拟机的参数(-Xmx与-Xms)的设置是否适当。
- 虚拟机栈和本地方法栈溢出
- 如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常。
- 如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常
- 这里需要注意当栈的大小越大可分配的线程数就越少。
- 运行时常量池溢出
- 异常信息:java.lang.OutOfMemoryError:PermGenspace
- 如果要向运行时常量池中添加内容,最简单的做法就是使用String.intern()这个Native方法。
- 该方法的作用是:如果池中已经包含一个等于此String的字符串,则返回代表池中这个字符串的String对象;否则,将此String对象包含的字符串添加到常量池中,并且返回此String对象的引用。由于常量池分配在方法区内,我们可以通过-XX:PermSize和-XX:MaxPermSize限制方法区的大小,从而间接限制其中常量池的容量。
- 方法区溢出
- 方法区用于存放Class的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等。也有可能是方法区中保存的class对象没有被及时回收掉或者class信息占用的内存超过了我们配置。
- 异常信息:java.lang.OutOfMemoryError:PermGenspace
方法区溢出也是一种常见的内存溢出异常,一个类如果要被垃圾收集器回收,判定条件是很苛刻的。在经常动态生成大量Class的应用中,要特别注意这点。
- OutOfMemoryError异常:
- SOF(堆栈溢出StackOverflow):
- StackOverflowError 的定义:当应用程序递归太深而发生堆栈溢出时,抛出该错误。
- 因为栈一般默认为1-2m,一旦出现死循环或者是大量的递归调用,在不断的压栈过程中,造成栈容量超过1m而导致溢出。
- 栈溢出的原因:递归调用,大量循环或死循环,全局变量是否过多,数组、List、map数据过大
Java 序列化中如果有些字段不想进行序列化,怎么办?
- 对于不想进行序列化的变量,使用 transient 关键字修饰。
- transient 关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被 transient 修饰的变量值不会被持久化和恢复。transient 只能修饰变量,不能修饰类和方法。
说说Java 中 IO 流
- Java 中 IO 流分为几种?
- 按照流的流向分,可以分为输入流和输出流;
- 按照操作单元划分,可以划分为字节流和字符流;
- 按照流的角色划分为节点流和处理流。
- Java Io 流共涉及 40 多个类,这些类看上去很杂乱,但实际上很有规则,而且彼此之间存在非常紧密的联系, Java I0 流的 40 多个类都是从如下 4 个抽象类基类中派生出来的。
- InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
- OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。
Java IO与 NIO的区别
NIO即New IO,这个库是在JDK1.4中才引入的。NIO和IO有相同的作用和目的,但实现方式不同,NIO主要用到的是块,所以NIO的效率要比IO高很多。在Java API中提供了两套NIO,一套是针对标准输入输出NIO,另一套就是网络编程NIO。
java反射的作用于原理
- 定义:
反射机制是在运行时,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意个对象,都能够调用它的任意一个方法。在java中,只要给定类的名字,就可以通过反射机制来获得类的所有信息。这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。 - 哪里会用到反射机制?
jdbc就是典型的反射:Class.forName(‘com.mysql.jdbc.Driver.class’);//加载MySQL的驱动类这就是反射。如hibernate,struts等框架使用反射实现的。 - 反射的实现方式:获取Class对象,有4中方法:
- 1)Class.forName(“类的路径”);
- 2)类名.class
- 3)对象名.getClass()
- 4)基本类型的包装类,可以调用包装类的Type属性来获得该包装类的Class对象
- 4、实现Java反射的类:
- 1)Class:表示正在运行的Java应用程序中的类和接口 注意: 所有获取对象的信息都需要Class类来实现。
- 2)Field:提供有关类和接口的属性信息,以及对它的动态访问权限。
- 3)Constructor:提供关于类的单个构造方法的信息以及它的访问权限
- 4)Method:提供类或接口中某个方法的信息
- 反射机制的优缺点:
- 优点:
1)能够运行时动态获取类的实例,提高灵活性;
2)与动态编译结合 - 缺点:
1)使用反射性能较低,需要解析字节码,将内存中的对象进行解析。 解决方案: 1、通setAccessible(true)关闭JDK的安全检查来提升反射速度; 2、多次创建一个类的实例时,有缓存会快很多 3、ReflectASM工具类,通过字节码生成的方式加快反射速度
2)相对不安全,破坏了封装性(因为通过反射可以获得私有方法和属性)
- 优点: