معرفی
علوم زیادی در دنیا وجود دارد که به مطالعه نظریه احتمال می پردازند. و علوم از بخشهای گوناگون تشکیل شده است. به عنوان مثال، در ریاضیات بخش جداگانه ای به مطالعه رویدادهای تصادفی، کمیت ها و غیره اختصاص داده شده است. اما علم به سادگی گرفته نمی شود. در این مورد، نظریه احتمال زمانی شروع به شکل گیری کرد که مردم سعی کردند بفهمند چه الگوهایی در پرتاب تاس در هنگام انجام بازی های شانسی وجود دارد. اگر دقت کنید، چیزهای به ظاهر تصادفی زیادی در اطراف ما وجود دارد. اما همه چیز تصادفی کاملاً تصادفی نیست. اما در ادامه بیشتر در مورد آن. زبان برنامه نویسی جاوا همچنین از اعداد تصادفی پشتیبانی می کند که از اولین نسخه JDK شروع می شود. اعداد تصادفی در جاوا را می توان با استفاده از کلاس
java.util.Random استفاده کرد .
برای تست از کامپایلر آنلاین tutorialspoint java استفاده خواهیم کرد . در اینجا یک مثال ابتدایی از استفاده
تصادفی برای تقلید پرتاب "تاس" یا مکعب به زبان روسی آورده شده است:
import java.util.Random;
public class HelloWorld{
public static void main(String []args){
Random rnd = new Random();
int number = rnd.nextInt(6) + 1;
System.out.println("Random number: " + number);
}
}
به نظر می رسد که این می تواند پایان توصیف
Random باشد ، اما به این سادگی نیست. بیایید توضیحات کلاس
java.util.Random را در Java API باز کنیم. و در اینجا شاهد چیزهای جالبی هستیم. کلاس
Random از اعداد شبه تصادفی استفاده می کند. چطور؟ معلوم می شود که اعداد تصادفی چندان تصادفی نیستند؟
شبه تصادفی java.util.Random
مستندات کلاس
java.util.Random میگوید که اگر نمونههایی
از Random با پارامتر seed یکسان ایجاد شوند و دنبالههای مشابهی از اقدامات روی نمونهها انجام شود، آنها دنبالههای یکسانی از اعداد را برمیگردانند. و اگر به دقت نگاه کنیم، میتوانیم ببینیم که
Random در واقع سازندهای دارد که مقداری
طولانی به عنوان
دانه میگیرد:
Random rnd1 = new Random(1L);
Random rnd2 = new Random(1L);
boolean test = rnd1.nextInt(6) == rnd2.nextInt(6);
System.out.println("Test: " + test);
این مثال
درست باز خواهد گشت زیرا
بذر هر دو مورد یکسان است. چه باید کرد؟ سازنده پیش فرض تا حدودی مشکل را حل می کند. در زیر نمونه ای از محتویات سازنده
تصادفی آورده شده است :
public Random() {
this(seedUniquifier() ^ System.nanoTime());
}
سازنده پیش فرض از عملیات
OR انحصاری بیتی استفاده می کند . و برای این کار از
یک طولانی نشان دهنده زمان فعلی و مقداری
دانه استفاده می کند :
private static long seedUniquifier() {
for (;;) {
long current = seedUniquifier.get();
long next = current * 181783497276652981L;
if (seedUniquifier.compareAndSet(current, next))
return next;
}
}
نکته جالب دیگر در اینجا این است که هر فراخوانی به متد
seedUniquifier دریافت کننده مقدار
seedUniquifier را تغییر می دهد . به این معنا که کلاس طوری طراحی شده است که اعداد تصادفی را به بهترین نحو ممکن انتخاب کند. با این حال، همانطور که اسناد می گوید، آنها "
از نظر رمزگذاری ایمن نیستند ". یعنی برای برخی از اهداف استفاده برای مقاصد رمزنگاری (تولید رمز عبور و غیره) مناسب نیست، زیرا توالی با رویکرد مناسب پیش بینی می شود. مثال هایی در مورد این موضوع در اینترنت وجود دارد، به عنوان مثال در اینجا: "
پیش بینی Math.random() بعدی در جاوا ". یا به عنوان مثال کد منبع اینجا: "
آسیب پذیری ضعیف رمزگذاری ". java.util.Random (تولید کننده اعداد تصادفی) یک "میانبر" خاص دارد،
یعنی یک نسخه کوتاه شده از تماس که از طریق Math.random اجرا می شود:
public static void main(String []args){
int random_number = 1 + (int) (Math.random() * 6);
System.out.println("Value: " + random_number);
}
اما اگر با دقت نگاه کنید، همان Random داخلش مینشیند:
public static double random() {
return RandomNumberGeneratorHolder.randomNumberGenerator.nextDouble();
}
private static final class RandomNumberGeneratorHolder {
static final Random randomNumberGenerator = new Random();
}
JavaDoc استفاده از کلاس
SecureRandom را برای "
تولید کننده اعداد شبه تصادفی ایمن رمزنگاری " توصیه می کند.
جاوا تصادفی ایمن
کلاس
SecureRandom یک زیر کلاس از
java.util.Random است و در بسته java.security قرار دارد . مقایسه این دو کلاس را می توانید در مقاله "
تفاوت بین java.util.Random و java.security.SecureRandom " بخوانید. چرا این SecureRandom اینقدر خوب است؟ واقعیت این است که برای او منبع اعداد تصادفی چیزی جادویی مانند "استخر آنتروپی هسته" است. این هم یک مثبت و هم منفی است. مضرات این کار را می توانید در مقاله بخوانید: "
خطرات java.security.SecureRandom ". به طور خلاصه، لینوکس دارای یک هسته مولد اعداد تصادفی (RNG) است. RNG اعداد تصادفی را بر اساس داده های استخر آنتروپی تولید می کند که بر اساس رویدادهای تصادفی در سیستم، مانند زمان بندی صفحه کلید و دیسک، حرکات ماوس، وقفه ها و ترافیک شبکه پر می شود. اطلاعات بیشتر در مورد استخر آنتروپی در مطالب "
اعداد تصادفی در لینوکس (RNG) یا نحوه "پر کردن" /dev/random و /dev/urandom " توضیح داده شده است. در سیستم های ویندوز، SHA1PRNG استفاده می شود که در sun.security.provider.SecureRandom پیاده سازی شده است. با توسعه جاوا، SecureRandom نیز تغییر کرد، که ارزش خواندن آن را در بررسی "
به روز رسانی جاوا SecureRandom از آوریل 2016 " برای یک تصویر کامل دارد.
چند رشته ای یا مانند سزار باشید
اگر به کد کلاس
Random نگاه کنید ، به نظر می رسد هیچ چیز نشان دهنده مشکل نیست.
روشها به صورت همزمان علامتگذاری نشدهاند . اما یک BUT وجود دارد: هنگام ایجاد
Random با سازنده پیشفرض در چندین رشته، همان
seed نمونه را بین آنها به اشتراک میگذاریم که توسط آن
Random ایجاد میشود . و همچنین هنگامی که یک عدد تصادفی جدید دریافت می شود،
AtomicLong داخلی نمونه نیز تغییر می کند . از یک طرف این از نظر منطقی اشکالی ندارد زیرا ...
AtomicLong استفاده می شود . از سوی دیگر، شما باید برای همه چیز از جمله بهره وری هزینه کنید. و برای این هم بنابراین، حتی مستندات رسمی برای
java.util.Random می گوید: "
نمونه های java.util.Random امن هستند. با این حال، استفاده همزمان از همان نمونه java.util.Random در سراسر رشته ها ممکن است با اختلاف و در نتیجه عملکرد ضعیف مواجه شود. در عوض از ThreadLocalRandom در طرح های چند رشته ای استفاده کنید . یعنی در برنامه های چند رشته ای هنگام استفاده فعال از
Random از چندین رشته، بهتر است از کلاس
ThreadLocalRandom استفاده شود .
استفاده از آن کمی با تصادفی معمولی متفاوت است :
public static void main(String []args){
int rand = ThreadLocalRandom.current().nextInt(1,7);
System.out.println("Value: " + rand);
}
همانطور که می بینید، ما
یک دانه برای آن مشخص نمی کنیم . این مثال در آموزش رسمی Oracle توضیح داده شده است:
اعداد تصادفی همزمان . می توانید اطلاعات بیشتری در مورد این کلاس در بررسی بخوانید: "
راهنمای ThreadLocalRandom در جاوا ".
StreamAPI و تصادفی
با انتشار جاوا 8 ویژگی های جدید زیادی داریم. از جمله Stream API. و تغییرات همچنین بر تولید مقادیر
تصادفی تأثیر گذاشت . به عنوان مثال، کلاس
Random متدهای جدیدی دارد که به شما امکان می دهد یک Stream با مقادیر تصادفی مانند
int
،
double
یا دریافت کنید
long
. مثلا:
import java.util.Random;
public class HelloWorld{
public static void main(String []args){
new Random().ints(10, 1, 7).forEach(n -> System.out.println(n));
}
}
همچنین یک کلاس جدید SplittableRandom وجود دارد :
import java.util.SplittableRandom;
public class HelloWorld{
public static void main(String []args){
new SplittableRandom().ints(10, 1, 7).forEach(n -> System.out.println(n));
}
}
در اینجا می توانید در مورد تفاوت بین
SplittableRandom و سایر کلاس ها بیشتر بخوانید: "
روش های مختلف برای ایجاد اعداد تصادفی در جاوا ".
نتیجه
به نظر من ارزش نتیجه گیری را دارد. شما باید JavaDoc را برای کلاس های استفاده شده به دقت بخوانید. پشت چیزی که در نگاه اول ساده مانند تصادفی است، نکات ظریفی وجود دارد که می تواند یک شوخی بی رحمانه باشد. #ویاچسلاو
GO TO FULL VERSION