JavaRush /Java Blog /Random-TW /Java 開發者訪談問答分析。第13部分

Java 開發者訪談問答分析。第13部分

在 Random-TW 群組發布
你好!
朝著目標的運動首先是運動。
因此,僅僅認為自己想要實現某些目標是不夠的。你需要做一些事情——即使是最小的步驟——但每天都這樣做,只有這樣你才能實現最終的目標。由於您來這裡是為了成為 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.091980705743516% 48.091980705743566%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