1、java容器:2个
Collection:存储对象的集合
Map:存储着键值对(两个对象)的映射表
collection
-
1. Set
- TreeSet(有序,唯一):
- 基于红黑树实现,支持有序性操作,例如根据一个范围查找元素的操作。
- 但是查找效率不如 HashSet,HashSet 查找的时间复杂度为 O(1),TreeSet 则为 O(logN)。
- HashSet(无序,唯一):
- 基于哈希表实现,支持快速查找,但不支持有序性操作。
- 并且失去了元素的插入顺序信息,也就是说使用 Iterator 遍历 HashSet 得到的结果是不确定的。
- LinkedHashSet:
- 具有 HashSet 的查找效率,并且内部使用双向链表维护元素的插入顺序。
- LinkedHashSet是HashSet的子类,并且内部是通过LinkedHashMap实现的
-
2. List
- ArrayList:
- 基于动态数组实现,支持随机访问。
- Vector:
- 和 ArrayList 类似,但它是线程安全的。
- LinkedList:
- 基于双向链表实现,只能顺序访问,但是可以快速地在链表中间插入和删除元素。不仅如此,LinkedList 还可以用作栈、队列和双向队列。
-
3. Queue
- LinkedList:可以用它来实现双向队列。
- PriorityQueue:基于堆结构实现,可以用它来实现优先队列
Map;
- treeMap:
- 基于红黑树实现
- HashMap:
- 基于哈希表实现:数组+链表
- HashTable:
- 和HashMap类似,但是线程安全的;同一时刻多个线程同时写入HashTable不回答导致数据不一致。
- 它是遗留类,不应该去使用它,而是使用 ConcurrentHashMap 来支持线程安全,
- ConcurrentHashMap 的效率会更高,因为 ConcurrentHashMap 引入了分段锁。
- HsahTable:数组+链表组成,链表是为了解决哈希冲突而存在的
- LinkedHashMap:
- 使用双向链表来维护元素的顺序,顺序为插入顺序或者最近最少使用(LRU)顺序
- 继承自HashMap,底层是基于拉链式散列结构即由数组和链表 或 红黑树组成
2、List、Map、Set区别
3、如何选择集合?
- 根据键值对获取元素值:Map接口下的集合
- 需要排序:TreeMap
- 不需要排序:HashMap
- 线程安全:ConcurrentHashMap
什么是线程安全?
进程中最小执行单元是线程;
多线程:串行和并行
串行:任务B必须在任务A完成后执行,两个任务不能再时间上重叠
并行:同一时刻,A、B、C任务同时进行
多线程:打开腾讯管家这个进程,同时进行垃圾清除、查杀病毒、电脑加速等多线程
什么是线程安全?
多线程访问时,采用加锁机制,当一个线程访问该类的某个数据时,进行保护,
其余的线程不能进行访问指导该线程读取完,其余线程才可使用
线程不安全:不提供数据访问保护,有可能出现多个线程先后更改数据造成得到的数据是不准确的
4、HashMap和HashTable的区别
-
1、继承的父类不同:
其中,Dictionary类是一个荒废的类,所以它的子类也没有人用了
-
2、对外提供的接口不同
Hashtable比HashMap多提供了elments() 和contains() 两个方法。
elments() 方法继承自Hashtable的父类Dictionnary。elements() 方法用于返回此Hashtable中的value的枚举。
contains()方法判断该Hashtable是否包含传入的value。它的作用与containsValue()一致。事实上,contansValue() 就只是调用了一下contains() 方法。
-
3、线程安全性不同
Hashtable是线程安全的,它的每个方法中都加入了Synchronize方法。在多线程并发的环境下,可以直接使用Hashtable,不需要自己为它的方法实现同步
HashMap不是线程安全的,在多线程并发的环境下,可能会产生死锁等问题。具体的原因在下一篇文章中会详细进行分析。使用HashMap时就必须要自己增加同步处理,
虽然HashMap不是线程安全的,但是它的效率会比Hashtable要好很多。这样设计是合理的。在我们的日常使用当中,大部分时间是单线程操作的。HashMap把这部分操作解放出来了。当需要多线程操作的时候可以使用线程安全的ConcurrentHashMap。ConcurrentHashMap虽然也是线程安全的,但是它的效率比Hashtable要高好多倍。因为ConcurrentHashMap使用了分段锁,并不对整个数据进行锁定。
-
4、对 Null key 和 Null value支持不同
HashMap可以存空的key 和 value,但HashTable不支持
-
5、初始容量大小和每次扩充容量大小的不同
Hashtable默认的初始大小为11,之后每次扩充,容量变为原来的2n+1。HashMap默认的初始化大小为16。之后每次扩充,容量变为原来的2倍。
5、arraylist和linkedlist的区别
- 都是实现List接口的容器类,存储一系列的对象引用,都可以对元素进行增删改查
- 1、ArrayList实现了基于动态数组的数据结构,LinkedList是基于链表的数据结构
- 2、 对于随机访问get和set,ArrayList要优于LinkedList,因为LinkedList要移动指针
- 3、对于添加和删除操作add和remove,LinkedList要比ArrayList快,因为ArrayList要移动数据。
- 4、但是实际情况并非这样,对于添加或删除,LinkedList和ArrayList并不能明确说明谁快谁慢。
- 5、查操作时用ArrayList,增删操作:LinkedList
7、Java gc机制
- 垃圾不回收,内存会被占满消耗空
- 要回收的内存对象:不可能再被任何途径利用的对象,如何找到这些对象??
- 1、引用计数法
- 给对象中添加一个引用计数器,每当一个地方引用这个对象时,计数器值+1;当引用失效时,计数器值-1。任何时刻计数值为0的对象就是不可能再被使用的
- 2、可达性分析法
- 通过一系列称为“GC Roots”的对象作为起始点,从这些节点向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链(即GC Roots到对象不可达)时,则证明此对象是不可用的。
-
如何选取GCRoots对象呢?在Java语言中,可以作为GCRoots的对象包括下面几种:
(1). 虚拟机栈(栈帧中的局部变量区,也叫做局部变量表)中引用的对象。
(2). 方法区中的类静态属性引用的对象。
(3). 方法区中常量引用的对象。
(4). 本地方法栈中JNI(Native方法)引用的对象
- 3、引用状态
- 引用类型的数据中存储的数值代表的是另一块内存的起始地址,就称这块内存代表着一个引用
- 1)、强引用
- 类似"Object obj = new Object()"这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象
- 2)、软引用
- 描述有用但并非必需的对象。在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围进行二次回收
- 3)、弱引用
- 描述非必需对象。被弱引用关联的对象只能生存到下一次垃圾回收之前,垃圾收集器工作之后,无论当前内存是否足够,都会回收掉只被弱引用关联的对象
- 4)、虚引用
- 这个引用存在的唯一目的就是在这个对象被收集器回收时收到一个系统通知,被虚引用关联的对象,和其生存时间完全没关系
- 1)、强引用
- 方法区的垃圾回收
- 1、废弃常量
- 如何判断废弃常量呢?
- 以字面量回收为例,如果一个字符串“abc”已经进入常量池,但是当前系统没有任何一个String对象引用了叫做“abc”的字面量,那么,如果发生垃圾回收并且有必要时,“abc”就会被系统移出常量池。常量池中的其他类(接口)、方法、字段的符号引用也与此类似
- 2、无用的类
-
如何判断无用的类呢?需要满足以下三个条件
1. 该类的所有实例都已经被回收,即Java堆中不存在该类的任何实例。
2. 加载该类的ClassLoader已经被回收。
3. 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
-
8、垃圾回收算法:复制算法、标记-清除算法、标记-整理算法。
1)、标记-清除算法
分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,标记完成后统一回收所有被标记的对象
这种算法的不足主要体现在效率和空间,从效率的角度讲,标记和清除两个过程的效率都不高;从空间的角度讲,标记清除后会产生大量不连续的内存碎片, 内存碎片太多可能会导致以后程序运行过程中在需要分配较大对象时,无法找到足够的连续内存而不得不提前触发一次垃圾收集动作
2)、复制算法
为了解决效率问题而出现的,它将可用的内存分为两块,每次只用其中一块,当这一块内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已经使用过的内存空间一次性清理掉。
这样每次只需要对整个半区进行内存回收,内存分配时也不需要考虑内存碎片等复杂情况,只需要移动指针,按照顺序分配即可。复制算法的执行过程如图:
3)、标记-整理算法
- 复制算法在对象存活率较高的场景下要进行大量的复制操作,效率很低。
万一对象100%存活,那么需要有额外的空间进行分配担保。
老年代都是不易被回收的对象,对象存活率高,因此一般不能直接选用复制算法。
根据老年代的特点,有人提出了另外一种标记-整理算法,过程与标记-清除算法一样,
不过不是直接对可回收对象进行清理,而是让所有存活对象都向一端移动,然后直接清理掉边界以外的内存。
9、抽象类和接口的区别
1)、抽象类和普通类的区别
抽象类不能被实例化,只是一种概念而非具体;抽象类可以被实例化调用
抽象类和普通类都可以被继承,但抽象类的子类必须重写重写继承的方法,除非子类也是抽象类
2)、抽象类和接口的区别
①概念不一样:
接口是对动作的抽象;抽象类是对本质的抽象
抽象类 表示的是这个类是什么;接口表示的是这个类能做什么;
女人、男人的抽象类是“人”,接口是“吃东西”;这些类可以去实现接口
②使用不一样:
抽象类和接口都是用来抽象具体对象的,但接口的抽象级别最高
抽象类可以有具体的方法和属性,接口只能有抽象方法和不可变常量
抽象类主要用来抽象类别,接口用来抽象功能
抽象类是重构的结果,接口是设计的结果
③使用方向:
接口:抽象一个功能
抽象类:一个事物的本质
10、线程的实现方式有哪些
java创建多线程的方式(参考文章,写的很好)
extend Thread、
implement runnable、
implement callable
11、Integer和int的区别
1)、Integer是int的包装类,int是java的一种基本的数据类型
2)、Integer必须实例化才能使用,int变量不需要
3)、Integer实际是对象的引用,当new一个Integer时,是生成一个指针指向此对象,int是直接存储数据值
4)、Integer的默认值是null,int的默认值是0;
12、String、StringBuilder与StringBuffer
1)、String 字符串常量
2)、StringBuffer 字符串变量(线程安全)
3)、StringBuilder 字符串变量(线程不安全)
变量是不可更改的,常量是可以更改的,StringBuffer 和 StringBuilder 运行速度高于string
操作是多线程的,那么就要使用StringBuffer,但是在单线程的情况下,还是建议使用速度比较快的StringBuilder。
String:适用于少量的字符串操作的情况
StringBuilder:适用于单线程下在字符缓冲区进行大量操作的情况
StringBuffer:适用多线程下在字符缓冲区进行大量操作的情况
13、内存溢出和内存泄露
内存溢出(out of memory):
程序在申请内存时,没有足够的内存空间供其使用
申请了integer但存的是long,内存溢出
内存溢出就是你要求分配的内存超出了系统能给你的,系统不能满足需求,于是产生溢出
内存泄漏:(memory leak):
指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,
但内存泄露堆积后果很严重,无论多少内存,迟早会被占光
内存泄漏是指你向系统申请分配内存进行使用(new),可是使用完了以后却不归还(delete),结果你申请到的那块内存你自己 也不能再访问(也许你把它的地址给弄丢了),而系统也不能再次将它分配给需要的程序。一个盘子用尽各种方法只能装4 个果子,你装了5个,结果掉倒地上不能吃了。这就是溢出!
14、protected,private,public
public:
具有最大的访问权限,可以访问任何一个在classpath下的类、接口、异常等。它往往用于对外的情况,也就是对象或类对外的一种接口的形式。
protected:
主要的作用就是用来保护子类的。它的含义在于子类可以用它修饰的成员,其他的不可以,它相当于传递给子类的一种继承的东西
default:
有时候也称为friendly,它是针对本包访问而设计的,任何处于本包下的类、接口、异常等,都可以相互访问,即使是父类没有用protected修饰的成员也可以。
private:
访问权限仅限于类的内部,是一种封装的体现,例如,大多数成员变量都是修饰符为private的,它们不希望被其他任何外部的类访问。