pengenalan
Terdapat banyak sains di dunia yang mengkaji teori kebarangkalian. Dan sains terdiri daripada pelbagai bahagian. Sebagai contoh, dalam matematik terdapat bahagian berasingan yang dikhaskan untuk mengkaji peristiwa rawak, kuantiti, dll. Tetapi sains tidak dipandang ringan. Dalam kes ini, teori kebarangkalian mula terbentuk apabila orang cuba memahami corak yang ada dalam membaling dadu apabila bermain permainan peluang. Jika anda melihat dengan teliti, terdapat banyak perkara yang kelihatan rawak di sekeliling kita. Tetapi segala-galanya secara rawak tidak sepenuhnya rawak. Tetapi lebih lanjut mengenai itu kemudian. Bahasa pengaturcaraan Java juga mempunyai sokongan untuk nombor rawak, bermula dengan versi pertama JDK. Nombor rawak dalam Java boleh digunakan menggunakan kelas
java.util.Random . Untuk ujian, kami akan menggunakan
pengkompil dalam talian tutorialspoint java . Berikut ialah contoh primitif menggunakan
Rawak untuk meniru balingan "dadu", atau kiub dalam bahasa Rusia:
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);
}
}
Nampaknya ini boleh menjadi penghujung perihalan
Random , tetapi ia tidak semudah itu. Mari buka penerangan kelas
java.util.Random dalam API Java. Dan di sini kita melihat perkara yang menarik. Kelas
Rawak menggunakan nombor rawak pseudo. Bagaimana pula? Ternyata nombor rawak tidak begitu rawak?
Pseudo-randomness java.util.Random
Dokumentasi untuk kelas
java.util.Random mengatakan bahawa jika kejadian
Rawak dicipta dengan parameter
benih yang sama dan urutan tindakan yang sama dilakukan pada kejadian, ia mengembalikan urutan nombor yang sama. Dan jika kita melihat dengan teliti, kita dapat melihat bahawa
Random sebenarnya mempunyai
pembina yang mengambil nilai yang
lama sebagai
benih:
Random rnd1 = new Random(1L);
Random rnd2 = new Random(1L);
boolean test = rnd1.nextInt(6) == rnd2.nextInt(6);
System.out.println("Test: " + test);
Contoh ini akan kembali
benar kerana
benih kedua-dua kejadian adalah sama. Apa nak buat? Pembina lalai sebahagiannya menyelesaikan masalah. Di bawah ialah contoh kandungan pembina
Rawak :
public Random() {
this(seedUniquifier() ^ System.nanoTime());
}
Pembina lalai menggunakan operasi bitwise exclusive
OR .
Dan menggunakan panjang mewakili masa semasa dan beberapa
benih untuk ini :
private static long seedUniquifier() {
for (;;) {
long current = seedUniquifier.get();
long next = current * 181783497276652981L;
if (seedUniquifier.compareAndSet(current, next))
return next;
}
}
Satu lagi perkara yang menarik di sini ialah setiap panggilan ke kaedah getter
seedUniquifier mengubah nilai
seedUniquifier . Iaitu, kelas direka bentuk untuk memilih nombor rawak secekap mungkin. Walau bagaimanapun, seperti yang dinyatakan dalam dokumentasi, mereka "
tidak selamat dari segi kriptografi ". Iaitu, untuk beberapa tujuan penggunaan untuk tujuan kriptografi (penjanaan kata laluan, dll.) ia tidak sesuai, kerana urutan dengan pendekatan yang betul diramalkan. Terdapat contoh mengenai topik ini di Internet, contohnya di sini: "
Meramalkan Math.random() seterusnya dalam Java ". Atau sebagai contoh kod sumber di sini: "
Vulnerability Weak Crypto ". java.util.Random (penjana nombor rawak) mempunyai "pintasan" tertentu,
iaitu versi pendek panggilan yang dilaksanakan melalui Math.random:
public static void main(String []args){
int random_number = 1 + (int) (Math.random() * 6);
System.out.println("Value: " + random_number);
}
Tetapi jika anda melihat dengan teliti, Random yang sama ada di dalam:
public static double random() {
return RandomNumberGeneratorHolder.randomNumberGenerator.nextDouble();
}
private static final class RandomNumberGeneratorHolder {
static final Random randomNumberGenerator = new Random();
}
JavaDoc menasihatkan menggunakan kelas
SecureRandom untuk "
penjana nombor pseudo-rawak yang selamat secara kriptografi ".
Java Rawak Selamat
Kelas
SecureRandom ialah subkelas
java.util.Random dan terletak dalam pakej
java.security . Perbandingan kedua-dua kelas ini boleh dibaca dalam artikel "
Perbezaan antara java.util.Random dan java.security.SecureRandom ". Mengapa SecureRandom ini sangat bagus? Hakikatnya ialah baginya sumber nombor rawak adalah sesuatu yang ajaib seperti "kolam entropi teras". Ini adalah tambah dan tolak. Anda boleh membaca tentang keburukan ini dalam artikel: “
Bahaya java.security.SecureRandom ”. Ringkasnya, Linux mempunyai penjana nombor rawak kernel (RNG). RNG menjana nombor rawak berdasarkan data daripada kumpulan entropi, yang diisi berdasarkan peristiwa rawak dalam sistem, seperti pemasaan papan kekunci dan cakera, pergerakan tetikus, gangguan dan trafik rangkaian. Maklumat lanjut tentang kumpulan entropi diterangkan dalam bahan "
Nombor rawak dalam Linux (RNG) atau cara “mengisi” /dev/random dan /dev/urandom ". Pada sistem Windows, SHA1PRNG digunakan, dilaksanakan dalam sun.security.provider.SecureRandom. Dengan pembangunan Java, SecureRandom juga berubah, yang patut dibaca dalam ulasan "
Kemas kini Java SecureRandom pada April 2016 " untuk gambaran lengkap.
Multithreading atau jadi seperti Caesar
Jika anda melihat kod kelas
Random , nampaknya tiada apa-apa yang menunjukkan masalah. Kaedah tidak ditandakan
disegerakkan . Tetapi ada satu TETAPI: apabila mencipta
Rawak dengan pembina lalai dalam beberapa utas, kami akan berkongsi
benih contoh yang sama antara mereka , yang mana
Rawak akan dibuat . Dan juga apabila nombor rawak baharu diterima,
AtomicLong dalaman contoh turut berubah . Di satu pihak, tidak ada yang salah dengan ini dari sudut pandangan logik, kerana...
AtomicLong digunakan . Sebaliknya, anda perlu membayar untuk segala-galanya, termasuk produktiviti. Dan untuk ini juga. Oleh itu, walaupun dokumentasi rasmi untuk
java.util.Random berkata: "
Instance java.util.Random adalah threadsafe. Walau bagaimanapun, penggunaan serentak java.util.Random yang sama merentas urutan mungkin menghadapi perbalahan dan akibatnya prestasi yang lemah. Pertimbangkan sebaliknya menggunakan ThreadLocalRandom dalam reka bentuk berbilang benang ". Iaitu, dalam aplikasi berbilang benang apabila secara aktif menggunakan
Rawak daripada beberapa utas, lebih baik menggunakan kelas
ThreadLocalRandom . Penggunaannya sedikit berbeza daripada
Random biasa :
public static void main(String []args){
int rand = ThreadLocalRandom.current().nextInt(1,7);
System.out.println("Value: " + rand);
}
Seperti yang anda lihat, kami tidak menentukan
benih untuknya . Contoh ini diterangkan dalam tutorial rasmi daripada Oracle:
Concurrent Random Numbers . Anda boleh membaca lebih lanjut mengenai kelas ini dalam ulasan: "
Panduan untuk ThreadLocalRandom di Java ".
StreamAPI dan Rawak
Dengan keluaran Java 8, kami mempunyai banyak ciri baharu. Termasuk Stream API. Dan perubahan juga mempengaruhi penjanaan nilai
Rawak . Sebagai contoh, kelas
Rawak mempunyai kaedah baharu yang membolehkan anda mendapatkan
Strim dengan nilai rawak seperti
int
,
double
atau
long
. Sebagai contoh:
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));
}
}
Terdapat juga kelas baru
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));
}
}
Anda boleh membaca lebih lanjut tentang perbezaan antara
SplittableRandom dan kelas lain di sini: "
Cara yang berbeza untuk mencipta nombor Rawak dalam Java ".
Kesimpulan
Saya fikir ia berbaloi untuk membuat kesimpulan. Anda perlu membaca JavaDoc dengan teliti untuk kelas yang digunakan. Di sebalik sesuatu yang mudah pada pandangan pertama seperti Random terdapat nuansa yang boleh memainkan jenaka yang kejam. #Viacheslav
GO TO FULL VERSION