111. چگونه داده ها را بین رشته ها تبادل کنیم؟
برای تبادل دادهها بین رشتهها، میتوانید از روشها و ابزارهای مختلفی استفاده کنید: به عنوان مثال، از متغیرهای اتمی، مجموعههای همگامسازی شده و یک سمافور استفاده کنید. اما برای حل این مشکل با 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();
}
}
}
}
در سازنده thread، یک شی Exchanger تعریف میکنیم که اشیایی از نوع String را میپذیرد ، و در هنگام راهاندازی (در روش run ) از 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 و رابط Runnable چیست؟
اولین چیزی که به آن اشاره می کنم این است که Thread یک کلاس است، Runnable یک رابط است، که یک تفاوت بسیار آشکار است =D همچنین می گویم که Thread از Runnable (ترکیب) استفاده می کند . یعنی دو راه داریم:-
از Thread ارث بری کنید ، متد run را لغو کنید، سپس این شی را ایجاد کنید و رشته را از طریق متد start() شروع کنید .
-
Runnable را در یک کلاس خاص پیادهسازی کنید ، متد run() آن را پیادهسازی کنید، و سپس یک شی Thread ایجاد کنید و این شیء پیادهسازی رابط Runnable را به سازنده آن اختصاص دهید . خوب، در پایان، شی Thread را با استفاده از متد start() اجرا کنید .
-
وقتی رابط Runnable را پیاده سازی می کنید ، رفتار رشته را تغییر نمی دهید. در اصل شما فقط به موضوع چیزی برای اجرا می دهید. و این ترکیب ماست که به نوبه خود رویکرد خوبی محسوب می شود.
-
اجرای Runnable انعطاف پذیری بیشتری به کلاس شما می دهد. اگر از Thread ارث ببرید ، عملی که انجام میدهید همیشه در thread خواهد بود. اما اگر Runnable را پیاده سازی کنید ، لازم نیست که فقط یک رشته باشد. پس از همه، شما می توانید آن را در یک رشته اجرا کنید یا آن را به برخی از سرویس های اجرایی ارسال کنید. خوب، یا فقط آن را به عنوان یک کار در یک برنامه تک رشته ای منتقل کنید.
-
استفاده از Runnable به شما این امکان را می دهد که به طور منطقی اجرای وظیفه را از منطق کنترل رشته جدا کنید.
-
در جاوا، تنها وراثت واحد امکان پذیر است، بنابراین فقط یک کلاس را می توان گسترش داد. در عین حال، تعداد اینترفیسهای قابل ارتقا نامحدود است (خب، نه کاملاً نامحدود، اما 65535 ، اما بعید است که هرگز به این حد نرسید).
113. نخ های T1، T2 و T3 وجود دارد. چگونه آنها را به صورت متوالی پیاده سازی کنیم؟
اولین و ساده ترین چیزی که به ذهن می رسد استفاده از متد 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)
شرط: مجموع تمام عناصر روی قطر اصلی و تمام عناصر روی قطر اضافی که جزء قطر اصلی نیستند را محاسبه کنید. 1. با یک ماتریس به شکل: mat = [[1،2،3]، [4،5،6]، [7،8،9]] خروجی باید - 25 باشد. با یک ماتریس - mat = [[1,1,1,1], [1,1,1,1], [1,1,1,1], [1,1,1,1]] خروجی باید - 8 3 باشد. a matrix - 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));
خروجی کنسول:
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));
خروجی کنسول:
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. همچنین، اگر از جاوا نسخه 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)));
خروجی کنسول:
118. بررسی کنید که آیا یک رشته پالیندروم است یا خیر
راه حل 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.0919807057235892% برای این است. نتیجه خیلی خوب
120. یک الگوریتم (توالی اعمال) برای نوشتن یک کلمه از نوع int با یک بایت از نوع تحت اللفظی بنویسید. توضیح دهید که چه اتفاقی برای حافظه می افتد
-
مقدار بایت به int تبدیل می شود. 1 بایت حافظه برای آن اختصاص داده نمی شود، اما مانند همه مقادیر int - 4، اگر این مقدار هنوز در پشته int نباشد. اگر وجود داشته باشد، یک لینک به آن به سادگی دریافت می شود.
-
دو مقدار int اضافه می شود و سومی به دست می آید. یک بخش حافظه جدید برای آن اختصاص داده می شود - 4 بایت (یا یک مرجع از پشته int به مقدار موجود دریافت می شود).
در این حالت، حافظه دو int همچنان اشغال می شود و مقادیر آنها به ترتیب در پشته int ذخیره می شود.
GO TO FULL VERSION