目录
1.1一个".java"源文件中是否可以包括多个类?有什么限制
[1.2Java 的优势](#1.2Java 的优势)
[1.4Java 中是否存在内存溢出、内存泄漏?如何解决?举例说明](#1.4Java 中是否存在内存溢出、内存泄漏?如何解决?举例说明)
[1. 内存溢出(OutOfMemoryError)](#1. 内存溢出(OutOfMemoryError))
[2. 内存泄漏(Memory Leak)](#2. 内存泄漏(Memory Leak))
[1.5. 如何看待Java是一门半编译半解释型的语言](#1.5. 如何看待Java是一门半编译半解释型的语言)
[2.1 高效的方式计算2 * 8的值](#2.1 高效的方式计算2 * 8的值)
[2.2 &和&&的区别?](#2.2 &和&&的区别?)
[2.3 Java中的基本类型有哪些?String 是最基本的数据类型吗?](#2.3 Java中的基本类型有哪些?String 是最基本的数据类型吗?)
[2.4 Java开发中计算金额时使用什么数据类型?](#2.4 Java开发中计算金额时使用什么数据类型?)
[2.5 char型变量中能不能存储一个中文汉字,为什么?(*通快递)](#2.5 char型变量中能不能存储一个中文汉字,为什么?(*通快递))
[2.6 代码分析](#2.6 代码分析)
[2.7 int i=0; i=i++执行这两句化后变量 i 的值为](#2.7 int i=0; i=i++执行这两句化后变量 i 的值为)
[2.8 如何将两个变量的值互换](#2.8 如何将两个变量的值互换)
[2.9 boolean 占几个字节](#2.9 boolean 占几个字节)
[2.10 为什么Java中0.1 + 0.2结果不是0.3?](#2.10 为什么Java中0.1 + 0.2结果不是0.3?)
[3.1 break和continue的作用](#3.1 break和continue的作用)
[3.2 if分支语句和switch分支语句的异同之处](#3.2 if分支语句和switch分支语句的异同之处)
[3.3 switch语句中忘写break会发生什么](#3.3 switch语句中忘写break会发生什么)
[3.4 Java支持哪些类型循环](#3.4 Java支持哪些类型循环)
[3.5 while和do while循环的区别](#3.5 while和do while循环的区别)
[4.1 数组有没有length()这个方法? String有没有length()这个方法?](#4.1 数组有没有length()这个方法? String有没有length()这个方法?)
[4.2 有数组int[] arr,用Java代码将数组元素顺序颠倒](#4.2 有数组int[] arr,用Java代码将数组元素顺序颠倒)
[4.3 为什么数组要从0开始编号,而不是1](#4.3 为什么数组要从0开始编号,而不是1)
[4.4 数组有什么排序的方式,手写一下](#4.4 数组有什么排序的方式,手写一下)
[4.5 常见排序算法,说下快排过程,时间复杂度?](#4.5 常见排序算法,说下快排过程,时间复杂度?)
[4.6 二分算法实现数组的查找](#4.6 二分算法实现数组的查找)
[4.7 怎么求数组的最大子序列和](#4.7 怎么求数组的最大子序列和)
[4.8 Arrays 类的排序方法是什么?如何实现排序的?](#4.8 Arrays 类的排序方法是什么?如何实现排序的?)
1、Java语言概述
1.1一个".java"源文件中是否可以包括多个类?有什么限制
是!
一个源文件中可以声明多个类,但是最多只能有一个类使用public进行声明。 且要求声明为public的类的类名与源文件名相同。
1.2Java 的优势
-
跨平台型
-
安全性高
-
简单性
-
高性能
-
面向对象性
-
健壮性
1.3常用的几个命令行操作都有哪些?(至少4个)
-
编译Java文件 :
javac FileName.java
将Java源文件编译为字节码文件(
.class
),例如javac HelloWorld.java
。 -
运行Java程序 :
java ClassName
运行编译后的字节码文件。例如,编译生成的
HelloWorld.class
可以通过java HelloWorld
来执行。 -
查看Java版本 :
java -version
用于查看当前安装的Java版本,方便检查兼容性等问题。
-
打包为JAR文件 :
jar cf jarFileName.jar *.class
将多个
.class
文件打包成一个JAR文件。例如,将所有的.class
文件打包为MyApp.jar
,可使用jar cf MyApp.jar *.class
。
1.4Java 中是否存在内存溢出、内存泄漏?如何解决?举例说明
1.4.1 内存溢出(OutOfMemoryError)
内存溢出通常发生在JVM无法分配足够的内存给应用程序时,表现为OutOfMemoryError
。它主要分为以下几种类型:
-
堆内存溢出:在堆区存放了大量数据,通常是因为对象过多或使用了大对象。
- 示例:在代码中创建过多的对象或使用了大量的字符串常量,导致内存不足。
- 解决方法 :增大JVM的堆内存分配,例如通过
-Xmx
参数来设置堆内存大小;优化代码,避免不必要的大量对象创建。
-
栈内存溢出:由于方法递归层级太深或无限递归,导致栈区内存耗尽。
- 示例:函数递归层数过多或无限递归,导致栈空间耗尽。
- 解决方法 :优化递归逻辑,避免无限递归;增加JVM的栈大小,如
-Xss
参数。
-
永久代溢出(元空间):在Java 8之前,类的元数据信息存放在永久代(PermGen),而Java 8之后存放在元空间(Metaspace)。
- 示例:过多类的加载,或频繁生成和加载动态类。
- 解决方法 :通过
-XX:MaxMetaspaceSize
增加元空间大小,或减少动态类生成和加载。
1.4.2 内存泄漏(Memory Leak)
内存泄漏是指程序中存在的无用对象无法被GC(垃圾回收器)清除,导致内存逐渐被占满。
- 示例 :在集合(如
List
或Map
)中不断添加对象但从未清除,即便这些对象已经不再需要。- 解决方法 :避免在集合中无限添加对象,可以手动移除不再使用的对象;另外,使用弱引用(
WeakReference
)来保存不重要的数据对象,这样当对象不再需要时可以被GC回收。
- 解决方法 :避免在集合中无限添加对象,可以手动移除不再使用的对象;另外,使用弱引用(
实际代码示例
java
import java.util.ArrayList;
import java.util.List;
public class MemoryLeakExample {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
// 模拟内存泄漏
while (true) {
list.add("Memory Leak Example"); // 不断向集合中添加数据而不清除
}
}
}
此示例代码会导致内存泄漏,因为程序一直在向list
中添加字符串而不清理内存,最终会导致OutOfMemoryError
。
1.5. 如何看待Java是一门半编译半解释型的语言
2、变量与运算符
2.1 高效的方式计算2 * 8的值
使用 <<
2.2 &和&&的区别?
&
- 按位与 和 逻辑与
-
按位与 :如果
&
用于两个整数或二进制数字之间,它会执行逐位比较。仅当两个位都为1
时,结果为1
。- 示例 :
5 & 3
(5
的二进制是0101
,3
是0011
),结果为0001
,即1。
- 示例 :
-
逻辑与 :如果
&
用于两个布尔表达式之间,它作为逻辑运算符。它会在两边表达式都为true
时返回true
。- 示例 :
true & false
返回false
。
注意 :使用
&
进行逻辑运算时,不会短路 。即便左侧表达式为false
,右侧表达式也会被计算。 - 示例 :
&&
- 逻辑与(带短路特性)
-
&&
只能用于布尔表达式的逻辑运算,表示逻辑与操作,且具有短路特性。 -
短路特性 :如果第一个表达式为
false
,则整个表达式必定为false
,此时右侧表达式不会被计算。- 示例 :
(a != 0) && (b / a > 1)
,若a
为0
,则(a != 0)
为false
,右侧的除法操作不会执行,从而避免除零异常。
- 示例 :
java
int a = 0;
int b = 10;
// 使用 &&,具有短路特性
if (a != 0 && b / a > 1) {
System.out.println("不会抛出异常");
}
// 使用 &,没有短路特性
if (a != 0 & b / a > 1) {
System.out.println("此处可能会抛出除零异常");
}
2.3 Java中的基本类型有哪些?String 是最基本的数据类型吗?
- 整数类型
- byte :占用1字节,取值范围为
-128
到127
。 - short :占用2字节,取值范围为
-32,768
到32,767
。 - int :占用4字节,取值范围为
-2^31
到2^31 - 1
。 - long :占用8字节,取值范围为
-2^63
到2^63 - 1
。
- 浮点数类型
- float:占用4字节,单精度浮点数。
- double:占用8字节,双精度浮点数。
- 字符类型
- char :占用2字节,存储单个字符(Unicode编码),取值范围为
0
到65,535
。
- 布尔类型
- boolean :占用1位,取值为
true
或false
。
String 不是基本数据类型
String 在Java中不是基本数据类型 ,而是一个引用数据类型(类)。尽管String的使用方式类似于基本数据类型,但它实际上是一个对象,可以调用方法,例如length()
、substring()
等。String在Java中是不可变的,每次对String的修改都会创建一个新的对象。
2.4 Java开发中计算金额时使用什么数据类型?
不能使用float或double,因为精度不高。
使用BigDecimal类替换,可以实现任意精度的数据的运算。
2.5 char型变量中能不能存储一个中文汉字,为什么?(*通快递)
可以的。char c1 = '中';
char c2 = 'a'。
因为char使用的是unicode字符集,包含了世界范围的所有的字符。
2.6 代码分析
java
short s1=1;
s1=s1+1; //有什么错? =右边是int类型。需要强转
java
short s1=1;
s1+=1; //有什么错? 没错
2.7 int i=0; i=i++执行这两句化后变量 i 的值为
0。
2.8 如何将两个变量的值互换
java
String s1 = "abc";
String s2 = "123";
String temp = s1;
s1 = s2;
s2 = temp;
2.9 boolean 占几个字节
java
编译时不谈占几个字节。
但是JVM在给boolean类型分配内存空间时,boolean类型的变量占据一个槽位(slot,等于4个字节)。
细节:true:1 false:0
>拓展:在内存中,byte\short\char\boolean\int\float : 占用1个slot
double\long :占用2个slot
2.10 为什么Java中0.1 + 0.2结果不是0.3?
在代码中测试0.1 + 0.2,你会惊讶的发现,结果不是0.3,而是0.3000......4。这是为什么?
几乎所有现代的编程语言都会遇到上述问题,包括 JavaScript、Ruby、Python、Swift 和 Go 等。引发这个问题的原因是,它们都采用了IEEE 754标准
。
IEEE是指"电气与电子工程师协会",其在1985年发布了一个IEEE 754计算标准,根据这个标准,小数的二进制表达能够有最大的精度上限提升。但无论如何,物理边界是突破不了的,它仍然
不能实现"每一个十进制小数,都对应一个二进制小数"
。正因如此,产生了0.1 + 0.2不等于0.3的问题。
具体的:
整数变为二进制,能够做到"每个十进制整数都有对应的二进制数",比如数字3,二进制就是11;再比如,数字43就是二进制101011,这个毫无争议。
对于小数,并不能做到"每个小数都有对应的二进制数字"。举例来说,二进制小数0.0001表示十进制数0.0625 (至于它是如何计算的,不用深究);二进制小数0.0010表示十进制数0.125;二进制小数0.0011表示十进制数0.1875。看,对于四位的二进制小数,二进制小数虽然是连贯的,但是十进制小数却不是连贯的。比如,你无法用四位二进制小数的形式表示0.125 ~ 0.1875之间的十进制小数。
所以在编程中,遇见小数判断相等情况,比如开发银行、交易等系统,可以采用四舍五入
或者"同乘同除
"等方式进行验证,避免上述问题。
3.流程控制语句
3.1 break和continue的作用
break
:终止整个循环。continue
:跳过当前循环的剩余部分,直接进行下一次循环。
3.2 if分支语句和switch分支语句的异同之处
-
if-else语句优势
-
if语句的条件是一个布尔类型值,if条件表达式为true则进入分支,可以用于范围的判断,也可以用于等值的判断,
使用范围更广
。 -
switch语句的条件是一个常量值(byte,short,int,char,枚举,String),只能判断某个变量或表达式的结果是否等于某个常量值,
使用场景较狭窄
。
-
-
switch语句优势
-
当条件是判断某个变量或表达式是否等于某个固定的常量值时,使用if和switch都可以,习惯上使用switch更多。因为
效率稍高
。当条件是区间范围的判断时,只能使用if语句。 -
使用switch可以利用
穿透性
,同时执行多个分支,而if...else没有穿透性。
-
3.3 switch语句中忘写break会发生什么
case穿透
3.4 Java支持哪些类型循环
-
for;while;do-while
-
增强for (或foreach)
3.5 while和do while循环的区别
- do-while至少会执行一次。
4.数组
4.1 数组有没有length()这个方法? String有没有length()这个方法?
数组没有length(),是length属性。
String有length()
4.2 有数组int[] arr,用Java代码将数组元素顺序颠倒
java
public class ReverseArray {
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5};
reverseArray(arr);
// 输出颠倒后的数组
for (int i : arr) {
System.out.print(i + " ");
}
}
public static void reverseArray(int[] arr) {
int left = 0;
int right = arr.length - 1;
while (left < right) {
// 交换 arr[left] 和 arr[right]
int temp = arr[left];
arr[left] = arr[right];
arr[right] = temp;
// 移动指针
left++;
right--;
}
}
}
4.3 为什么数组要从0开始编号,而不是1
数组的索引,表示了数组元素距离首地址的偏离量。因为第1个元素的地址与首地址相同,所以偏移量就是0。所以从0开始。
4.4 数组有什么排序的方式,手写一下
冒泡。
快排。
4.5 常见排序算法,说下快排过程,时间复杂度?
快速排序的步骤
-
选取基准元素(Pivot):从待排序数组中选择一个基准元素。基准元素的选择可以是第一个元素、最后一个元素、随机元素或中间元素。
-
分区(Partition):将数组中的元素重新排序,使得基准元素左边的元素都小于它,右边的元素都大于它。此时,基准元素在其正确的排序位置上。
-
递归排序:递归地对基准元素左边和右边的子数组进行快速排序,直到子数组的长度为 1(已排序)。
快速排序代码示例
java
public class QuickSort {
public static void quickSort(int[] arr, int low, int high) {
if (low < high) {
int pivotIndex = partition(arr, low, high); // 获取基准元素的正确位置
quickSort(arr, low, pivotIndex - 1); // 对左子数组排序
quickSort(arr, pivotIndex + 1, high); // 对右子数组排序
}
}
private static int partition(int[] arr, int low, int high) {
int pivot = arr[high]; // 选择最后一个元素作为基准
int i = low - 1; // i 是比基准小的元素的最后位置
for (int j = low; j < high; j++) {
if (arr[j] < pivot) { // 找到比基准小的元素
i++;
// 交换 arr[i] 和 arr[j]
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
// 将基准元素放到正确位置
int temp = arr[i + 1];
arr[i + 1] = arr[high];
arr[high] = temp;
return i + 1; // 返回基准元素的索引
}
public static void main(String[] args) {
int[] arr = {3, 6, 8, 10, 1, 2, 1};
quickSort(arr, 0, arr.length - 1);
for (int i : arr) {
System.out.print(i + " ");
}
}
}
时间复杂度
- 平均时间复杂度 :
O(n log n)
,其中n
是数组的长度。 - 最坏时间复杂度 :
O(n^2)
,最坏情况发生在每次分区时选择的基准总是数组的最大或最小值,这会导致分区极不平衡。 - 最好时间复杂度 :
O(n log n)
,当每次分区都能将数组平分时。
4.6 二分算法实现数组的查找
java
public class BinarySearch {
public static int binarySearch(int[] arr, int target) {
int left = 0;
int right = arr.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2; // 避免直接相加可能产生的溢出
if (arr[mid] == target) {
return mid; // 找到目标,返回索引
} else if (arr[mid] < target) {
left = mid + 1; // 目标在右半部分
} else {
right = mid - 1; // 目标在左半部分
}
}
return -1; // 若未找到目标,返回 -1
}
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; // 必须为已排序数组
int target = 7;
int result = binarySearch(arr, target);
if (result != -1) {
System.out.println("目标元素在索引位置: " + result);
} else {
System.out.println("目标元素未找到");
}
}
}
代码说明
left
指针指向数组的起始位置,right
指针指向数组的末尾位置。- 计算中间位置
mid
。 - 如果
arr[mid]
等于target
,返回mid
索引。 - 如果
arr[mid]
小于target
,则将left
移动到mid + 1
,表示只需在右半部分查找。 - 如果
arr[mid]
大于target
,则将right
移动到mid - 1
,表示只需在左半部分查找。 - 如果
left
超过right
,说明目标元素不存在于数组中,返回 -1。
时间复杂度
- 时间复杂度 :
O(log n)
,每次查找范围减半。 - 空间复杂度 :
O(1)
,只需常量空间。
4.7 怎么求数组的最大子序列和
java
/*
* 输入一个整形数组,数组里有正数也有负数。数组中连续的一个或多个整数组成一个子数组,每个子数组都有一个和。
* 求所有子数组的和的最大值。要求时间复杂度为O(n)。
例如:输入的数组为1, -2, 3, 10, -4, 7, 2, -5,和最大的子数组为3, 10, -4, 7, 2,
因此输出为该子数组的和18。
*/
public class ArrDemo {
public static void main(String[] args) {
int[] arr = new int[]{1, -2, 3, 10, -4, 7, 2, -5};
int i = getGreatestSum(arr);
System.out.println(i);
}
public static int getGreatestSum(int[] arr){
int greatestSum = 0;
if(arr == null || arr.length == 0){
return 0;
}
int temp = greatestSum;
for(int i = 0;i < arr.length;i++){
temp += arr[i];
if(temp < 0){
temp = 0;
}
if(temp > greatestSum){
greatestSum = temp;
}
}
if(greatestSum == 0){
greatestSum = arr[0];
for(int i = 1;i < arr.length;i++){
if(greatestSum < arr[i]){
greatestSum = arr[i];
}
}
}
return greatestSum;
}
}
4.8 Arrays 类的排序方法是什么?如何实现排序的?
Arrays.sort()
的常见用法
-
基本数据类型排序:
Arrays.sort(int[] a)
Arrays.sort(double[] a)
Arrays.sort(char[] a)
对于基本数据类型数组,
Arrays.sort()
使用了双轴快速排序算法(Dual-Pivot Quicksort)。它是一种对传统快速排序进行优化的算法,适用于大多数情况。该算法在 Java 7 中引入,能够有效地减少比较次数和内存访问,具有较好的性能。 -
对象数组排序:
Arrays.sort(T[] a)
:用于Comparable
对象数组的排序。Arrays.sort(T[] a, Comparator<? super T> c)
:用于指定Comparator
比较器的对象数组排序。
对于对象数组,
Arrays.sort()
使用了Timsort。Timsort 是一种混合排序算法,结合了归并排序和插入排序,适合处理部分已排序的数据。在 Java 8 中,Timsort 被广泛应用于对象数组的排序,因为它具有稳定性(相同元素的顺序不会改变),并且在大多数情况下效率较高。
实现原理
-
双轴快速排序(Dual-Pivot Quicksort):
- 选择两个基准点,将数组划分为三个部分:小于第一个基准点、介于两个基准点之间、大于第二个基准点的部分。
- 递归地对这三个部分进行快速排序。
- 双轴快速排序在大多数情况下比单轴快速排序更高效,但仍然是不稳定排序。
-
Timsort:
- 将数组分成若干小块(run)并分别排序。
- 在较小块的元素已排序的情况下,使用插入排序。
- 然后将小块归并排序,结合了归并排序的效率和插入排序的稳定性。
- Timsort 是一种稳定的排序算法,在处理已部分排序的数据时效率更高。
代码示例
java
import java.util.Arrays;
import java.util.Comparator;
public class ArraysSortExample {
public static void main(String[] args) {
// 基本数据类型排序
int[] intArray = {3, 1, 4, 1, 5, 9};
Arrays.sort(intArray);
System.out.println("排序后的int数组: " + Arrays.toString(intArray));
// 对象数组排序(Comparable)
String[] strArray = {"banana", "apple", "cherry"};
Arrays.sort(strArray);
System.out.println("排序后的String数组: " + Arrays.toString(strArray));
// 对象数组排序(Comparator)
Arrays.sort(strArray, Comparator.reverseOrder());
System.out.println("按逆序排序的String数组: " + Arrays.toString(strArray));
}
}