第一章、基础技术栈
1.1)集合,string等基础问题
1、arraylist ,linkedlist的区别,为啥集合有的快有的慢
①[ArrayList]它的底层是数组,有下标的概念,可以通过index下标直接定位元素,所以查询快;在增删时,会进行扩容判断和拷贝,所以增删慢。
②LinkedList的底层是[双向链表] 。每次查询都要循环遍历,所以查询慢;在增删时只需要链接新的元素,而不用修改列表中剩余的元素,所以增删快。
集合有的快有的慢,主要是因为它们的底层实现不同。
2、字符串倒叙输出
有很多种方法,最简单的就是使用StringBuilder类,它里面有个reverse()方法,可以将字符串进行倒序输出。
第二种是String类的toCharArray()方法把字符串转换为char数组。使用循环将数组中的字符进行倒序交换,输出结果。
2.1、字符串常用方法
toCharArray() 将字符串转换为字符数组
split(String regex) 切割
substring(int beginIndex, int endIndex)截取字串
int length() 获取字符串的长度
int indexOf(String str) 获取特定字符的位置
toUpperCase() 转大写转小写忽略大小写
2.2、字符串+号拼接的底层原理
看一段代码:
public class Test {
public static void main(String[] args) {
String a = \"abc\";
String b = \"def\";
String c = \"abc\" + \"def\";
String d = a + \"def\";
String e = new String(\"abc\") + \"abc\";
String g = \"abcdef\";
}
}
运行本项目java
运行
12345678910
反编译后
public class Test
{
public Test()
{
}
public static void main(String args[])
{
String a = \"abc\";
String b = \"def\";
String c = \"abcdef\";
String d = (new StringBuilder()).append(a).append(\"def\").toString();
String e = (new StringBuilder()).append(new String(\"abc\")).append(\"abc\").toString();
String g = \"abcdef\";
}
}
运行本项目java
运行
1234567891011121314151617
结论:字符串+拼接如果无变量和new关键词参与则在字符串常量池。。
如果有变量或者是new关键词参与拼接, 那么就会都new出一个StringBuilder对象, 然后使用append方法, 随后又使用toString方法来new一个对应的String类, 这样繁琐的创建对象, 不仅消耗时间, 还会消耗内存资源。
所以:循环体内,字符串的连接方式,使用 StringBuilder 的 append 方法进行扩展。而不要使用+
3、讲一下Java的集合框架
说到集合框架我们可以把集合框架分为两个部分,单列集合collection和双列集合顶层接口 map。其中map的存储是key-value形式,collection下面有set接口特点是存储无序不可重复,list接口存储有序可重复。
| 列表list(集合有序可重复,可有null元素) | 集set(元素是无序不可重复的) | map(顶层接口,存储的是键值对,键是唯一的。) |
|---|---|---|
| Vector:底层数据结构是数组数据结构.特点是查询和增删速度都很慢。 集合长度。线程安全。 | HashSet:底层数据结构是哈希表、存取速度快、元素唯一、线程不安全。 | HashMap:底层是哈希表数据结构;允许使用null键和null值;线程不安全,效率高; |
| ArrayList:底层的数据结构是数组数据结构,特点是查询速度快(因为带下标),但是增删速度稍慢,因为当元素多时,增删一个元素则所有元素的下标都得改变,线程不安全。默认长度是10,当超过长度时,按1.5倍延长集合长度。 | TreeSet:底层数据结构式二叉树。可以对Set集合中的元素进行排序。元素有序、线程不安全。 | HashTable: 底层是哈希表数据结构;不可以使用null键和null值线程安全效率低,因为它里面的方法都是用了 synchronized关键字修饰。 |
| LinkedList:底层数据结构式双向链表数据结构(即后面一个元素记录前一个),特点:查询速度慢,因为每个元素只知道前面一个元素,但增删速度快,因为元素再多,增删一个只要让其前后 的元素 重新相连即可,线程不安全。 | ①特点:有序的,保证元素不可重复的前提下,维护了一层添加顺序,判断是否重复的依据:hashCode、equals | TreeMap:底层是二叉树结构;允许使用null键和null值;线程不安全; |
4、定义线程安全的map,有哪些方法,ConcurrentHashMap原理
①定义线程安全的Map可以通过在每个方法上添加synchronized关键字实现或者使用Collections.synchronizedMap方法来保证线程安全。但是这样效率比较低。
②还可使用ConcurrentHashMap。原理是采用分段锁的机制,将整个Map分成多个Segment,在每个Segment上都加锁,不同的线程可以同时访问不同的Segment,这样兼顾了线程安全和运行效率。
5、equals与==
①==:如果比较的对象是基本数据类型,则比较的是数值;如果比较的是引用数据类型,则比较的是对象的地址值。
②equals():用来比较两个对象的内容是否相等。equals方法不能用于基本数据类型的变量,如果没有对equals方法进行重写,则比较的是对象的地址值。
6、hashtable和hashmap的区别
hashmap底层是哈希表存储是无序的它和HashTable最大的区别是hashmap线程不安全并且key值允许为null,而HashTable线程安全并且key值不允许为null,由于Hashtable直接在put和get方法上面加synchronized关键字来实现线程安全所有操作都需要竞争同一把锁所以效率很低。
| 区别 | 存储 | 底层 | 如何选择 | key是否允许null | 是否线程同步 |
|---|---|---|---|---|---|
| HashMap | 存储无序 | 哈希表 | 不需要排序 | 允许 | 非线程安全 |
| HashTable | 存储无序 | 哈希表 | 需要线程安全 | 不允许 | 线程安全 |
| TreeMap | 存储有序 | 红黑树 | 需要排序 | 不允许 | 非线程安全 |
| LinkedHashMap | 存储有序 | 链表和哈希表 | 需要存储有序 | 允许 | 非线程安全 |
public class Test1{
//1、私有化本类所有的构造方法
private Test1(){}
//2、直接在本类中创建唯一对象
private static Test1 t1 = new Test1();
//3、提供外界获取唯一对象的方法(公共的、静态的)
public static Test1 getInstance(){
return t1;
}
}
运行本项目java
运行
1234567891011
懒汉式单例设计模式的特点
①懒汉式模式的特点是类加载时没有生成单例,只有当第一次调用 getlnstance (获取实例)方法 时才去创建这个单例。(比较懒别人喊一次才动一次?)。
②好处:不存在浪费内存的问题,弊端:在多线程环境下,可能不能保证对象是唯一的
public class Test2{
//1、私有化本类所有的构造方法
private Test2(){ }
//private和static修饰的成员变量
private static Test2 t2;
//3、提供外界获取唯一对象的方法(公共的、静态的)
public static Test2 getInstance(){
if(t2 == null){
//2、在本类中创建唯一对象
t2 = new Test2();
}
return t2;
}
}
运行本项目java
运行
123456789101112131415
8、什么是哈希表
哈希表又叫散列表是一种数据结构,特点是查找增删都很快。哈希表里面每个数据都有唯一的键(key),把这个关键码值(key)通过映射函数映射到哈希表中一个位置来访问。
这个映射函数叫做哈希函数(散列函数),可以把任意长度的key值变换输出成固定长度的哈希值(Hash value)。一个好的哈希函数能够将键均匀地映射到哈希表中,以减少冲突和查找时间。
Hash code是一种编码方式,在Java中,每个对象都会有一个hashcode。Java可以通过这个hashcode来识别一个对象。
9、什么是哈希冲突,怎么解决
哈希函数把key变换输出为哈希码(哈希值),当不同的key值产生的哈希值H(key)是一样的,就产生了哈希冲突。
解决哈希冲突的方法
(1) 再哈希法
当发生冲突时,用不同的哈希函数计算地址,直到无冲突。虽然不易发生聚集,但是增加了计算时间。
(2) 链地址法
通过将具有相同哈希码的数据元素存储在一个链表中,来避免冲突的发生。
在查询、插入或删除数据元素时,首先根据哈希码找到对应的链表,然后在链表中搜索或修改相应的数据元素。
(3)建立公共溢出区
将哈希表分为基本表和溢出表两部分,将冲突的元素存储在另一个溢出表中
10、final关键字可以修饰哪些对象
final类:不可被继承,如java.lang.Math就是一个 final类,不可被继承。
final方法:不可被重写
final变量:final修饰的变量是一个常量一般和static联用。只能被赋值一次在初始化后不可改变变量值。如果final变量是引用变量,则不可以改变它的引用对象,但可以改变对象的属性。
public static final double pi=3.14;
11、lise集合便利查找其中的一项怎么处理比较快。
12、固定的不可变的一些对象,放到哪里让全局都可以使用?
放在常量的类里,配置到数据库里,放在配置文件里,放到缓存里面,第三方配置中心
1.2)java8新特性,xxx原理。反射等高级问题
1、Java8有哪些新特性
Java8出现了很多新特性我说几个比较大的改变,第一个是接口可以写默认方法和静态方法,默认方法用default修饰符标记,不强制实现类实现默认方法。第二个是stream流,提供了便捷快速的操作数据的方式。第三个是Lambda表达式和函数式接口,函数式接口仅有一个抽象方法的接口,Lambda 表达式本质上是一个匿名方法,让我们的代码更简洁直观。第四个Java 8引入了重复注解的概念,允许在同一个地方多次使用同一个注解。
2、单例模式
1)饿汉式单例设计模式的特点
①饿汉式模式的特点是类一旦加载就创建一个单例,保证在调用 getInstance (获取实例)方法之前单例就已经存在了。(比较饿,没喊就迫不及待的创建了)。
②好处:就算在多线程环境下,也一定可以保证对象是唯一的。弊端:创建比较早,有浪费内存的现象。
2)懒汉式单例设计模式的特点
在第一次使用时才创建实例,但需要考虑线程安全问题。
3、
4、
5、
第二章、深入技术栈
2.1)JVM 运行机制等问题
1、JVM是什么 包含哪些模块
①JVM是Java虚拟机,是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。Java语言非常重要的特点跨平台性就是通过jvm实现,Java虚拟机在执行字节码时,把字节码解释成具体平台上的机器指令执行。
整个JVM 框架由加载器加载文件,然后执行器在内存中处理数据,通过本地接口与异构系统交互
②JVM包括
1、类加载器(Class Loader )类加载器的作用是加载类文件到内存
2、执行器(Execution Engine) 执行引擎执行引擎也叫做解释器,负责解释命令,提交操作系统执行。
3、本地接口(Native Interface )作用是融合不同的编程语言为Java 所用
4、运行时数据区(Runtime data area) 我们所有写的程序都被加载到这里之后才开始运行。包括:
(1)线程共享的堆(含有字符串常量池),元空间(元空间在本地内存中,包含了加载的类信息和运行时常量池)
(2)线程私有的虚拟机栈、本地方法栈以及程序计数器
2、JVM 内存区域(运行时数据区)
①栈是运行时的单位 , 是线程私有的,它的生命周期和线程相同,随着线程的创建而创建,随着线程的死亡而死亡。所有的 Java 方法调用都是通过栈来实现的,方法调用的数据需要通过栈进行传递,每一次方法调用都会有一个对应的栈帧被压入栈中,每一个方法调用结束后,都会有一个栈帧被弹出。
②Java 堆是所有线程共享的最大的一块内存区域,唯一目的就是存放对象实例。里面包含字符串常量池(针对字符串专门开辟的一块区域,主要目的是为了避免字符串的重复创建。)。因为Java 堆是垃圾收集器管理的主要区域,因此也被称作 GC 堆。通过垃圾回收算法 Java 堆还可以细分为:新生代和老年代。
③方法区(元空间)
方法区是被所有线程共享,方法区会存储已被虚拟机加载的 类信息、字段信息等。JDK1.8 最大的变化就是方法区的位置转移到了本地内存中被称为元空间。元空间中还包含运行时常量池:各种字面量和符号引用的常量池表 。
④程序计数器
线程私有的,是一块很小的内存空间,每个线程都有一个程序计数器,代码的流程控制,如分支、循环、跳转、异常处理、线程恢复等功能都需要依赖这个计数器来完成。
⑤本地方法栈
本地方法栈为JVM调用native方法时服务,一个Native方法就是一个java调用非java代码的接口。
PS:直接内存在本地内存中,不是虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用。而且也可能导致 OutOfMemoryError 错误出现
3、垃圾收集器之G1 收集器
①垃圾回收器通常是作为一个单独的低级别的线程运行,通过垃圾回收算法对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收。
②JDK9 开始,G1 垃圾收集器成为了默认的垃圾收集器。使用标记整理算法主要有初始标记,并发标记,最终标记,筛选回收四个过程。G1将整个堆分为大小相等的多个Region(区域),G1跟踪每个区域的垃圾大小,在后台维护一个优先级列表,每次根据允许的收集时间,优先回收价值最大的区域,已达到在有限时间内获取尽可能高的回收效率。
4.1、内存中死亡对象判断方法
①引用计数法
每当有一个地方引用它,计数器就加 1;
当引用失效,计数器就减 1;
任何时候计数器为 0 的对象就是不可能再被使用的。
②可达性分析算法
以 “GC Roots” 的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的,需要被回收。
引用的概念:
引用分为强引用、软引用、弱引用、虚引用四种(引用强度逐渐减弱)
1、强引用:最普遍的引用如String s = new String(“aaa”)。如果一个对象具有强引用,垃圾回收器绝不会回收它
2、软引用:如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。
3、弱引用:的对象拥有更短暂的生命周期。一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
4、虚引用:虚引用并不会决定对象的生命周期。是一种形同虚设的引用,随时会被回收。
4.2、新老年代的含义和晋升逻辑
①长期存活的对象将进入老年代
对象首先在 Eden 区域分配。如果对象在 Eden 出生并经过第一次 Minor GC (新生代GC)后仍然能够存活年龄就增加 1 岁,当它的年龄增加到一定程度(默认为 15 岁),就会被晋升到老年代中。
②大对象直接进入老年代
大对象就是需要大量连续内存空间的对象(比如:字符串、数组)。
③新生代 GC(Minor GC):指发生新生代的的垃圾收集动作,Minor GC 非常频繁,也比较快。
老年代 GC(Major GC/Full GC):指发生在老年代的 GC,Major GC 的速度一般会比 Minor GC 的慢 10 倍以上。
4.3、垃圾回收算法
①标记清除算法
标记阶段,标记所有的可访问对象。
收集阶段,垃圾收集算法扫描堆并回收所有的未标记对象。
② 标记整理算法
标记-整理(Mark-and-Compact)算法是根据老年代的特点提出的一种标记算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。
③复制算法
将内存按照容量大小分为大小相等的两块,每次只使用一块,当一块使用完了,就将还存活的对象移到另一块上,然后在把使用过的内存空间移除。特点:不会产生空间碎片;内存使用率极低
④分代收集算法
不同的对象的生命周期是不一样的。分代回收把不同生命周期的对象 放在不同代上,在新生代中,有大量对象死去和少量对象存活,所以采用复制算法,老年代中因为对象的存活率极高,所以采用标记清理或者标记整理算法进行回收。
5、常用 JVM 调优手段
1、内存分配:根据应用程序的需求调整JVM的内存分配。如果应用程序需要处理大量数据,则可以增加JVM的堆内存和非堆内存。
2、垃圾回收:JVM的垃圾回收是一项重要的任务,它可以释放无用的对象并回收内存。通过选择不同的垃圾回收器、调整垃圾回收器的参数和设置合适的内存阈值等,可以提高垃圾回收的效率。
3、线程管理:JVM中的线程是Java应用程序的核心组成部分。通过调整线程的数量、设置线程的优先级和使用线程池等方式,可以提高Java应用程序的并发性能。
4、类加载:Java应用程序需要在运行时动态加载类。通过优化类的加载过程,可以提高应用程序的启动速度和响应性能。
5、编译优化:JIT编译器可以将Java代码编译为本机代码,从而提高应用程序的执行速度。通过设置JIT编译器的参数和选择适当的编译器,可以提高编译器的性能和效率。
6、I/O优化:I/O操作是Java应用程序中常见的性能瓶颈之一。通过使用缓冲区、选择合适的I/O库、减少I/O操作次数等方式,可以提高I/O操作的效率。
6、类的加载
JVM中类的装载是由类加载器和它的子类来实现的,类加载器负责在运行时查找和装入类文件中的类。当Java程序需要使用某个类时,JVM会确保这个类已经被加载、连接(验证、准备和解析)和初始化。
1)如果类存在直接的父类并且这个类还没有被初始化,那么就先初始化父类;
2)如果类中存在初始化语句,就依次执行这些初始化语句。
7、主内存和本地内存
①主内存 :所有线程创建的实例对象都存放在主内存中,不管该实例对象是成员变量还是方法中的本地变量(也称局部变量)
②本地内存 :每个线程都有一个私有的本地内存来存储共享变量的副本,并且,每个线程只能访问自己的本地内存,无法访问其他线程的本地内存。本地内存是 JMM 抽象出来的一个概念,存储了主内存中的共享变量副本。
8、类什么时候被初始化?
1)创建类的实例,也就是 new 一个对象
2)访问某个类或接口的静态变量,或者对该静态变量赋值
3)调用类的静态方法
4) 反 射 (Class.forName(“com.lyj.load”))
5)初始化一个类的子类(会首先初始化子类的父类)
6)JVM 启动时标明的启动类,即文件名和类名相同的那个类只有这 6 中情况才会导致类的类的初始化。
类的初始化步骤:
1)如果这个类还没有被加载和链接,那先进行加载和链接
2)假如这个类存在直接父类,并且这个类还没有被初始化(注意:在一个类加载器中,类只能初始化一次),那就初始化直接的父类(不适用于接口)
3)加入类中存在初始化语句(如 static 变量和 static 块),那就依次执行这些初始化语句。



还没有评论呢,快来抢沙发~