目录
String、StringBuffer、StringBuilder
面向过程和面向对象的区别
面向过程
:是分析解决问题的步骤,然后用函数把这些步骤一步一步地实现,然后在使用时调用即可。性能较高,单片机、嵌入式开发一般采用面向过程开发。面向对象
:就是把构成问题的事物分解成各个对象,不关心其具体实现过程,只关心完成功能。面向对象有封装
、继承
、多态
的特性,所以易维护
、易复用
、易扩展
。可以设计出低耦合的系统,但是从性能上来说,要比面向过程要低。
八种基本数据类型的大小,以及他们的封装类
数据类型 | 大小(字节) | 封装类 |
---|---|---|
byte | 1 | Byte |
short | 2 | Short |
int | 4 | Integer |
long | 8 | Long |
float | 4 | Float |
double | 8 | Double |
char | 2 | Character |
boolean | 1 | Boolean |
- 6 种数字类型:
- 4 种整数型:
byte
、short
、int
、long
- 2 种浮点型:
float
、double
- 4 种整数型:
- 1 种字符类型:
char
- 1 种布尔型:
boolean
。
基本类型和包装类型的区别
用途:我们在方法参数、对象属性中很少会使用基本类型来定义变量,都是使用包装类型。并且,包装类型可用于泛型,而基本类型不可以。
默认值:成员变量包装类型不赋值就是 null
,而基本类型有默认值且不是 null
。比如int默认值是0,而Integer默认值是null,所以Integer能区分出0和null的情况。
比较方式:对于基本数据类型来说,==
比较的是值。对于包装数据类型来说,==
比较的是对象的内存地址。所有整型包装类对象之间值的比较,全部使用 equals()
方法。
缓存机制:包装类型大部分都用到了缓存机制来提升性能。
4种整型包装类默认创建了数值 [-128,127] 的相应类型的缓存数据,范围内的数据就会从缓存中读取,范围外就会创建一个新的对象。虽然integer对象在[-128,127] 范围内会复用对象使用==判断也是正确的,但是这个区间之外,都会在堆上产生新的对象。因此所有整型包装类对象之间值的比较,全部使用 equals 方法比较。
equals和==的区别
基本数据类型,==比较内容
引用数据类型,==比较对象的引用,也就是内存地址
对于equals方法,类没有重写equals,就和==作用一样,用的是object类的equals方法,object类equals()方法默认实现是使用==来比较的,所以都是比较内存地址。
但是类可以重写equals方法以实现比较对象内容的功能。比如String类就默认重写了object类的equals方法。
String类的equals方法
先比较地址,地址相同直接返回true,再遍历比较字符串内容,内容相同就返回true
为什么重写equals必须重写hashcode
hashcode的作用:
就是根据对象的内存地址换算出一个散列值,这个值可以确定该对象在哈希表中的索引位置。
在一些容器(比如 HashMap
、HashSet
)中,有了 hashCode()
之后,判断元素是否在对应容器中的效率会更高。先调用这个元素的hashCode
方法,如果同样的 hashCode
有多个对象,它会继续使用 equals()
来判断是否真的相同。
为什么要一起重写
因为两个相等的对象的 hashCode
值必须是相等。也就是说如果 equals
方法判断两个对象是相等的,那这两个对象的 hashCode
值也要相等。
如果重写 equals()
时没有重写 hashCode()
方法的话就可能会导致 equals
方法重写后通过内容比较判断是相等的两个对象,hashCode
值却不相等。因为不重写就会调用object类的hashcode方法,不是一个内存地址计算出来的hashcode值就不同。
比如HashSet集合中类如果重写equals方法没有重写hashcode方法,当添加两个等值的对象,集合先根据hashcode方法判断两个相等值对象的哈希值不同,最终导致HashSet集合根据哈希值认为他们不同,添加了两个等值的对象,集合中比较是先根据hashcode,hashcode值一样再根据equals判断,所以要一起重写。
另外,在Java中,String、Long、Integer等常见的数据类型已经实现了hashCode()和equals()方法,因此可以直接作为HashMap的key或者HashSet元素存储。
接口和抽象类共同点和区别
共同点:
都不能被实例化,都可以有抽象方法
java8之后都可以有默认方法
区别:
一个类只能继承一个抽象类,却可以实现多个接口
抽象类中成员变量可以是各种类型,但接口中成员变量只能是public static final
jdk1.8以前:抽象类中方法可以有方法体,而接口中方法不行。
1.8以后:接口里也可以有包含方法体的默认方法
抽象方法就是为了被重写,所以肯定不能用private修饰符。1.9以后,允许将接口方法定义为private
深拷贝和浅拷贝的区别是什么
深拷贝:
是指将一个对象复制到另一个对象,新对象与原对象不共享
引用类型属性(如数组、集合、对象等),也就是说,新对象和原对象的引用类型属性指向的是不同的地址,修改其中一个对象中的引用类型属性,不会影响另一个对象中的属性值。浅拷贝:
是指将一个对象复制到另一个对象,新对象与原对象共享
引用类型属性,也就是说,新对象与原对象中的引用类型属性指向的是同一个地址,修改器中一个对象的引用类型属性,会影响到另一个对象的属性值,Java中的Object类提供了clone方法来实现浅拷贝
String、StringBuffer、StringBuilder
可变性
String不可变,StringBuffer和StringBuilder可变
例如给string类型s赋值“abcd”,第二次给s赋值为“abcdef”,不是在原内存地址上修改,而是重新生成一个新对象,新地址。指针指向新的string对象
每次+
操作:隐式在堆上new
了一个跟原字符串相同的StringBuilder
对象,再调用append方法,拼接+
后面的字符
String直接创建的字符串会将字符串对象的引用存储在常量池中,如果一个string对象已经创建过了,那么第二次就直接返回string对象在字符串常量池中的引用
线程安全
string不可变常量,所以线程是安全的。
StringBuffer是线程安全的,内部使用synchronized同步。
StringBuilder线程不安全。
性能
StringBuilder因为没加锁比StringBuffer性能更快,不要求线程安全的情况下,多数选StringBuilder
反射的理解
反射机制是指在运行时,对于任意一个类,通过反射都能够知道这个类的所有属性和方法,在java中,反射可以通过Class类的一些方法来获取Constructor
、Method
、Filed
等类的信息,通过这些信息可以实现对类的实例化
、调用方法
、获取字段值
等操作,这种动态获取信息的功能称为反射机制。
class类对象的获取方式
主要三种
根据类名:类名.class
根据对象:对象名.getClass()
根据全限定类名:Class.forName(全限定类名)
还有通过类加载器获取ClassLoader.loadClass()方法
Object类中的常用方法
- Object类是Java中所有类的基类,她定义了一些常用的方法,包括
equals(Object obj):
判断当前对象是否与另一个对象相等,通常需要重写该方法hashCode():
返回当前对象的哈希码,用于哈希表等数据结构toString():
返回当前对象的字符串表示,通常需要重写该方法getClass():
返回当前对象的类类型wait():
使当前线程等待,直到其他线程调用该对象的notify()或notifyAll()方法notify():
唤醒一个等待中的线程notifyAll():
唤醒所有等待中的线程finalize():
在垃圾回收器回收对象之前调用,用于释放资源等清理工作
IO流
分为哪几种
inputstream/Reader 字节输入流和字符输入流,是所有输入流的基类
Outputstream/Writer 字节输出流和字符输出流,是所有输出流的基类
字节/字符缓冲流的作用
缓冲流先将数据加载至缓冲区,再从缓冲区一次性读取或写入多个字节,从而避免频繁的IO操作,提高流的传输效率。
ArrayList和LinkedList区别
ArrayList
底层数据结构:基于动态数组实现,连续内存存储。
查找:所以适合下标查询(因为数组是连续的内存空间)时间复杂度为O(1)
插入删除:除了尾部都不适合,性能消耗很大。比如:执行add(E e)
方法的时候, ArrayList
会默认在将指定的元素追加到此列表的末尾,这种情况时间复杂度就是 O(1)。
因为如果在指定位置i插入删除时候会涉及到一个元素的移动(从插入位置开始的元素向后/向前复制一份)这种情况时间复杂度就为 O(n-i)。所以我们插入的时候使用尾插,并且指定一个合适的初始容量可以极大提升性能,甚至超过LinkedList。
另外因为数组长度是固定的,arraylist有一个扩容机制,以无参数构造方法创建 ArrayList
时,实际上初始化赋值的是一个空数组。当真正对数组进行添加元素操作时,才真正分配容量。即向数组中添加第一个元素时,数组容量扩为 10。当超出长度时,他会新建一个数组(根据扩容因子),然后将老数组的数据拷贝到新的数组。
内存占用:ArrayList底层是数组,内存连续,节省内存。
LinkedList
底层数据结构: 底部基于双向链表实现,内部使用Node节点来存储元素,不是连续的。
插入删除:所以适合元素头尾部的插入删除,因为链表有很多节点,节点有指针可以指向下一个节点,只需要改变指针的指向就可以实现插入和删除 近似 O(1),但是也不适合指定位置i的插入删除,因为要先移动到指定位置再插入。时间复杂度近似为o(n)。
查找:不适合元素的查询,需要逐一的遍历来找到目标元素,时间复杂度O(n)。
内存占用:LinkedList是双向链表,需要存储数据和两个指针,更占用内存。
Vector
Vector
是 List
的古老实现类,底层使用 Object[ ]
存储,线程安全的。
ArrayList 和 Array(数组)的区别
ArrayList
会根据实际存储的元素动态地扩容或缩容,而Array
被创建之后就不能改变它的长度了,容量是固定的ArrayList
允许你使用泛型来确保类型安全,Array
则不可以。ArrayList
中只能存储对象。对于基本类型数据,需要使用其对应的包装类(如 Integer、Double 等)。Array
可以直接存储基本类型数据,也可以存储对象。ArrayList
支持插入、删除、遍历等常见操作,并且提供了丰富的 API 操作方法,比如add()
、remove()
等。Array
只是一个固定长度的数组,只能按照下标访问其中的元素,不具备动态添加、删除元素的能力。ArrayList
创建时不需要指定大小,而Array
创建时必须指定大小
红黑树
- 红黑树是一种自平衡的二叉搜索树,具有以下特征
- 每个节点要么是黑色,要么是红色
- 根节点是黑色的
- 所有叶子结点都是黑色的空节点(NIL节点)
- 如果一个节点是红色的,则它的啷个子节点都是黑色
- 任意节点到其每个叶子结点的所有路径都包含相同数目的黑色节点
- 这些特征保证了红黑树在插入和删除节点时能够保持平衡,从而保证了其查找、插入、删除操作的时间复杂度都是O(log n)级别的。
HashMap
底层实现:
jdk1.8之前:数组+单向链表实现。
若遇到哈希冲突,就判断该元素与要存入的元素的 key 是否相同,如果相同的话,直接覆盖,不相同就通过拉链法解决冲突。即数组中每一格就是一个链表,将冲突的值直接加到链表中即可。
jdk1.8之后:当链表长度大于8且只有数组长度超过64,链表会转变为红黑树,元素以内部类Node节点存在。否则,就是只是执行 resize()
方法对数组扩容。
put方法:
1.当table数组为null或者长度为0时进行resize扩容初始化数组
2.计算key的hash值,找到对应的数组下标,如果没有产生hash冲突,就直接插入元素存入数组
3.如果产生hash冲突有元素,就要和插入的key先进行比较,key相同就直接覆盖取代该元素,如果不同,则判断是否是红黑树节点,如果是,就放入树中,如果为链表节点,就遍历链表插入尾部。
当链表长度大于8,并且数组长度大于等于64则将链表转变为红黑树,否则就只是执行数组扩容,当长度低于6则将红黑树转回链表。
4.如果key为null,就存在数组下标为0的位置。HashMap
可以存储 null 的 key 和 value,但 null 作为键只能有一个,null 作为值可以有多个
5.并且其数组也是和Arraylist一样有数组扩容机制
get方法:
获取时,直接找到hash值对应的下标,在进一步判断key是否相同,从而找到对应值。
扩容机制
在添加元素或初始化的时候需要调用resize方法进行扩容,第一次添加数据初始化数组长度为16,以后每次每次扩容都是达到了扩容阈值(数组长度*0.75)每次扩容的时候,都是扩容之前容量的2倍;扩容之后,会新创建一个数组,需要把老数组中的数据挪动到新的数组中没有hash冲突的节点,则直接使用e.hash & (newCap - 1)计算新数组的索引位置如果是红黑树,走红黑树的添加如果是链表,则需要遍历链表,可能需要拆分链表,判断(e.hash &oldCap)是否为0,该元素的位置要么停留在原始位置,要么移动到原始位置+增加的数组大小这个位置上
加载因子为什么是0.75不是1
加载因子是控制数组存放数据的疏密程度,加载因子越接近1,数组存放的就越密,也就是会让链表长度增加,加载因子越接近0,数组存放数据也就越稀疏,hash冲突次数也就越少
加载因子太大,查询效率较低(因为hash冲突概率大,链表长度大,要一个个遍历)
加载因子太小,数组的空间利用率太低,存放的数据很分散
所以官方给出了空间和时间上的比较好的平衡点,也就是0.75
HashMap如何避免内存泄露问题
自定义对象为key的时候,一定要重写equals和hashcode保证对象key不重复创建。
HashMap 和 Hashtable 的区别
- 线程是否安全:
HashMap
是非线程安全的,Hashtable
是线程安全的,因为Hashtable
内部的方法基本都经过synchronized
修饰。(如果你要保证线程安全的话就使用ConcurrentHashMap
吧!); - 效率: 因为线程安全的问题,
HashMap
要比Hashtable
效率高一点。另外,Hashtable
基本被淘汰,不要在代码中使用它; - 对 Null key 和 Null value 的支持: HashMap允许key和value为null,而HashTable不允许;
- 数据结构:Hashtable还是数组+链表。
HashMap 和 HashSet 区别
HashSet
底层就是基于 HashMap
实现
HashMap | HashSet |
---|---|
实现了 Map 接口 | 实现 Set 接口 |
存储键值对 | 仅存储对象 |
调用 put() 向 map 中添加元素 | 调用 add() 方法向 Set 中添加元素 |
Java常见的集合类
第一个是Collection属于单列集合,第二个是Map属于双列集合
在Collection中有三个子接口List、Set和Queue。在我们平常开发的过程中用的比较多像list接口中的实现类ArrarList和LinkedList。在Set接口中有实现类HashSet和TreeSet。
在map接口中有很多的实现类,平时比较常见的是HashMap、TreeMap,还有一个线程安全的map:ConcurrentHashMap。
Collection与Collections的区别
Collection是java集合类中
接口,用于表示一组对象的集合。它提供了一些通用的操作,如添加、删除、遍历等。
Collections是Java中的一个工具类,它包含了一组静态方法,用于操作各种集合类型。它提供了一些常用的算法和工具方法,如排序、查找、复制等。Collections类中的方法通常是针对Collection类型的实例进行操作的。
它们是两个独立的概念。