java这些基础面试题你都掌握了吗? -- -持续更新,2024年最新阿里巴巴国际站面试题及答案

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip1024b (备注Java)
img

正文

char v1[] = value;

char v2[] = anotherString.value;

int i = 0;

while (n-- != 0) {

if (v1[i] != v2[i])

return false;

i++;

}

return true;

}

}

return false;

}

所以当我们判断两个对象是否相同使用 equals 其实和使用 == 是一样的,因为对象没有重写 equals 方法(当然,我们也可以重写 equals 方法 来实现对比值是对象的属性是否相同来判断对象是否相同),而当我们使用new Stirng() 创建的字符串 通过 equals 方法进行比较时,他们对比的就是字符串的值,如果值相同就返回 true,不会对比内存地址,因为 String 重写 Object类中的 equals 方法。

重载和重写的区别?

重载:发生在同一个类中,方法名必须相同,实质表现就是多个具有不同的参数个数或者类型的同名函数(返回值类型可随意,不能以返回类型作为重载函数的区分标准),返回值类型、访问修饰符可以不同,发生在编译时。

重写: 发生在父子类中,方法名、参数列表必须相同,是父类与子类之间的多态性,实质是对父类的函数进行重新定义。返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类;如果父类方法访问修饰符为 private 则子类就不能重写该方法。

修饰符public、private、protected,以及不写(默认)时的区别?

private:私有,只有当前类有操作权。

default:当成员变量没有访问修饰符时默认为default,对于同一个包下,它相当于 public(公开),不同包下相当于 private (私有)。

protected:受保护,对同一个包中的类或者子类相当于 public(公开),对于不同包下且没有父子关系的相当于 private(私有)。

public:公开,对所有类都是公开的。

| 修饰符 | 当前类 | 同包 | 子类 | 其他包 |

| :-: | :-: | :-: | :-: | :-: |

| private | ✔ | ✖ | ✖ | ✖ |

| default | ✔ | ✔ | ✖ | ✖ |

| protected | ✔ | ✔ | ✔ | ✖ |

| public | ✔ | ✔ | ✔ | ✔ |

注意

可以修饰外部类的修饰符只有 public 和 default。

public 修饰外部类时,在同一包内,可以访问,无需导包;同一包外可以访问,需要导包。

default 修饰外部类时,在同一包内,可以访问,无需导包;同一包外,无法访问。

构造器Constructor是否可被override?

构造器Constructor不能被继承,因此不能重写Override,但可以被重载Overload。

Constructor不能被继承,所以Constructor也就不能被override。每一个类必须有自己的构造函数,负责构造自己这部分的构造。子类不会覆盖父类的构造函数,相反必须负责在一开始调用父类的构造函数。

String、StringBuffer、StringBuilder的区别

String:字符串常量,字符串长度不可变。Java 中 String 是 immutable(不可变)的。

StringBuffer:字符串变量(Synchronized,即线程安全),线程安全的可变字符序列,可以改变字符序列的长度和内容,如果需要频繁的对字符串进行拼接,StringBuffer 相对于 “+” 的重载效率会高得多,如果需要将 StringBuffer 转 String,只需要调用 StringBuffer 的toString() 方法。

StringBuilder:字符串变量(非线程安全,JDK1.5 引入)。在内部,StringBuilder 对象被当作是一个包含字符序列的变长数组。此类提供一个与 StringBuffer 兼容的 API,但不保证同步。该类被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。

三者区别

String 和 StringBuffer区别主要在性能方面,由于 String 是不可变对象,所以通过 “+” 来拼接的字符串都会生成一个新的 String 类,然后将指针指向新的 String 对象。这并不能说明 “+” 的性能就弱于StringBuffer,我们知道,在 JDK 1.5 之后引入了 StringBuilder, “+” 是通过 StringBuilder 实现,对于一般的字符串拼接,“+” 和 StringBuilder 的性能其实没有什么差别,因为 JVM 会对 String 的 “+” 做优化,既然这样的话,是否就没有必要使用 StringBuffer 和 StringBuilder 了呢?不是的,如果你在某个循环中对字符串进行拼接, “+” 会在循环内构造 StringBuilder ,也就是说,循环了多少次,StringBuilder 就被创建了多少次。而 StringBuffer 和 StringBuilder 在循环拼接的时候只需要创建一个对象即可,性能非常的明显。

对于 StringBuffer 和 StringBuilder ,他们都是 AbstractStringBuilder 抽象类的子类,都有着相同的实现,主要区别在于 StringBuffer 线程安全,而 StringBuilder 非线程安全,这也必然会导致 StringBuilder 的性能高于 StringBuffer 。

最后 String、StringBuffer、StringBuilder 的性能由高到低:StringBuilder > StringBuffer > String。

final, finally, finalize的区别

final:用于声明属性,方法和类,分别表示属性不可交变,方法不可覆盖,类不可继承。请参考前文中的final关键字的介绍。

finally:Java 中的 Finally 关键一般与try一起使用,在程序进入try块之后,无论程序是因为异常而终止或其它方式返回终止的,finally块的内容一定会被执行 。

finalize:是Object类的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法,供垃圾收集时的其他资源回收,例如关闭文件等。

通常来说,这三个关键字之前没有什么必然的联系,只是名字有点像,给人感觉他们很相似。

字节流与字符流的区别

字节流就是普通的二进制流,读出来的是bit。

字符流就是在字节流的基础按照字符编码处理,处理的是char。

什么是java序列化,如何实现java序列化?或者请解释Serializable接口的作用

Java 提供了一种对象序列化的机制,该机制中,一个对象可以被表示为一个字节序列,该字节序列包括该对象的数据、有关对象的类型的信息和存储在对象中数据的类型。

将序列化对象写入文件之后,可以从文件中读取出来,并且对它进行反序列化,也就是说,对象的类型信息、对象的数据,还有对象中的数据类型可以用来在内存中新建对象。

简单来说:序列化就是将java对象转成字节流的过程,反序列化则是将字节流转成java对象的过程。

java实现序列化

  • 原生序列化:java对象实现 Serializable 接口,通过原生流( InputStream/outputStream) 实现。

  • JSON序列化:spring提供了一种默认的json序列化工具包:jackson ,通过ObjectMapper类来实现对象转byte[] 数组或者 json 串转对象的过程。

  • FastJson:阿里巴巴旗下一个员工因为没有好用的序列化工具开发的,虽然目前该工具 bug 满天飞,但是使用 FastJson 的公司还是不少。

  • ProtoBuff序列化: protobuf(Google Protocol Buffers)是Google提供一个具有高效的协议数据交换格式工具库(类似Json),但相比于Json,Protobuf有更高的转化效率,时间效率和空间效率都是JSON的3-5倍。后面将会有简单的demo对于这两种格式的数据转化效率的对比。

abstract class和interface有什么区别?

abstract class:含有 abstract 修饰符的class即为抽象类,abstract 类不能创建实例对象,含有 abstract 修饰的方法的类必须是 abstract 类,但是 abstract 类并不要求其所有方法都是抽象方法。抽象类中定义的抽象方法必须在继承的子类中实现,所以,不能有抽象构造方法或者抽象静态方法。如果子类没有实现父类(抽象类)的所有抽象方法,那么该子类也必须定义为抽象类。

interface:可以说成是抽象类的一种特例,接口中的所有方法都必须是抽象的。接口中的方法定义默认为public abstract类型,接口中的成员变量类型默认为public static final。

| | abstract class(抽象类) | interface(接口) |

| — | — | — |

| 类 | 继承关系,只能继承一个,但是可以实现多个接口 | 可以实现多个接口 |

| 数据 | 可以自定义 | 只能是静态的 |

| 方法 | 私有或者受保护的,非抽象方法,但是抽象方法必须实现 | 只能是公开(public)方法 |

| 实例化 | 不能 | 不能 |

| 变量 | 可以定义私有变量、默认,可以在子类中重新定义或者重新赋值 | 只能是公开静态抽象变量(public static abstract ),实现类不能修改 |

| 设计 | is-a | like-a |

| 实现方式 | 继承(extends) | 实现(implements) |

a=a+b与a+=b有什么区别吗?


+= 操作符会进行隐式自动类型转换,此处 a+=b 隐式的将加操作的结果类型强制转换为持有结果的类型,而 a=a+b 则不会自动进行类型转换。

从图片中我们可以看出: a+b 的结果应该是 int类型,第一组操作 a += b;之所以没有报错是因为编译器给我自动做了类型转换(将int 类型强转为 short 类型),而 a=a+b 没有做类型转换,所以编译无法通过。

一个类能拥有多个main方法吗?

可以,但只能拥有一个这样的main方法

public static void main(String[] args) {

}

否者程编译将无法通过,警告你的main方法已存在。

java支持什么类型的参数传递?

直接说结论吧,java只有值传递,没有引用传递,解释的话有点长,感兴趣的可以参考一下我之前写的文章:天真,居然还有人认为java的参数传递方式是引用传递

这里说一下什么是引用传递、什么是值传递

什么是引用传递?

在C++中,函数参数的传递方式有引用传递。所谓引用传递是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。

什么是值传递?

值传递是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。

集合

Arraylist 和 LinkedList的区别?

arrayList

ArrayList 是集合框架的一部分,存在于 java.util 包中。它为我们提供了 Java 中的动态数组。

虽然,它可能比标准数组慢,但在需要对数组进行大量操作的程序中很有帮助。此类位于java.util包中。

特性

  • ArrayList 容器大小可以默认(10)也可以指定,如果数据增多或者减少,ArrayList 容器也会触发扩容或者缩减操作(ArrayList 的扩容通过数据拷贝的方式实现,浅拷贝,所以我们在使用 ArrayList 的时候最好指明容器大小,如果触发扩容次数较多,会严重影响性能)。

  • ArrayList 支持随机访问(下标访问),且效率非常高,时间复杂度:O(1),但是按需查找(查找值),效率和链表一样,时间复杂度为:O(n)。

  • ArrayList 只能被用于包装类型,基本类型不可用。

  • ArrayList 线程不安全的,也就是不同步,可以使用 Vector代替。

ArrayList 工作流程:ArrayList通过构造函数创建了一个容器(默认容器大小 10),调用 add() 方法可以往 ArrayList 中的 elementData 数组中添加数据,

当添加的数据到达10个的时候,就会触发扩容操作,扩容一般是将容器扩大到原来的1.5倍(自己指定扩容的除外,手动扩容会将容器大小变成你指定的大小),

将老的数组拷贝到新数组中,然后废弃老数组。删除数据时,会将数组中指定的元素置空,然后做判断,如果删除的元素不是在最后一个,那么执行一遍数据拷贝,新数组的大小 -1。

image-20210812221105161

LinkedList

链表是一种线性数据结构,其中元素不存储在连续的内存位置。链表中的元素使用指针链接,链表由节点组成,每个节点中至少包含本身数据和指向下一个节点的指针。

单向链表

image-20210812221105161

双向链表

双链表是链表的一种,由节点组成,每个数据结点中都有两个指针,分别指向直接后继和直接前驱。

image-20210812221105161

LinkedList 特点

  • LinkedList是双向链表实现的List(双向链表指的是每个节点都会记录前一个节点和下一个节点的指针)。

  • LinkedList 非线程安全。

  • LinkedList元素允许为null,允许重复元素

  • 插入删除效率高,时间复杂度:O(1),查找效率一般,时间复杂度:O(n)。

  • 不存在扩容的说法,添加数据直接在尾节点后添加,删除直接将上一个节点的下一个节点指针指向删除节点下一个节点的地址(有点绕。可以仔细品一下)。

ArrayList 和 LinkedList的区别

  • 数据结构不同,ArrayList 基于数组;LinkedList 基于链表。

  • 效率不同:随机访问 ArrayList 快增加删除 LinkedList 快

  • 限制不同:ArrayList 由于基于数组,所以需要动态扩容,LinkedList 基于链表,不存在扩容的说法。

  • 占用空间不同:ArrayList 需要申请一块连续的内存地址(数组的原因),LinkedList 对内存连续性没有要求(链表的原因)。

HashMap、HashSet、HashTable的区别?

HashMap

HashMap 是一个散列表,它存储的内容是键值对(key-value)映射,数据结构主要是:数组+链表的方式存储,jdk1.8 之后引入了红黑树用来优化链表过长问题。

HashMap 实现了 Map 接口,根据键的 HashCode 值存储数据,具有很快的访问速度,最多允许一条记录的键为 null,不支持线程同步。

HashMap 是无序的,即不会记录插入的顺序。

HashMap 继承于AbstractMap,实现了 Map、Cloneable、java.io.Serializable 接口。

HashSet

HashSet 实现了 Set 接口,从他的构造函数中可以得知他也是一个 HashMap,所以他也是无序的,但是它允许空元素,虽然多个null不会报错,但是由于set的重复判断的条件,HashSet 集合中只会出现一条 null 数据,其他null会被视为重复数据,同时,由于 HashSet 依赖HashMap实现,所以它也是线程不安全的。我们再来看看 HashSet 的几个重要特性。

  • 实现了集合接口

  • 由于 HashSet 实现了 Set 接口,所以 HashSet 不允许出现重复数据

  • HashSet 插入的对象无法保证以相同的顺序插入,插入位置由 HashCode 决定

  • HashSet 允许出现 null 值

  • HashSet 还实现了SerializableCloneable接口。

HashTable

HashTable 与HashMap 类似,它们存储的内容都是键值对(key-value)映射,不过 HashTable 不允许将空值作为key或者value,从哈希表中查询或者存储对象时,用作键的对象必须实现 hashCode() 方法和 equails()方法。由于HasTable 的添加操作在方法上加上了 synchronized 同步锁,所以他是线程安全的。HashTable 特点如下

与HashMap相似,但它线程安全。

Hashtable 将键/值对存储在哈希表中。

HashTable 类的初始默认容器是11(HashMap 初始容器为 16 ),loadFactor(装载因子) 0.75。

Hashtable 提供非快速失败的枚举,而HashMap没有。

三者区别

线程安全性:HashTable 由于加了 synchronized 同步锁,所以他是线程安全的,HashMap与 HashSet 则是非线程安全的。

数据重复:HashSet 由于实现了 Set接口,所以它不允许出现重复的值,HashMap中不允许出现重复的键,但是可以出现重复的值,HashTable也是如此,并且HashTable还不允许将空值作为key或者value

HashMap是线程安全的吗?有什么替代集合吗?

HashMap不是线程安全的

替代品

HashTable:通过在方法上添加 synchronized 同步锁来保证线程安全,效率偏低。

Collections.synchronizedMap(Map):通过对象排斥锁 mutex 保证线程安全,效率偏低。

ConcurrentHashMap:CAS +分段锁的机制保证线程安全,相比前面两种,它的效率会好很多。

HashMap如何解决哈希冲突问题?

hash算法

哈希算法是将任意长度的二进制值映射为较短的固定长度的二进制值,这个小的二进制值称为哈希值(java 通过 hash 将会得到一个整数 )。

哈希值是一段数据唯一且极其紧凑的数值表示形式。如果散列一段明文而且哪怕只更改该段落的一个字母,随后的哈希都将产生不同的值。要找到散列为同一个值的两个不同的输入,在计算上是不可能的,所以数据的哈希值可以检验数据的完整性。一般用于快速查找和加密算法。

hash碰撞

Hash算法并不完美,有可能两个不同的原始值在经过哈希运算后得到同样的结果, 这样就是哈希碰撞

我们希望的 hash:

  • 散列函数计算得到的散列值是一个非负整数。

  • 如果 key1 = key2,那 hash(key1) == hash(key2)。

  • 如果 key1 ≠ key2,那 hash(key1) ≠ hash(key2)。

前面两点没啥说的,key 相同,hash计算出来的值必须一致,但是至于最后一点,想要找到一个不同 key 对应散列值(hash值)也都不一样的散列(hash)函数,那是几乎不可能的。即便像业界著名的MD5、SHA、CRC等哈希算法,也无法完全避免这种散列冲突。而且,因为数组的存储空间有限,也会加大散列冲突的概率。

解决方案

开放寻址法

如果出现了 hash 冲突,那我们就重新探测一个空闲位置,然后再插入,二如何重新探测新的插入位置呢?线性探测就能解决这个问题。线性探测指的是:如果某个 key 经过 散列函数得到的下标已经被占用了,这个时候就从当前位置出发,后移一位,判断位置是否空闲,空闲则插入,否则继续重复探测。

最后

各位读者,由于本篇幅度过长,为了避免影响阅读体验,下面我就大概概括了整理了

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
我们就重新探测一个空闲位置,然后再插入,二如何重新探测新的插入位置呢?线性探测就能解决这个问题。线性探测指的是:如果某个 key 经过 散列函数得到的下标已经被占用了,这个时候就从当前位置出发,后移一位,判断位置是否空闲,空闲则插入,否则继续重复探测。

最后

各位读者,由于本篇幅度过长,为了避免影响阅读体验,下面我就大概概括了整理了

[外链图片转存中…(img-CP75YxgT-1713259075049)]

[外链图片转存中…(img-ePyPCGEg-1713259075049)]

[外链图片转存中…(img-F7r0XmHS-1713259075050)]

[外链图片转存中…(img-gGoE16ng-1713259075050)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-xDKgW5O0-1713259075051)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值