侧边栏壁纸
博主头像
996worker

祇園精舎の鐘の聲, 諸行無常の響き有り。

  • 累计撰写 134 篇文章
  • 累计创建 40 个标签
  • 累计收到 3 条评论

阿里面试&网上搜的答案

996worker
2021-07-12 / 0 评论 / 0 点赞 / 55 阅读 / 6,339 字
温馨提示:
本文最后更新于 2021-07-24,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

一面

  1. String类型的几种存储方式,是否是线程安全的,两个字符串相加是否是新的实例?

    答: 字符串直接赋值String str = "abc";和实例化String对象String str = new String("abc");

    • 直接赋值:在堆区开辟空间,“abc”存储在字符串池中,在栈区创建变量str指向“abc”,当我们再次创建变量String str1 = “abc”时,JVM会到字符串池中寻找“abc”,找到后将引用赋值给str1,不会再次开辟空间创建“abc”;
    • 实例化String对象:在堆区开辟空间存储“abc”,然后再在堆区开辟空间创建字符串的对象,将“abc”赋值给字符串对象,最后将对象的引用赋值给str变量,当我们再次创建变量String str1 = “abc”时,JVM会重复执行前面的动作;

第二种方法很耗内存,且运行速度慢,一般情况下采用第一种方法,不允许采用第二种方法。

字符串线程安全,因为不可变。
两字符串相加,属于StringBuilder语法糖,会新实例。

  1. Stringbuffer用append方式,对象是否发生变化?

    答: append方法的作用是在一个StringBuffer对象后面追加字符串。append()方法 相当于"+",但是!!!
    String1+String2 和Stringbuffer1.append("y")虽然打印效果一样,但在内存中表示却不同。String1+String2 存在于不同的两个地址内存,但是Stringbuffer1.append(Stringbuffer2)是放在一起的。

  2. Java的值传递

    答:

    • 对于基本类型 num ,赋值运算符会直接改变变量的值,原来的值被覆盖掉。
    • 对于引用类型 str,赋值运算符会改变引用中所保存的地址,原来的地址被覆盖掉。但是原来的对象不会被改变(重要)。
      值传递(pass by value)是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数;引用传递(pass by reference)是指在调用函数时将实际参数的地址直接传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。
  3. Java内存区域的划分,线程共享的部分,哪些部分会有溢出的情况?
    答:

    • 线程共享区:方法区、堆
    • 线程私有区:虚拟机栈、本地方法栈、程序计数器

程序计数器:

是一块较小的内存空间,可以看作当前线程所执行的字节码的行号指示器。

java多线程就是通过对线程的轮流切换并分配处理器的执行时间的方式来实现的,在任意时刻,一个处理器都只会执行某一个线程中的指令。所以每条线程为了切换后能恢复到正确的执行位置,每条线程都会有一个独立的程序计数器,各条线程之间互不影响,独立存储。

JAVA虚拟机栈:

线程私有,生命周期和线程一样。是描述java方法执行的内存模型。在每个方法执行时都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机中入栈到出栈的过程。局部变量表包括了编译器可知的各种基本数据类型、对象引用和returnAddress类型(指向了一个字节码指令的地址)。其中64位长度的long和double类型的数据会占用2个局部变量空间,其余的数据类型只占用1个。局部变量表所占用的内存在编译器就完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。

栈内存的管理:通过压栈和弹栈操作来完成的,以栈帧为基本单位来管理程序的调用关系,每当有函数调用时,都会通过压栈方式创建新的栈帧,每当函数调用结束后都会通过弹栈的方式释放栈帧

本地方法栈:

与java虚拟机栈发挥的作用十分相似,区别是java虚拟机栈是为虚拟机执行java方法的,本地方法栈是为虚拟机执行native方法的。在本地方法栈中使用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。甚至有的虚拟机(如sun hotspot)直接把本地方法栈和虚拟机栈合二为一。

JAVA堆:

是java虚拟机所管理的内存中最大的一块。java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。所有的对象实例和数组都要在堆上分配。java堆是垃圾收集器管理的主要区域,因此很多时候也被称作GC堆。

从内存回收的角度来看,由于现在收集器基本都采用分代手机算法,所以java堆还可以细分为:新生代和老年代;再细致一点的有Eden空间、From Survivor空间、To Survivor空间等。

从内存分配的角度来看,线程共享的java堆中可能划分出多个线程私有的分配缓冲区。不论如何划分,都与存放内容无关,无论哪个区域,存储的都仍然是对象实例,进一步的划分目的是为了更好地回收内存和更快地分配内存。java堆可以处于物理上的不连续的内存空间中,只要逻辑上是连续的即可,就像我们的磁盘空间一样。在实现时,既可以实现成固定大小的,也可以是可扩展的,不过当前主流的虚拟机都是按照可扩展来实现的。如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。

每个Java程序都运行在一个单独的JVM实例上,每一个实例唯一对应一个堆,一个Java程序内的多个线程也就运行在同一个JVM实例上,因此这些线程之间会共享堆内存。

方法区:

方法区与java堆一样。是各个线程所共享的内存区域,它用于存储class二进制文件,包含了虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然java虚拟机规范把方法去描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-Heap(非堆),目的应该是与java堆区分开来。对于习惯在hotspot虚拟机上开发的开发者来说,更愿意把方法区叫做永久堆。本质上并不一样,仅仅是因为hotspot设计团队把GC分堆收集扩展至方法区,或者说使用永久堆来实现方法区而已,这样hotspot的垃圾收集器可以像管理java堆一样管理这部分内存,能够省去专门为方法去编写内存管理代码的工作。但是这样更容易出现内存溢出的问题。永久堆有上限。而其他不存在永久堆的虚拟机只要没有触碰到进程可用内存上限,就不会出现问题。在jdk7中,已经将放在永久堆的字符常量池移出。

运行时常量池是方法区的一部分,用于存放编译器生成的各种字面量和符号引用。运行时常量池相对于class文件常量池的另外一个重要特征是具备动态性。

  1. final类

    • 修饰类:无法被继承
    • 修饰成员方法:无法被重写
    • 修饰成员变量:无法被修改
    • 修饰局部变量:无法被修改,哪怕值与之前一样也不行
      好处:
  2. final方法比非final快一些

  3. final关键字提高了性能。JVM和Java应用都会缓存final变量。

  4. final变量可以安全的在多线程环境下进行共享,而不需要额外的同步开销。

  5. 使用final关键字,JVM会对方法、变量及类进行优化。

  6. static修饰符

  • 修饰类:不用实例化
  • 修饰方法:可以用类名直接访问到它
  • 修饰变量:共享的数据可以给所有对象使用
    存储位置:静态成员变量存储在方法区,保存一份;动态的存储在堆,n个对象n个数据
    生命周期:静态成员随着类的加载而存在,随着类文件消失二消失;动态的随着对象实例化二存在,可以被垃圾回收。
  1. 深拷贝和浅拷贝

深拷贝,在拷贝引用类型成员变量时,为引用类型的数据成员另辟了一个独立的内存空间,实现真正内容上的拷贝。

(1) 对于基本数据类型的成员对象,因为基础数据类型是值传递的,所以是直接将属性值赋值给新的对象。基础类型的拷贝,其中一个对象修改该值,不会影响另外一个(和浅拷贝一样)。
(2) 对于引用类型,比如数组或者类对象,深拷贝会新建一个对象空间,然后拷贝里面的内容,所以它们指向了不同的内存空间。改变其中一个,不会对另外一个也产生影响。
(3) 对于有多层对象的,每个对象都需要实现 Cloneable 并重写 clone() 方法,进而实现了对象的串行层层拷贝。
(4) 深拷贝相比于浅拷贝速度较慢并且花销较大。

浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址。

(1) 对于基本数据类型的成员对象,因为基础数据类型是值传递的,所以是直接将属性值赋值给新的对象。基础类型的拷贝,其中一个对象修改该值,不会影响另外一个。
(2) 对于引用类型,比如数组或者类对象,因为引用类型是引用传递,所以浅拷贝只是把内存地址赋值给了成员变量,它们指向了同一内存空间。改变其中一个,会对另外一个也产生影响。

  1. 简述map,set,list概念及用途。详述hashmap的存储结构
    见其他博文。
  2. 创建线程方式,run和start的区别。几种线程池设计模式
  • 方式1:继承Java.lang.Thread类,并覆盖run() 方法。
    优势:编写简单;
    劣势:单继承的限制----无法继承其它父类,同时不能实现资源共享。

  • 方式2:实现Java.lang.Runnable接口,并实现run()方法。
    优势:可继承其它类,多线程可共享同一个Thread对象;
    劣势:编程方式稍微复杂,如需访问当前线程,需调用Thread.currentThread()方法 。

  1. mysql的char和varchar的区别;行级锁和表级锁的概念,以及给项目带来的影响
  • char类型的长度是固定的,varchar的长度是可变的。这就表示,存储字符串'abc',使用char(10),表示存储的字符将占10个字节(包括7个空字符)使用varchar2(10),,则表示只占3个字节,10是最大值,当存储的字符小于10时,按照实际的长度存储。
  • char类型的效率比varchar的效率稍高

表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低;
行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高;

  1. 做了两道算法题,难度介于简单和中等之间
    

二面

  1. 介绍自己做的项目,其中的技术点、难点。关于项目的技术点,拓展聊了聊
  2. TCP/IP握手过程,重传相关知识
  3. TCP与UDP区别,TCP可靠性保证,拥塞控制与流量控制
  4. 各种排序算法的方式、时间复杂度
  5. 哈希冲突解决方案,各个优缺点
    1.开放地址方法
      (1)线性探测
       按顺序决定值时,如果某数据的值已经存在,则在原来值的基础上往后加一个单位,直至不发生哈希冲突。 

  (2)再平方探测
   按顺序决定值时,如果某数据的值已经存在,则在原来值的基础上先加1的平方个单位,若仍然存在则减1的平方个单位。随之是2的平方,3的平方等等。直至不发生哈希冲突。

  (3)伪随机探测
   按顺序决定值时,如果某数据已经存在,通过随机函数随机生成一个数,在原来值的基础上加上随机数,直至不发生哈希冲突。

2.拉链法(HashMap的哈希冲突解决方法)
  对于相同的值,使用链表进行连接。使用数组存储每一个链表。
  优点:
  (1)拉链法处理冲突简单,且无堆积现象,即非同义词决不会发生冲突,因此平均查找长度较短;
  (2)由于拉链法中各链表上的结点空间是动态申请的,故它更适合于造表前无法确定表长的情况;
  (3)开放定址法为减少冲突,要求装填因子α较小,故当结点规模较大时会浪费很多空间。而拉链法中可取α≥1,且结点较大时,拉链法中增加的指针域可忽略不计,因此节省空间;
  (4)在用拉链法构造的散列表中,删除结点的操作易于实现。只要简单地删去链表上相应的结点即可。
  
缺点:
  指针占用较大空间时,会造成空间浪费,若空间用于增大散列表规模进而提高开放地址法的效率。

3.建立公共溢出区
  建立公共溢出区存储所有哈希冲突的数据。

4.再哈希法
  对于冲突的哈希值再次进行哈希处理,直至没有哈希冲突。

  1. 大文件里进行去重

    一般可能会想到一次将文本内容读取到内存中,用HashSet对内容去重,但是很不幸这个过程jvm会内存溢出,无奈,只能另想办法,首先将这个大文件中的内容读取出来,对每行String的hashCode取模取正整数,可用取模结果作为文件名,将相同模数的行写入同一个文件,再单独对每个小文件进行去重,最后再合并。

打开大文件,每次只读一行;
对读入的行字符串hash(string) = F(string) mod x, x要保证mod完之后不冲突,将此字符串写入对应号码的文件中;
处理完之后,我们对大小超出一行的文件进行去重;
最后再读取逐个文件,写入到外存中的一个大文件中。
或者,我们可以对每个小文件只读取其第一行(后面的行都是经过计算后重复的),写入结果文件中。

  1. 进程和线程区别

  2. 进程通信方式

1 匿名管道通信

匿名管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。

通过匿名管道实现进程间通信的步骤如下:
父进程创建管道,得到两个⽂件描述符指向管道的两端
父进程fork出子进程,⼦进程也有两个⽂件描述符指向同⼀管道。
父进程关闭fd[0],子进程关闭fd[1],即⽗进程关闭管道读端,⼦进程关闭管道写端(因为管道只支持单向通信)。⽗进程可以往管道⾥写,⼦进程可以从管道⾥读,管道是⽤环形队列实现的,数据从写端流⼊从读端流出,这样就实现了进程间通信。

2 高级管道通信
高级管道(popen):将另一个程序当做一个新的进程在当前程序进程中启动,则它算是当前程序的子进程,这种方式我们成为高级管道方式。

3 有名管道通信
有名管道 (named pipe) : 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。

4 消息队列通信
消息队列( message queue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。

5 信号量通信
信号量( semophore ) : 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。

6 信号
信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。

7 共享内存通信
共享内存( shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。

8 套接字通信
套接字( socket ) : 套接口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信。

  1. 用户态和内核态区别
  2. Java双亲委派机制,打破双亲委派
  3. volatile和synchronized区别
  4. 数据库索引的选取
  5. 设计模式,主要问单例模式、观察者模式、工厂模式及抽象工厂模式。

三面

  1. 介绍自己做的项目,其中的技术点,开发历程;开发过程中遇到的难点,以及排查问题、解决的方式
  2. 常用的数据结构,Arraylist和Linkedlist的遍历效率
  3. 平时技术的学习方式,在看什么书籍,接下来的学习方向

HR面

  1. 任务没有及时完成,如何进行处理?
  2. 生活中互助的经历
  3. 实习及转正地点意愿
0

评论区