JavaRush /مدونة جافا /Random-AR /تحليل الأسئلة والأجوبة من المقابلات لمطور جافا. الجزء 13

تحليل الأسئلة والأجوبة من المقابلات لمطور جافا. الجزء 13

نشرت في المجموعة
مرحبًا!
الحركة نحو الهدف هي في المقام الأول حركة.
لذلك، لا يكفي مجرد التفكير في أنك تريد تحقيق شيء ما. أنت بحاجة إلى القيام بشيء ما - حتى ولو بأصغر الخطوات - ولكن افعلها كل يوم، وبهذه الطريقة فقط ستحقق الهدف النهائي. وبما أنك هنا لتصبح مطورًا لـ Java، فأنت بحاجة إلى اتخاذ خطوة بسيطة على الأقل نحو تعميق معرفتك بـ Java كل يوم. بالنسبة لخطوة Java اليوم، أقترح عليك أن تتعرف على الجزء الجديد من تحليل أسئلة المقابلة الأكثر شيوعًا للمطورين. تحليل الأسئلة والأجوبة من المقابلات لمطور جافا.  الجزء 13 - 1سنتناول اليوم الجزء العملي من الأسئلة المخصصة للمبتدئين. المهمة العملية في المقابلة ليست غير شائعة. من المهم ألا تضيع في مثل هذا الموقف، وحاول الحفاظ على هدوئك وتقديم الحل الأمثل، أو حتى عدة حلول. وأود أيضًا أن أوصي بعدم التزام الصمت عند حل مشكلة ما، بل التعليق على سلسلة أفكارك وكتابة الحل، أو بعد الكتابة، اشرح بالكلمات ما فعلته ولماذا. وهذا سيجعلك محبوبًا لدى القائم بإجراء المقابلة أكثر بكثير من مجرد قرار صامت. اذا هيا بنا نبدأ!

111. كيفية تبادل البيانات بين المواضيع؟

تحليل الأسئلة والأجوبة من المقابلات لمطور جافا.  الجزء 13 - 2لتبادل البيانات بين المواضيع، يمكنك استخدام العديد من الأساليب والوسائل المختلفة: على سبيل المثال، استخدام المتغيرات الذرية، والمجموعات المتزامنة، والإشارة. ولكن لحل هذه المشكلة، سأقدم مثالا مع Exchanger . Exchanger عبارة عن فئة مزامنة من الحزمة المتزامنة التي تسهل تبادل العناصر بين زوج من الخيوط عن طريق إنشاء نقطة تزامن مشتركة. استخدامه يبسط تبادل البيانات بين خيطين. الطريقة التي تعمل بها بسيطة للغاية: فهي تنتظر خيطين منفصلين لاستدعاء طريقة التبادل () الخاصة بها . يتم إنشاء شيء مثل نقطة التبادل بينهما: يضع الخيط الأول موضوعه ويستقبل موضوع الآخر في المقابل، ويستقبل الأخير بدوره موضوع الأول ويضع موضوعه. أي أن الخيط الأول يستخدم طريقة التبادل () ويظل خاملاً حتى يستدعي مؤشر ترابط آخر طريقة التبادل () على نفس الكائن ويتم تبادل البيانات بينهما. على سبيل المثال، ضع في اعتبارك التنفيذ التالي لفئة 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();
     }
   }
 }
}
في مُنشئ مؤشر الترابط، نحدد كائن Exchanger الذي يقبل كائنات من النوع String ، وعند بدء التشغيل (في طريقة التشغيل ) نستخدم Exchange() الخاص به لتبادل رسالة مع مؤشر ترابط آخر يستخدم هذه الطريقة في نفس Exchanger . دعونا تشغيله بشكل رئيسي :
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 والواجهة القابلة للتشغيل؟

أول شيء سألاحظه هو أن Thread عبارة عن فئة، وRunnable عبارة عن واجهة، وهو فرق واضح جدًا =D تحليل الأسئلة والأجوبة من المقابلات لمطور جافا.  الجزء 13 - 3سأقول أيضًا أن Thread يستخدم Runnable (التركيب). أي أن أمامنا طريقين:
  1. وراثة من Thread ، وتجاوز طريقة التشغيل، ثم إنشاء هذا الكائن وبدء تشغيل الخيط من خلال طريقة start() .

  2. قم بتنفيذ Runnable في فئة معينة، وقم بتنفيذ طريقة run() الخاصة بها ، ثم قم بإنشاء كائن Thread ، وقم بتعيين تنفيذ هذا الكائن لواجهة Runnable إلى المُنشئ الخاص به . حسنًا، في النهاية، قم بتشغيل كائن Thread باستخدام طريقة start() .

ما هو الأفضل؟ لنفكر قليلا:
  • عندما تقوم بتنفيذ واجهة Runnable ، فإنك لا تغير سلوك مؤشر الترابط. في الأساس، أنت فقط تعطي الخيط شيئًا ليتم تشغيله. وهذا هو تكويننا الذي يعتبر بدوره منهجًا جيدًا.

  • يوفر تطبيق Runnable مزيدًا من المرونة لفصلك. إذا ورثت من Thread ، فسيكون الإجراء الذي تقوم به دائمًا على الموضوع. ولكن إذا قمت بتطبيق Runnable، فلا يجب أن يكون مجرد موضوع. بعد كل شيء، يمكنك إما تشغيله في سلسلة رسائل أو تمريره إلى بعض خدمات المنفذ. حسنًا، أو قم فقط بتمريرها إلى مكان ما كمهمة في تطبيق ذي ترابط واحد.

  • يتيح لك استخدام Runnable فصل تنفيذ المهمة منطقيًا عن منطق التحكم في مؤشر الترابط.

  • في Java، يكون الوراثة واحدة فقط ممكنة، لذلك يمكن تمديد فئة واحدة فقط. في الوقت نفسه، عدد الواجهات القابلة للتوسيع غير محدود (حسنًا، ليس غير محدود تمامًا، ولكن 65535 ، ولكن من غير المرجح أن تصل إلى هذا الحد على الإطلاق).

حسنًا، ما هو الأفضل استخدامه بالضبط متروك لك لتقرر ^^

113. هناك خيوط T1 و T2 و T3. كيفية تنفيذها بالتتابع؟تحليل الأسئلة والأجوبة من المقابلات لمطور جافا.  الجزء 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)

الحالة: احسب مجموع كل العناصر الموجودة على القطر الرئيسي وجميع العناصر الموجودة على القطر الإضافي التي لا تشكل جزءًا من القطر الرئيسي. تحليل الأسئلة والأجوبة من المقابلات لمطور جافا.  الجزء 13 - 51. مع مصفوفة من الشكل: 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. مع مصفوفة - حصيرة = [[ 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. تحريك الأصفار (تحدي Letcode)

الحالة: في مصفوفة أعداد صحيحة، قم بنقل جميع الأصفار إلى النهاية، مع الحفاظ على الترتيب النسبي للعناصر غير الصفرية. 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. قائمة أسماء <سلسلة> معينة. قم بإزالة الحرف الأول من كل اسم وقم بتدوير القائمة التي تم فرزها

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));
إخراج وحدة التحكم:
[متعطشا، إيتر، جور، ميتري، نا، أوب، أوهن]

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. تحقق مما إذا كانت السلسلة متناظرة

تحليل الأسئلة والأجوبة من المقابلات لمطور جافا.  الجزء 13 - 6الحل 1 يجدر بنا أن نتذكر على الفور StringBuilder : فهو أكثر مرونة وغنيًا بالطرق المختلفة مقارنة بالسلسلة العادية . نحن مهتمون بشكل خاص بالطريقة العكسية :
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% بالنسبة لي، هذا جيد جدًا نتيجة. تحليل الأسئلة والأجوبة من المقابلات لمطور جافا.  الجزء 13 - 7

120. اكتب خوارزمية (تسلسل الإجراءات) لتكوين حرفي من النوع int مع حرفي من النوع بايت. اشرح ماذا يحدث للذاكرة

  1. يتم تحويل قيمة البايت إلى int. لن يتم تخصيص بايت واحد من الذاكرة له، ولكن مثل جميع قيم int - 4، إذا لم تكن هذه القيمة موجودة بعد في مكدس int. إذا كان هناك، سيتم ببساطة تلقي رابط إليه.

  2. ستتم إضافة قيمتين int وسيتم الحصول على القيمة الثالثة. سيتم تخصيص قسم ذاكرة جديد له - 4 بايت (أو سيتم استلام مرجع من مكدس int إلى القيمة الموجودة).

    في هذه الحالة، ستظل ذاكرة ints مشغولة، وسيتم تخزين قيمها في مكدس int، على التوالي.

في الواقع، هذا هو المكان الذي تنتهي فيه أسئلة المستوى المبتدئ من قائمتنا. بدءًا من المقالة التالية، سوف نفهم مشكلات المستوى المتوسط. أود أن أشير إلى أن الأسئلة ذات المستوى المتوسط ​​يتم طرحها أيضًا بشكل نشط على المطورين المبتدئين - المبتدئين. ابقي على اتصال. حسنًا، هذا كل شيء لهذا اليوم: نراكم!تحليل الأسئلة والأجوبة من المقابلات لمطور جافا.  الجزء 13 - 8
مواد أخرى في السلسلة:
تعليقات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION