JavaRush /Java 博客 /Random-ZH /Java 开发人员访谈问答分析。第13部分

Java 开发人员访谈问答分析。第13部分

已在 Random-ZH 群组中发布
你好!
朝着目标的运动首先是运动。
因此,仅仅认为自己想要实现某些目标是不够的。你需要做一些事情——即使是最小的步骤——但是每天都这样做,只有这样你才能实现最终的目标。由于您来这里是为了成为 Java 开发人员,因此您每天至少需要采取最小的步骤来加深您对 Java 的了解。对于今天的 Java 步骤,我建议您熟悉一下开发人员最热门面试问题分析的新部分。 Java 开发人员访谈问答分析。 第 13 - 1 部分今天我们将为初级专家解答问题的实践部分。面试中的实际任务并不罕见。重要的是不要在这种情况下迷失方向,尽量保持冷静并提供最佳解决方案,甚至是几个。我还建议在解决问题时不要保持沉默,而是评论你的思路并写下解决方案,或者在写完后用文字解释你做了什么以及为什么。这比默默的决定更能让面试官喜欢你。那么让我们开始吧!

111.线程间如何交换数据?

Java 开发人员访谈问答分析。 第 13 - 2 部分要在线程之间交换数据,可以使用许多不同的方法和手段:例如,使用原子变量、同步集合和信号量。但为了解决这个问题,我将举一个Exchanger的例子。 Exchanger是发包中的同步类,它通过创建公共同步点来促进一对线程之间的元素交换。它的使用简化了两个线程之间的数据交换。它的工作方式非常简单:它等待两个单独的线程调用其Exchange()方法。它们之间创建了类似交换点的东西:第一个线程放置其对象并接收另一个线程的对象作为回报,而后者又接收第一个线程的对象并放置自己的对象。也就是说,第一个线程使用Exchange()方法并处于空闲状态,直到另一个线程调用同一对象上的Exchange()方法并在它们之间交换数据。作为示例,请考虑Thread类的以下实现:
public class CustomThread extends Thread {
 private String threadName;
 private String message;
 private Exchanger<String> exchanger;

 public CustomThread(String threadName, Exchanger<String> exchanger) {
   this.threadName = threadName;
   this.exchanger = exchanger;
 }

 public void setMessage(final String message) {
   this.message = message;
 }

 @Override
 public void run() {
   while (true) {
     try {
       message = exchanger.exchange(message);
       System.out.println(threadName + " поток получил сообщение: " + message);
       Thread.sleep(1000);
     } catch (Exception e) {
       e.printStackTrace();
     }
   }
 }
}
在线程构造函数中,我们定义了一个接受String类型对象的Exchanger对象,并且在启动时(在run方法中)我们使用它的Exchange()与同一Exchanger中使用此方法的另一个线程交换消息。让我们在main中运行它:
Exchanger<String> exchanger = new Exchanger<>();
CustomThread first = new CustomThread("Первый ", exchanger);
first.setMessage("Сообщение первого потока");
CustomThread second = new CustomThread("Второй", exchanger);
second.setMessage("Сообщение второго потока");
first.start();
second.start();
控制台将显示:
第一个线程收到消息: 来自第二个线程的消息 第二个线程收到消息: 来自第一个线程的消息 第二个线程收到消息: 来自第二个线程的消息 第一个线程收到消息: 来自第一个线程的消息 第二个线程收到消息: 来自第一个线程的消息线程收到消息:来自第一个线程的消息 第一个线程收到消息:来自第二个线程的消息...。
这意味着线程之间的数据交换成功了。

112.Thread类和Runnable接口有什么区别?

我首先要注意的是Thread是一个类,Runnable是一个接口,这是一个非常明显的区别=D Java 开发人员访谈问答分析。 第 13 - 3 部分我还要说Thread使用Runnable(组合)。也就是说,我们有两种方式:
  1. 继承自Thread,重写run方法,然后创建这个对象并通过start()方法启动线程。

  2. 在某个类中实现Runnable ,实现其run()方法,然后创建一个Thread对象,并将该对象对Runnable接口的实现赋给其构造函数。好吧,最后,使用start()方法启动Thread对象。

什么是更好的?让我们想一下:
  • 当您实现Runnable接口时,您不会更改线程的行为。本质上你只是给线程一些运行的东西。这就是我们的构图,这又被认为是一个很好的方法。

  • 实现Runnable可以为您的类提供更多灵活性。如果您继承自Thread,那么您执行的操作将始终在线程上。但如果您实现Runnable,它不必只是一个线程。毕竟,您可以在线程中运行它或将其传递给某些执行器服务。好吧,或者只是将其作为单线程应用程序中的任务传递到某个地方。

  • 使用Runnable允许您在逻辑上将任务执行与线程控制逻辑分开。

  • 在Java中,只能进行单继承,因此只能扩展一个类。同时,可扩展接口的数量是无限的(好吧,不是完全无限,而是65535,但你不太可能达到这个限制)。

好吧,到底用什么更好由你决定^^

113. 有线程T1、T2和T3。如何依次实施?Java 开发人员访谈问答分析。 第 13 - 4 部分

我想到的第一个也是最简单的事情是使用join()方法。它挂起当前线程(调用该方法的线程)的执行,直到调用该方法的线程完成执行。让我们创建自己的线程实现:
public class CustomThread extends Thread {
private String threadName;

 public CustomThread(final String  threadName){
   this.threadName = threadName;
 }

 @Override
 public void run() {
   System.out.println(threadName + " - начал свою работу");
   try {
     // происходит некая логика
     Thread.sleep(1000);
   } catch (InterruptedException e) {
     e.printStackTrace();
   }

   System.out.println(threadName + " - закончил свою работу");
 }
}
让我们使用join() 一一启动三个这样的线程:
CustomThread t1 = new CustomThread("Первый поток");
t1.start();
t1.join();
CustomThread t2 = new CustomThread("Второй поток");
t2.start();
t2.join();
CustomThread t3 = new CustomThread("Третий поток");
t3.start();
t3.join();
控制台输出:
第一个线程 - 开始其工作 第一个线程 - 完成其工作 第二个线程 - 开始其工作 第二个线程 - 完成其工作 第三个线程 - 开始其工作 第三个线程 - 完成其工作
这意味着我们已经完成了我们的任务。接下来,我们直接进入初级阶段的实际任务。

实际任务

114.矩阵对角线和(Leetcode问题)

条件: 计算主对角线上所有元素和附加对角线上不属于主对角线的所有元素的总和。 Java 开发人员访谈问答分析。 第 13 - 5 部分1. 对于以下形式的矩阵: mat = [[1,2,3], [4,5,6], [7,8,9]] 输出应为 - 25 2. 对于矩阵 - mat = [[1,1 ,1,1], [1,1,1,1], [1,1,1,1], [1,1,1,1]] 输出应为 - 8 3. 随着矩阵 - mat = [[ 5]] 结论应该是 - 5 暂停阅读并实施你的决定。我的解决方案如下:
public static int countDiagonalSum(int[][] matrix) {
 int sum = 0;
 for (int i = 0, j = matrix.length - 1; i < matrix.length; i++, j--) {
   sum += matrix[i][i];
   if (j != i) {
     sum += matrix[i][j];
   }
 }
 return sum;
}
一切都发生在一次遍历数组的过程中,在此期间我们有两个报告索引:i - 用于报告数组的行和主对角线的列,j - 用于报告附加对角线的列。如果主对角线的单元格与附加单元格重合,则在计算总和时忽略其中一个值。让我们使用条件矩阵进行检查:
int[][] arr1 = {
   {1, 2, 3},
   {4, 5, 6},
   {7, 8, 9}};
System.out.println(countDiagonalSum(arr1));

int[][] arr2 = {
   {1, 1, 1, 1},
   {1, 1, 1, 1},
   {1, 1, 1, 1},
   {1, 1, 1, 1}};
System.out.println(countDiagonalSum(arr2));

int[][] arr3 = {{5}};
System.out.println(countDiagonalSum(arr3));
控制台输出:
25 8 5

115.移动零(Leetcode 挑战)

条件: 在整数数组中,将所有 0 移动到末尾,保持非零元素的相对顺序。1. 使用数组:[0,1,0,3,12] 输出应为:[1,3,12,0,0] 2. 使用数组:[0] 输出应为:[0]暂停并写下我的决定...我的决定:
public static void moveZeroes(int[] nums) {
 int counterWithoutNulls = 0;
 int counterWithNulls = 0;
 int length = nums.length;
 while (counterWithNulls < length) {
   if (nums[counterWithNulls] == 0) {// находим нулевые элементы и увеличиваем счётчик
     counterWithNulls++;
   } else { // сдвигаем элементы на количество найденных нулевых элементов слева
     nums[counterWithoutNulls++] = nums[counterWithNulls++];
   }
 }
 while (counterWithoutNulls < length) {
   nums[counterWithoutNulls++] = 0;// заполняем последние элементы массива нулями согласно счётчику нулей
 }
}
考试:
int[] arr1 = {1, 2, 0, 0, 12, 9};
moveZeroes(arr1);
System.out.println(Arrays.toString(arr1));

int[] arr2 = {0};
moveZeroes(arr2);
System.out.println(Arrays.toString(arr2));
控制台输出:
[1, 2, 12, 9, 0, 0] [0]

116. 给定列表 <String> 名称。删除每个名字的第一个字母并旋转排序列表

1、首先想到的是Collections类的方法,它包含了很多集合的辅助方法:
public static List<String> processTheList(List<String> nameList) {
 for (int i = 0; i < nameList.size(); i++) {
   nameList.set(i, nameList.get(i).substring(1));
 }
 Collections.sort(nameList);
 return nameList;
}
2. 另外,如果我们使用 Java 版本 8 及更高版本,我们只需通过流显示解决方案:
public static List<String> processTheList(List<String> nameList) {
 return nameList.stream()
     .map(x -> x.substring(1))
     .sorted().collect(Collectors.toList());
}
无论选择哪种解决方案,检查都可能如下:
List<String> nameList = new ArrayList();
nameList.add("John");
nameList.add("Bob");
nameList.add("Anna");
nameList.add("Dmitriy");
nameList.add("Peter");
nameList.add("David");
nameList.add("Igor");

System.out.println(processTheList(nameList));
控制台输出:
[avid、eter、gor、mitriy、nna、ob、ohn]

117. 翻转阵列

解决方案 1 同样,首先想到的是使用辅助实用类Collections的方法。但既然我们有一个数组,我们首先需要将它转换成一个集合(列表):
public static Integer[] reverse(Integer[] arr) {
 List<Integer> list = Arrays.asList(arr);
 Collections.reverse(list);
 return list.toArray(arr);
}
解决方案2 由于问题是关于数组的,我认为有必要在不使用开箱即用的现成功能的情况下展示解决方案,可以这么说,根据经典:
public static Integer[] reverse(Integer[] arr) {
 for (int i = 0; i < arr.length / 2; i++) {
   int temp = arr[i];
   arr[i] = arr[arr.length - 1 - i];
   arr[arr.length - 1 - i] = temp;
 }
 return arr;
}
考试:
Integer[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9};
System.out.println(Arrays.toString(reverse(arr)));
控制台输出:
[9,8,7,6,5,4,3,2,1]

118.检查字符串是否是回文

Java 开发人员访谈问答分析。 第 13 - 6 部分解决方案 1 值得立即记住StringBuilder :与常规String相比,它更加灵活且具有丰富的方法。我们对相反的方法特别感兴趣:
public static boolean isPalindrome(String string) {
 string = string.toLowerCase(); //приводит всю строку к нижнему регистру
 StringBuilder builder = new StringBuilder();
 builder.append(string);
 builder.reverse(); // перевочиваем строку методом Builder-а
 return (builder.toString()).equals(string);
}
解决方案: 下一个方法将不使用开箱即用的“漏洞”。我们将字符串后面的字符与前面相应的字符进行比较:
public static boolean isPalindrome(String string) {
  string = string.toLowerCase();
 int length = string.length();
 int fromBeginning = 0;
 int fromEnd = length - 1;
 while (fromEnd > fromBeginning) {
   char forwardChar = string.charAt(fromBeginning++);
   char backwardChar = string.charAt(fromEnd--);
   if (forwardChar != backwardChar)
     return false;
 }
 return true;
}
我们检查这两种方法:
boolean isPalindrome = isPalindrome("Tenet");
System.out.println(isPalindrome);
控制台输出:
真的

119. 编写一个简单的排序算法(冒泡、选择或穿梭)。如何改进?

作为一个简单的算法实现,我选择了选择排序——Selection Sort:
public static void selectionSorting(int[] arr) {
 for (int i = 0; i < arr.length - 1; i++) {
   int min = i;
   for (int j = i + 1; j < arr.length; j++) {
     if (arr[j] < arr[min]) {
       min = j; // выбираем минимальный элемент в текущем числовом отрезке
     }
   }
   int temp = arr[min]; // меняем местами минимальный элемент с элементом под индексом i
   arr[min] = arr[i]; // так How отрезок постоянно уменьшается
   arr[i] = temp; // и выпадающие из него числа будут минимальными в текущем отрезке
 } // и How итог - числа оставшиеся вне текущей итерации отсортированы от самого наименьшего к большему
}
改进后的版本将如下所示:
public static void improvedSelectionSorting(int[] arr) {
 for (int i = 0, j = arr.length - 1; i < j; i++, j--) { // рассматриваемый отрезок с каждой итерацией
   // будет уменьшаться с ДВУХ сторон по одному элементу
   int min = arr[i];
   int max = arr[i];
   int minIndex = i;
   int maxIndex = i;
   for (int n = i; n <= j; n++) { // выбираем min и max на текущем отрезке
     if (arr[n] > max) {
       max = arr[n];
       maxIndex = n;
     } else if (arr[n] < min) {
       min = arr[n];
       minIndex = n;
     }
   }
   // меняем найденный минимальный элемент с позиции с индексом min на позицию с индексом i
   swap(arr, i, minIndex);

   if (arr[minIndex] == max) {// срабатывает, если элемент max оказался смещен предыдущей перестановкой -
     swap(arr, j, minIndex); // на старое место min, поэтому с позиции с индексом min смещаем его на позицию j
   } else {
     swap(arr, j, maxIndex); // простое обмен местами элементов с индексами max и j
   }
 }
}

static int[] swap(int[] arr, int i, int j) {
 int temp = arr[i];
 arr[i] = arr[j];
 arr[j] = temp;
 return arr;
}
好吧,现在我们需要确定排序是否真的有所改进。我们来比较一下性能:
long firstDifference = 0;
long secondDifference = 0;
long primaryTime;
int countOfApplying = 10000;
for (int i = 0; i < countOfApplying; i++) {
 int[] arr1 = {234, 33, 123, 4, 5342, 76, 3, 65,
     3, 5, 35, 75, 255, 4, 46, 48, 4658, 44, 22,
     678, 324, 66, 151, 268, 433, 76, 372, 45, 13,
     9484, 499959, 567, 774, 473, 3, 32, 865, 67, 43,
     63, 332, 24, 1};
 primaryTime = System.nanoTime();
 selectionSorting(arr1);
 firstDifference += System.nanoTime() - primaryTime;

 int[] arr2 = {234, 33, 123, 4, 5342, 76, 3, 65,
     3, 5, 35, 75, 255, 4, 46, 48, 4658, 44, 22,
     678, 324, 66, 151, 268, 433, 76, 372, 45, 13,
     9484, 499959, 567, 774, 473, 3, 32, 865, 67, 43,
     63, 332, 24, 1};
 primaryTime = System.nanoTime();
 improvedSelectionSorting(arr2);
 secondDifference += System.nanoTime() - primaryTime;
}

System.out.println(((double) firstDifference / (double) secondDifference - 1) * 100 + "%");
两种排序都在同一个循环中开始,因为 如果存在单独的循环,则从上面的代码中排序将显示比放在第二位更糟糕的结果。这是因为程序“预热”然后运行得更快一些。但我有点偏离主题了。在控制台中运行五次此检查后,我看到性能提高了: 36.41006735635892% 51.46131097160771% 41.88918834013988% 48.091980705743566% 37.120220461591444% 对于我来说,这是一个相当不错的结果结果。 Java 开发人员访谈问答分析。 第 13 - 7 部分

120. 编写一个算法(动作序列),用于将 int 类型的文字与 byte 类型的文字组合起来。解释一下内存发生了什么

  1. byte 值转换为 int。不会为其分配 1 个字节的内存,但与所有 int 值一样 - 4,如果该值尚未位于 int 堆栈上。如果有,则只会收到指向它的链接。

  2. 将两个int值相加,得到第三个。将为其分配一个新的内存部分 - 4 个字节(或者将从 int 堆栈接收对现有值的引用)。

    这种情况下,两个int的内存仍然会被占用,它们的值会分别存储在int栈上。

实际上,这就是我们列表中的初级问题的结尾。从下一篇文章开始,我们将了解中层问题。我想指出的是,中级问题也积极向入门级开发人员(初级)提出。所以请继续关注。好了,今天就到这里了:再见!Java 开发人员访谈问答分析。 第 13 - 8 部分
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION