java etiketine sahip kayıtlar gösteriliyor. Tüm kayıtları göster
java etiketine sahip kayıtlar gösteriliyor. Tüm kayıtları göster

Örnek bir Java Hello World Kodu ve Assembly Kodu karşılığı



Java dili yüksek seviye, nesne yönelimli bir programlama dilidir, oysa Assembly dili düşük seviyeli, makineye yakın bir dildir. Aynı işlevi gerçekleştiren Java kodu ve onun Assembly dilindeki karşılığını göstermek oldukça karmaşık olabilir çünkü bu iki dil arasındaki farklar çok büyüktür. Ancak, basit bir örnek üzerinden gidelim.


Java'da yazılmış basit bir "Merhaba Dünya" programı şu şekilde olabilir:



public class HelloWorld {

    public static void main(String[] args) {

        System.out.println("Merhaba Dünya");

    }

}

Bu Java kodunun Assembly dilinde bir karşılığı oluşturmak için, öncelikle hangi Assembly dilini (x86, ARM, vb.) ve işletim sistemini (Windows, Linux, vb.) kullanacağımızı belirlememiz gerekir. Farklı Assembly dilleri ve işletim sistemleri için yazım şekilleri değişiklik gösterebilir.


Örnek olarak, x86 Assembly dilinde ve Linux işletim sistemi için basit bir "Merhaba Dünya" programı aşağıdaki gibi olabilir:


section .data

    msg db 'Merhaba Dünya',0xa

    len equ $ - msg


section .text

    global _start


_start:

    mov eax, 4          ; syscall number for sys_write

    mov ebx, 1          ; file descriptor 1 is stdout

    mov ecx, msg        ; message to write

    mov edx, len        ; message length

    int 0x80            ; call kernel


    mov eax, 1          ; syscall number for sys_exit

    xor ebx, ebx        ; return 0 status

    int 0x80            ; call kernel

Bu Assembly kodu, basit bir "Merhaba Dünya" mesajını terminal üzerinde gösterir. Ancak, bu kodun Java koduyla aynı işlevi gerçekleştirdiğini söylemek zor çünkü Assembly, donanıma çok daha yakın bir düzeyde çalışır ve işletim sistemiyle doğrudan etkileşime girer. Java ise JVM (Java Sanal Makinesi) üzerinde çalışır ve birçok donanım ve işletim sistemi detayından soyutlanmıştır.


Linux işletim sistemi üzerinde çalışacak şekilde tasarlanmıştır. Her satırın ne yaptığını açıklayayım:


section .data


Bu satır, veri bölümünün başlangıcını işaret eder. Burada sabit veriler ve değişkenler tanımlanır.


msg db 'Merhaba Dünya',0xa


msg adında bir değişken tanımlar ve bu değişkene 'Merhaba Dünya' stringini ve bir satır sonu karakteri (0xa, yani newline) atar.


len equ $ - msg


len adında bir sabit tanımlar. Bu sabit, msg değişkeninin uzunluğunu hesaplar. $ işareti geçerli adresi, msg ise msg değişkeninin başlangıç adresini temsil eder. Bu fark, mesajın uzunluğunu verir.


section .text


Bu, programın kod bölümünün başlangıcını işaret eder. Burada programın çalıştırılabilir kodları yer alır.


global _start


Bu satır, _start etiketinin diğer dosyalar tarafından görülebilir olduğunu belirtir. Bu, programın giriş noktasını işaret eder.


_start:


Bu, programın başladığı yerdir.


mov eax, 4


EAX kaydedicisine 4 değerini yükler. Bu, sys_write sistem çağrısının syscall numarasıdır.


mov ebx, 1


EBX kaydedicisine 1 değerini yükler. Bu, standart çıktı dosya tanıtıcısını (stdout) temsil eder.


mov ecx, msg


ECX kaydedicisine msg değişkeninin adresini yükler. Bu, yazılacak mesajı gösterir.


mov edx, len


EDX kaydedicisine len sabitini yükler. Bu, yazılacak mesajın uzunluğunu belirtir.


int 0x80


Linux kerneline bir kesme çağrısı yapar. Bu çağrı, EAX'de belirtilen sistem çağrısını (bu durumda sys_write) gerçekleştirir.


mov eax, 1


EAX kaydedicisine 1 değerini yükler. Bu, sys_exit sistem çağrısının syscall numarasıdır.


xor ebx, ebx


EBX kaydedicisini sıfırlar. Programın çıkış durumu olarak 0 döndürülmesini sağlar.


int 0x80


Yine Linux kerneline bir kesme çağrısı yapar ve EAX'de belirtilen sistem çağrısını (bu durumda sys_exit) gerçekleştirir.

Bu kod, temel bir Assembly programının nasıl yazılacağı ve basit sistem çağrılarının nasıl kullanılacağı konusunda iyi bir örnektir.


Makina diline de çevirip yorumlar mısın?


Genel bir fikir vermek için kodunuzu makine dilinde nasıl temsil edilebileceği hakkında bazı yorumlar yapabilirim:


mov eax, 4


Bu komut, eax kaydedicisine 4 değerini yerleştirir. Makine dilinde, mov komutu genellikle kaynağı ve hedefi belirten belirli kodlarla temsil edilir. Örneğin, B8 04 00 00 00 gibi bir dizi bayt olabilir (bu sadece bir örnektir).

mov ebx, 1


Benzer şekilde, bu mov komutu ebx kaydedicisine 1 değerini atar. Makine dilinde bu, BB 01 00 00 00 gibi bir şey olabilir.

mov ecx, msg


Bu, ecx kaydedicisine msg değişkeninin adresini yükler. Makine dilindeki temsili, msg değişkeninin gerçek adresine bağlı olacaktır.

mov edx, len


edx kaydedicisine len değerini yerleştirir. Makine dilindeki temsili, len değerinin boyutuna bağlı olacaktır.

int 0x80


Bu, bir kesme çağrısıdır ve genellikle CD 80 gibi bir bayt dizisiyle temsil edilir.

xor ebx, ebx


Bu, ebx kaydedicisini sıfırlar. Makine dilinde, bu genellikle 31 DB gibi bir bayt dizisiyle temsil edilir.

Diğer mov ve int komutları


Benzer şekilde, bu komutlar da kendi makine dilindeki karşılıklarıyla temsil edilecektir.

Makine dilinde her bir komut, işlemcinin anlayabileceği spesifik bayt dizileriyle temsil edilir. Bu bayt dizileri, kullanılan işlemci mimarisine ve komutların belirli ayrıntılarına (kaynak, hedef, değerler vb.) bağlı olarak değişir. ​

Java'da String Pool Konsepti

Java'da String Pool Konsepti

    

    Java programlama dilinde String veri tipi, çift tırnak içerisine yazılmış karakterler dizisi olarak tanımlanır. Aşağıda en basit haliyle bir String tanımlaması yapılmıştır.

String name = "Muhammet";

    Yukarıdaki tanımlama String Literals olarak adlandırılmaktadır. String literals, bir programcının belirli bir değeri ifade etmek amacıyla yazdığı sabit karakter dizileridir.

    Yukarıda String türünde tanımlanan ve name olarak adlandırılan nesne elbette primitive tiplerden(int,byte,long vb.) farklı olarak Stack yerine Heap bölgesinde tutulmaktadır, dolayısıyla bir referansa sahiptir. Yani derleyici bu satırı çalıştırdığında Heap bölgesinde “Muhammet” değerini tutan bir obje oluşturur ve “name” isimli değişkene bunun referansını verir.

    Peki devam eden kısımda aşağıdaki gibi aynı değere sahip ikinci bir değişken tanımlaması yapsaydık bu iki nesnenin referansı aynı mı olurdu? Bunu anlamak için referanslarının hashCode değerlerini ekrana yazdıralım.

String name = "Muhammet";
String name2 = "Muhammet";

System.out.println(System.identityHashCode(name));
System.out.println(System.identityHashCode(name2));

//Output
//1705736037
//1705736037

    Evet! Görüldüğü üzere her iki nesnenin de referanslarının hashCode değerlerini ekrana yazdırdığımızda aynı değeri yazdığını görüyoruz. Yukarıdaki örnekte iki tane değil bir milyon tane değişken de tanımlasaydık bunların tamamının referansı aynı olacaktı.

    Buradan anlıyoruz ki biz bir milyon tane değişken tanımlasak da JVM bunlara karşılık Heap bölgesinde yalnızca bir tane obje tanımlıyor.

Peki Neden?

    Tabii ki amaç bellek optimizasyonu. Varsayılan olarak literals kullanarak bir String objesi oluşturduğunuzda JVM oluşturmak istediğiniz değerin varlığını String Pool yapısı içerisinde kontrol eder. Eğer burada zaten bu değer mevcut ise, yeni bir tane oluşturmak yerine geriye bu objenin referansını döner. Bu sayede tekrar eden verinin önüne geçilir.

Nedir bu String Pool?

    Makalemizin başlangıcındaki görselden görüldüğü üzere JVM Heap bellek kısmı içerisinde String Pool adı verilen bir alan vardır. Bu alan aynı zamanda String Constant Pool veya String Intern Pool olarak da adlandırılır.

    Siz bir String oluşturduğunuzda JVM varsayılan olarak önce bu alanı kontrol eder. Eğer bu alanda, oluşturduğunuz String değişken içerisindeki değer zaten daha önce tanımlanmış ise onun referansı değişkeninize atanır. Eğer öncesinde böyle bir değer String Pool içerisinde tanımlanmamış ise String Pool içerisinde bu değer tanımlanır ve değişkeninize atanır.

    Bundan sonra oluşturacağınız tüm String değişkenlerin değerleri bu alandan gelir veya bu alan içerisine tanımlanırlar. Bu sayede tekrar eden verinin önüne geçilmiş olur ve bellek kullanımı önemli ölçüde azaltılmış olur.

Peki String Pool Dışında String Tanımlamak İstersek?

    String Pool içerisinde işleri yürütmenin getirdiği bellek kullanımında tasarruf avantajının yanısıra her yeni String değişken oluşturduğunuzda bu alanın baştan sonra kontrol edilmesi gibi bir search işlemi gerçekleşir. Kimi zaman bellekten tasarruf etmek yerine buradaki işlem’in gecikmesinden, yükünden kaçınmak isteyebiliriz.

    Böyle durumlarda String Pool dışında, doğrudan Heap içerisinde String değişkenlerimizi tanımlamak, yönetmek isteyebiliriz. Bunun için tek yapmamız gereken tanımlama yaparken new anahtar kelimesini kullanmak.

String name = "Muhammet";
String name2 = "Muhammet";
String name3 = new String("Muhammet");

System.out.println(System.identityHashCode(name));
System.out.println(System.identityHashCode(name2));
System.out.println(System.identityHashCode(name3));

//Output
//1705736037
//1705736037
//455659002

    Yukarıdaki örnekte görüldüğü üzere new anahtar kelimesi kullanılarak String değişken tanımlandığında, farklı bir referansa sahip String Pool dışında tanımlamış oluyoruz.

String name = "Muhammet";
String name2 = "Muhammet";
String name3 = new String("Muhammet");

System.out.println(name == name2);
System.out.println(name == name3);
System.out.println(name.equals(name3));

//Output
//true
//false
//true

    Bu değişkenleri “==” operatörü kullanarak karşılaştırdığımızda, String Pool dışında olan ile içinde olan iki farklı değişken için sonuç false dönerken, String Pool içerisinde olan iki farklı değişken için sonuç true dönüyor.

    Bunun sebebi == operatörü kullanarak yapılan karşılaştırmalar değerler üzerinden değil referanslar üzerinden yapılır. Dolayısıyla String Pool içerisindeki aynı değere sahip değişkenlerin referansı aynı iken, new anahtar kelimesi ile tanımlanan değişkenin referansı farklı.

    Fakat referansları üzerinden değil değerleri üzerinden karşılaştırmak istersek de equals metodunu kullabiliriz.

intern() Fonksiyonu

String name4 = new String("Java");
String name5 = name4;

System.out.println(System.identityHashCode(name4));
System.out.println(System.identityHashCode(name5));

//Output
//250421012
//250421012

    Yine kodlar üzerinden ilerleyelim. Yukarıdaki örnekte name5 isimli değişkeni name4 değişkeninden türetiyoruz. Dolayısıyla referansları aynı. Fakat String Pool içerisine alınmıyorlar. Çünkü Heap üzerinde yaratıldı, Heap üzerinden referans alındı.

    Fakat bunun yerine oluşturacağımız yeni nesneyi String Pool içerisine koymak istiyor olabiliriz. Bu durumda .intern() fonksiyonu devreye giriyor.

String name4 = new String("Java");
String name5 = name4;
String name6 = name4.intern();
String name7 = "Java";

System.out.println(System.identityHashCode(name4));
System.out.println(System.identityHashCode(name5));
System.out.println(System.identityHashCode(name6));
System.out.println(System.identityHashCode(name7));

//Output
//250421012
//250421012
//1915318863
//1915318863

    Yukarıda “name6” isimli değişkeni oluştururken name4 değişkenini kullandık. Bu değişken String Pool’da değil, Heap üzerinde kendi başına bulunuyordu. Fakat bundan bir değişken oluştururken .intern() metodunu kullanarak oluşturduk. Bu metod sayesinde eğer String Pool’da böyle bir değer yoksa oluştur ve String Pool’a ekle demiş olduk.

    Devamında “Java” değerine sahip bir String değişkeni String literals kullanarak tanımladığımızda referansının String Pool’dan geldiğini görebiliyoruz.

Gelelim String Pool kavramının artı ve eksilerine;

Artılar

  • Performans. JVM yeni bir String objesi yaratmak yerine varolan üzerinden referans aldığı için String işlemleri daha hızlıdır.

  • Bellek Kullanımı. String Pool içerisinde bulunan tek bir String’i referansı üzerinden sürekli paylaştığınız için sürekli yeni String objeleri oluşturmaz ve bellek kullanımını önemli ölçüde azaltabilirsiniz.


Eksiler

  • Performans. Evet artılarda olan madde burada da var. Bunun sebebi yeni bir String değişken oluşturduğunuzda bellekte direkt olarak yenisini oluşturmak yerine, String Pool içerisinde arama işlemi yaptırmak zorunda kalırsınız. Böyle bir değer varsa yenisini oluşturmayalım derken eğer yüksek sayıda String objeleri ile çalışıyorsanız JVM kimi zaman koskoca bir havuzu taramak zorunda kalabilir. Ve bunu her seferinde yapacağı için eğer yüksek sayıda String objesi ile çalışıyorsanız String Pool yapısı size faydadan çok zarar getirebilir, dikkatli olmak zorundasınız.


Java PriorityQueue nedir? Örnek bir senaryo ile Java kodu



Java'da PriorityQueue sınıfı, bir öncelik kuyruğunu uygulayan bir koleksiyondur. Bu kuyruk, öğeleri doğal sıralamalarına göre veya bir Comparator aracılığıyla sağlanan bir sıralamaya göre saklar. PriorityQueue sınıfı, Java'nın java.util paketinin bir parçasıdır ve Queue arayüzünü uygular. Öncelik kuyruklarının(PriorityQueue) ana özellikleri şunlardır:

  • Sıralama: Öğeler, doğal sıralamalarına (örneğin, sayılar küçükten büyüğe) veya bir Comparator ile belirtilen bir sıraya göre saklanır.

  • Veri Yapısı: Genellikle bir ikili heap yapısı kullanılarak uygulanır, bu da hem ekleme (offer) hem de en yüksek öncelikli öğeyi çıkarma (poll) işlemlerinin verimli bir şekilde yapılmasını sağlar.

  • Ekleme ve Kaldırma: offer metodu, kuyruğa yeni bir öğe eklemek için kullanılır. poll metodu, kuyruktan en yüksek öncelikli öğeyi kaldırır ve döndürür. Eğer kuyruk boşsa, poll null değerini döndürür.

  • Görüntüleme: peek metodu, en yüksek öncelikli öğeyi kaldırmadan görüntülemek için kullanılır. Kuyruk boşsa, null değerini döndürür.

  • Boyut ve Kapasite: PriorityQueue başlangıçta belirli bir kapasite ile oluşturulabilir ve ihtiyaç duyuldukça otomatik olarak genişler.

  • İterasyon: PriorityQueue üzerinde iterasyon yapmak, öğeleri kesin bir sırayla döndürmez. Belirli bir sırayla elemanlara erişmek için öğeleri kuyruktan kaldırmak gerekir.

PriorityQueue sınıfı, özellikle performans açısından verimli bir şekilde öncelikli elemanlara erişim gerektiren durumlarda, örneğin görev planlayıcılarında veya minimum/maximum hesaplamalarında kullanışlıdır.

Örnek bir senaryo ile Java kodu

Senaryo: Hasta Sıralaması

Bir hastanede, acil servise gelen hastaların tedavi önceliğini belirlemek için bir öncelik kuyruğu kullanılıyor. Her hastanın bir "aciliyet derecesi" vardır. Bu derece ne kadar yüksekse, hastanın tedavi önceliği o kadar yüksek olur. Hastalar öncelik sırasına göre tedavi edilir.

Bu senaryoda, hastaları Patient adında bir sınıf ile temsil edeceğiz. Her Patient nesnesinin bir ismi ve bir aciliyet derecesi olacak. PriorityQueue sınıfını kullanarak hastaları aciliyet derecelerine göre sıralayacağız.


Kod:

java

Copy code

import java.util.PriorityQueue;

import java.util.Comparator;


// Hasta sınıfı

class Patient {

    String name;

    int urgency;

    public Patient(

        final String name,

        final int urgency

    ) {

        this.name = name;

        this.urgency = urgency;

    }


    @Override

    public String toString() {

        return name + " (Aciliyet Derecesi: " + urgency + ")";

    }

}

public class HospitalQueue {

    public static void main(String[] args) {

        // Öncelik kuyruğunu, aciliyet derecesine göre ters sıralama ile oluşturuyoruz

        PriorityQueue<Patient> queue = new PriorityQueue<>(Comparator.comparingInt(p -> -p.urgency));

        // Hastaları kuyruğa ekliyoruz

        queue.offer(new Patient("Ali", 5));

        queue.offer(new Patient("Ayşe", 3));

        queue.offer(new Patient("Ahmet", 10));

        queue.offer(new Patient("Fatma", 1));

      // Hastaları öncelik sırasına göre tedavi ediyoruz

        while (!queue.isEmpty()) {

            Patient nextPatient = queue.poll();

            System.out.println("Tedavi edilen             hasta: " + nextPatient);

        }

    }

}

Bu kodda, Patient sınıfı tanımlanır ve her hastanın ismi ve aciliyet derecesi saklanır. HospitalQueue sınıfının main metodunda, PriorityQueue sınıfından bir nesne oluşturulur ve Comparator ile hastaların aciliyet derecelerine göre sıralanması sağlanır. Daha sonra hastalar kuyruğa eklenir ve öncelik sırasına göre tedavi edilir. Bu sırada, en yüksek aciliyet derecesine sahip hasta öncelikle tedavi edilir.

queue.poll() edildiğinde ilk önce hangi hasta kuruktan çekilir?

Bu örnekte, queue.poll() çağrıldığında, PriorityQueue içindeki en yüksek aciliyet derecesine sahip hasta ilk olarak kuyruktan çekilir. PriorityQueue burada Comparator kullanılarak ters sıralama ile oluşturulduğu için, en yüksek aciliyet derecesine (yani en büyük sayıya) sahip hasta en önce çıkarılır.

Kodda tanımlanan hastalar ve aciliyet dereceleri şöyleydi:

Ali, Aciliyet Derecesi: 5

Ayşe, Aciliyet Derecesi: 3

Ahmet, Aciliyet Derecesi: 10

Fatma, Aciliyet Derecesi: 1

Bu durumda, queue.poll() ilk çağrıldığında, en yüksek aciliyet derecesine (10) sahip olan "Ahmet" isimli hasta kuyruktan çekilir.

HashMap,LinkedHashMap ve TreeMap çalışma yapısı



LinkedHashMap ve TreeMap, Java'da kullanılan iki farklı türde harita (map) yapısıdır ve farklı çalışma prensiplerine sahiptirler. İkisi arasındaki temel farklar şunlardır:


Sıralama ve İç Yapı:


LinkedHashMap:

LinkedHashMap, elemanları eklendiği sırayla saklar. Yani, bir anahtar-değer çifti eklediğinizde, bu çiftin sırası korunur.

İç yapısı, HashMap ile benzerdir ama ek olarak bir çift yönlü bağlı liste (doubly linked list) kullanarak sıralama bilgisini tutar.

TreeMap:

TreeMap, elemanları bir karşılaştırma mantığına göre sıralar. Varsayılan olarak doğal sıralama kullanılır (örneğin, sayısal veya alfabetik sıralama), ama özel bir Comparator da belirtilebilir.

İç yapısı kırmızı-siyah ağaç (red-black tree) temellidir. Bu, elemanların dengeli bir şekilde saklanmasını ve verimli arama/sıralama operasyonlarını sağlar.

Performans:


LinkedHashMap:

Ekleme, silme ve erişim işlemleri HashMap ile benzer hızdadır çünkü ana işlemler HashMap üzerinden yapılır.

İterasyon, eleman sayısıyla doğru orantılıdır çünkü elemanlar bağlı listede saklanır.

TreeMap:

Ekleme, silme ve arama işlemleri logaritmik zamanda (

O(logn)) gerçekleşir çünkü kırmızı-siyah ağaç yapısı kullanılır.

İterasyon, sıralı erişim gerektiğinde daha verimlidir.

Kullanım Senaryoları:


LinkedHashMap:

Sıralı erişim gereken ama sıralama kriteri eklenme sırası olan durumlar için uygundur.

Örneğin, bir önbellek (cache) yapısı olarak kullanılabilir, burada en eski elemanlar kolayca bulunup silinebilir.

TreeMap:

Doğal sıralamaya veya özel bir sıralama kriterine ihtiyaç duyulan durumlar için idealdir.

Örneğin, bir anahtar kümesinin sıralı tutulması gereken durumlarda kullanışlıdır.

Her iki yapı da Java Collections Framework'ün bir parçasıdır ve Map arayüzünü uygular. Hangisinin kullanılacağı, uygulamanın ihtiyaçlarına ve spesifik performans gereksinimlerine bağlıdır.


User

LinkedHashMap'de bağlı listede key'ler mi saklanır?

ChatGPT

Evet, LinkedHashMap'de bağlı listede anahtarlar (keys) saklanır. Ancak, bu sadece anahtarların saklandığı anlamına gelmez; aslında, LinkedHashMap'de her bir anahtar-değer çifti (entry), hem HashMap'in iç yapısında hem de içsel bir çift yönlü bağlı liste (doubly linked list) yapısında tutulur. Bu yapının amacı, elemanların ekleme sırasına göre sıralanmasını ve hızlı erişimini sağlamaktır.


İşleyişi şu şekildedir:


Her bir anahtar-değer çifti, HashMap'in hash tablosunda saklanır. Bu, anahtar üzerinden hızlı erişim ve arama operasyonlarını sağlar.


Aynı zamanda, her çift bir çift yönlü bağlı listeye de eklenir. Bu liste, elemanların eklendiği sırayı korur.


İterasyon yapılırken veya sıralı erişim gerektiğinde, bu çift yönlü bağlı liste kullanılır, böylece elemanlar eklendiği sırayla erişilebilir.


Bu yapı, LinkedHashMap'in hem hızlı anahtar bazlı erişim sağlamasını hem de elemanların eklendiği sırayı korumasını mümkün kılar. Bu özellik, özellikle belirli türde önbellekleme sistemleri veya sıra önemli olan veri yapıları için yararlıdır.


Yani hem hah table hem de bağlı listede ayrı auyrı tutuyor


Evet, doğru. LinkedHashMap hem hash tablosu hem de çift yönlü bağlı liste (doubly linked list) kullanarak anahtar-değer çiftlerini (entries) saklar. Bu iki yapı birlikte çalışarak LinkedHashMap'in benzersiz işlevselliğini oluşturur:


Hash Tablosu:


Hash tablosu, HashMap ile aynı prensiple çalışır.

Anahtarların hash kodları hesaplanır ve bu kodlara göre anahtar-değer çiftleri tabloda saklanır.

Bu yapı, anahtarlar üzerinden hızlı erişim (arama, ekleme, silme) sağlar.

Çift Yönlü Bağlı Liste:


Her anahtar-değer çifti, ayrıca bir çift yönlü bağlı listede de tutulur.

Bu liste, elemanların eklendiği ya da son erişildiği sıraya göre düzenlenir (bu, LinkedHashMap'in oluşturulma şekline bağlıdır).

Liste, iterasyon sırasında sıralı erişim için kullanılır, böylece elemanlar ekleme sırasına göre döndürülür.

Bu iki yapı bir arada, LinkedHashMap'e hem hızlı anahtar bazlı erişim hem de elemanların sıralı tutulmasını sağlama yeteneği kazandırır. Bu, LinkedHashMap'i özellikle önbellekleme gibi sıralı erişimin önemli olduğu uygulamalar için uygun bir seçim yapar.


HashMap'de bağlı liste kullanılır mı?


HashMap'de bağlı liste kullanılır, ancak bu LinkedHashMap'deki kullanımından farklı bir amaç için kullanılır. HashMap'in iç yapısında, çakışmaları (collisions) yönetmek için bağlı listeler kullanılır. İşte bu kullanımın ayrıntıları:


Hash Çakışmaları ve Bağlı Listeler:


HashMap bir anahtarı hash koduna dönüştürerek, bu kodu kullanarak anahtar-değer çiftini bir dizinde saklar.

Farklı anahtarlar aynı hash kodunu üretebilir (bu duruma "hash çakışması" denir). Böyle bir durumda, HashMap aynı dizin içindeki elemanları bir bağlı liste şeklinde saklar.

Bu bağlı liste, çakışan tüm anahtar-değer çiftlerini birbirine bağlar, böylece aynı hash koduna sahip çiftler aynı "kova" (bucket) içinde yönetilebilir.

Java 8'den İtibaren Geliştirmeler:


Java 8 ile birlikte, HashMap'in bu yapısında bir iyileştirme yapıldı. Eğer bir kovadaki eleman sayısı belirli bir eşiği aşarsa, bağlı liste yapısı, daha verimli aramalar sağlayan dengeli bir ağaca (red-black tree) dönüştürülür.

Bu değişiklik, büyük ve yoğun HashMap yapılarında performansı önemli ölçüde iyileştirmektedir.

HashMap ve LinkedHashMap arasındaki önemli bir fark, LinkedHashMap'in elemanları eklenme sırasına göre saklaması ve iterasyon sırasında bu sırayı korumasıdır. Buna karşın, HashMap'de elemanlar sırasız bir şekilde saklanır ve iterasyon sırası öngörülemezdir. Bu farklılıklar, her iki yapının farklı kullanım senaryolarına uygun olmasını sağlar.

Java'da ExecutorService ile insanmış gibi bir röportaj yapmış olsaydım, röportaj nasıl olurdu?





Java'da ExecutorService kullanarak bir röportaj yapma fikri oldukça ilginç. Böyle bir röportaj, ExecutorService'in çoklu görev yürütme yeteneklerini kullanarak, bir dizi soruyu paralel olarak işleyebilir. İşte böyle bir senaryoda olası bir röportaj örneği:


Röportajcı: Merhaba, ExecutorService'i kullanarak bu röportajı yapacağız. İlk sorum, Java'daki ExecutorService nedir?


ExecutorService: Merhaba! ExecutorService, Java'da eş zamanlı görev yürütme için bir çerçeve sağlar. Görevleri (Runnable veya Callable olarak) yürütebilen ve yönetebilen bir thread havuzu sağlar.


Röportajcı: Peki, ExecutorService'in avantajları nelerdir?


ExecutorService: ExecutorService, görev yönetimini kolaylaştırır, kaynak kullanımını optimize eder ve çoklu görev işleme kapasitesini artırır. Ayrıca, kodun daha okunabilir ve bakımının daha kolay olmasını sağlar.


Röportajcı: Java'da ExecutorService nasıl kullanılır?


ExecutorService: ExecutorService kullanımı genellikle Executors fabrika sınıfı aracılığıyla bir executor oluşturmakla başlar. Sonra, submit() metodunu kullanarak görevleri (Runnable veya Callable) yürütebilirsiniz. İşlemler tamamlandığında, shutdown() metodunu kullanarak kaynakları serbest bırakırsınız.


Röportajcı: Son olarak, ExecutorService kullanırken yaygın yapılan hatalar nelerdir?


ExecutorService: En yaygın hatalardan biri, kaynakları düzgün bir şekilde serbest bırakmamaktır. shutdown() veya shutdownNow() metodlarını çağırmayı unutmak, bellek sızıntılarına ve performans sorunlarına yol açabilir. Ayrıca, çok fazla thread oluşturmak da sistem kaynaklarını gereksiz yere tüketebilir.


Röportajcı: Çok iyi. Şimdi, ExecutorService ile ilgili biraz daha derine inelim. Örneğin, Callable ve Runnable arasındaki fark nedir?


ExecutorService: Runnable ve Callable her ikisi de yürütülebilir görevleri temsil eder, ancak ana fark, Callable'ın bir sonuç döndürebilmesi ve istisna fırlatabilmesidir. Runnable'ın run() metodu bir değer döndürmez ve istisnaları işlemez.


Röportajcı: Peki ya Future nesnesi? Bu terim ExecutorService bağlamında nasıl kullanılır?


ExecutorService: Future nesnesi, asenkron bir görevin sonucunu temsil eder. ExecutorService'in submit() metodu, bir Future döndürür. Bu Future, görevin tamamlanıp tamamlanmadığını kontrol etmek, görevin sonucunu almak ve gerekirse görevi iptal etmek için kullanılabilir.


Röportajcı: Anladım. Son olarak, ExecutorService'in ölçeklenebilirliği ve performansı hakkında ne söyleyebilirsiniz?


ExecutorService: ExecutorService, özellikle thread havuzları ile çok ölçeklenebilirdir. Executors sınıfı, farklı türlerde executor havuzları oluşturmanıza olanak tanır. Örneğin, newFixedThreadPool sabit sayıda thread içerirken, newCachedThreadPool gerektiğinde thread oluşturur ve ihtiyaç duyulmadığında onları kapatır. Bu, uygulamanızın ihtiyaçlarına bağlı olarak kaynak kullanımını optimize etmenize yardımcı olur.



Kod örnekleri ile röportaja devam et


Elbette, kod örnekleriyle röportaja devam edelim:


Röportajcı: Harika! Şimdi, bir ExecutorService oluşturmak için basit bir Java kodu örneği gösterebilir misiniz?


ExecutorService:


java

Copy code

ExecutorService executor = Executors.newFixedThreadPool(4); // 4 thread'lik bir havuz oluşturuluyor.

Röportajcı: Peki, bu executor'a bir Runnable görevi nasıl ekleyebiliriz?


ExecutorService:


java

Copy code

executor.submit(() -> {

    System.out.println("Runnable görev çalıştırılıyor.");

});

Röportajcı: Callable görevlerle nasıl çalışılır?


ExecutorService:


java

Copy code

Future<String> future = executor.submit(() -> {

    return "Callable'dan dönen sonuç.";

});


try {

    String sonuc = future.get(); // Görevin tamamlanmasını bekler ve sonucu alır.

    System.out.println("Callable sonucu: " + sonuc);

} catch (InterruptedException | ExecutionException e) {

    e.printStackTrace();

}

Röportajcı: Executor'u nasıl düzgün bir şekilde kapatırsınız?


ExecutorService:


java

Copy code

executor.shutdown(); // Görevlerin tamamlanmasını bekler.

try {

    if (!executor.awaitTermination(800, TimeUnit.MILLISECONDS)) {

        executor.shutdownNow();

    }

} catch (InterruptedException e) {

    executor.shutdownNow();

}

Röportajcı: Son olarak, ExecutorService kullanırken dikkat edilmesi gereken birkaç iyi uygulama kuralı var mı?


ExecutorService: Evet, öncelikle kaynak yönetimine dikkat edin. Executor'u kullanmayı bitirdiğinizde mutlaka kapatın. Ayrıca, iş yükünüze uygun thread havuzu boyutunu seçmeye özen gösterin. Çok fazla thread, bellek ve CPU üzerinde gereksiz yük oluşturabilir. Ve son olarak, Callable ve Future kullanarak görevlerinizi doğru şekilde yönetin, böylece asenkron görevlerden en iyi şekilde yararlanabilirsiniz.


Röportajcı: Bu bilgiler ve örnekler için teşekkürler! Java'nın eş zamanlılık araçları hakkında çok daha iyi bir fikir edindim.


Bu kod örnekleri ve açıklamalar, ExecutorService'in temel kullanımını gösteriyor ve Java'da eş zamanlı programlamayı anlamak için iyi bir başlangıç noktası oluşturuyor.




Thread Pool Nedir?

Thread pool, sınırlı sayıda ve önceden oluşturulmuş iş parçacıklarını içeren bir havuzdur. Bu havuz, görevleri sırayla işlemek için bu iş parçacıklarını yeniden kullanır. Thread pool kullanmanın temel amacı, her görev için yeni bir iş parçacığı oluşturmanın getirdiği maliyeti ve karmaşıklığı azaltmaktır.


Avantajları

Performans Artışı: İş parçacığı oluşturmanın ve yok etmenin maliyeti yüksektir. Thread pool, bu maliyeti azaltarak uygulamanın genel performansını artırır.

Kaynak Kullanımının Optimizasyonu: Sınırlı sayıda iş parçacığı kullanarak, sistem kaynaklarının aşırı tüketilmesini önler.

Daha İyi Görev Yönetimi: Görevler, havuzdaki iş parçacıkları arasında etkili bir şekilde dağıtılır, böylece görev yükü dengelenir.

Yüksek Tepki Süresi: Yeni bir görev geldiğinde, havuzda zaten mevcut olan bir iş parçacığı hemen bu görevi üstlenebilir.

Java'da Thread Pool Kullanımı

Java'da thread pool oluşturmak ve kullanmak için java.util.concurrent paketindeki Executor çerçevesi kullanılır. ExecutorService arayüzü, iş parçacığı havuzunu yönetmek için çeşitli metotlar sağlar. En yaygın kullanılan thread pool sınıfları şunlardır:


Executors.newFixedThreadPool(int): Sabit sayıda iş parçacığı içeren bir havuz oluşturur.

Executors.newCachedThreadPool(): Gerektiğinde yeni iş parçacıkları oluşturabilen ve kullanılmayan iş parçacıklarını otomatik olarak yok edebilen esnek bir havuz oluşturur.

Executors.newSingleThreadExecutor(): Yalnızca tek bir iş parçacığı içeren bir havuz oluşturur.

Örnek Kullanım

java

Copy code

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;


public class ThreadPoolExample {

    public static void main(String[] args) {

        // 5 iş parçacığına sahip bir thread pool oluştur

        ExecutorService executor = Executors.newFixedThreadPool(5);


        // Görevleri havuza ekle

        for (int i = 0; i < 10; i++) {

            Runnable worker = new WorkerThread("" + i);

            executor.execute(worker);

        }


        // İş parçacıklarını kapat

        executor.shutdown();

        while (!executor.isTerminated()) {

            // Tüm görevlerin tamamlanmasını bekle

        }


        System.out.println("Tüm görevler tamamlandı");

    }

}


class WorkerThread implements Runnable {

    private String command;


    public WorkerThread(String s){

        this.command = s;

    }


    @Override

    public void run() {

        System.out.println(Thread.currentThread().getName()+" Başlatıldı. Komut = " + command);

        processCommand();

        System.out.println(Thread.currentThread().getName()+" Bitirildi.");

    }


    private void processCommand() {

        try {

            Thread.sleep(5000);

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

    }


    @Override

    public String toString(){

        return this.command;

    }

}

Bu kod örneğinde, 5 iş parçacığı içeren bir thread pool oluşturulur ve bu havuz 10 görevi sırayla işler. Her görev, WorkerThread sınıfının bir örneği olarak tanımlanır ve execute() metodu ile havuza eklenir.


Java'nın ExecutorService arayüzünde submit ve execute metotları iş parçacığı havuzuna görev eklemek için kullanılır, ancak aralarında önemli farklar vardır:


execute Metodu

execute metodu, Runnable nesneleri kabul eder ve geriye bir şey dönmez (void).

execute, gönderilen görevin tamamlanmasını takip etmek için herhangi bir mekanizma sunmaz.

Genellikle görevin tamamlanmasını takip etmek veya sonucunu almak gerekmediğinde kullanılır.

submit Metodu

submit metodu, hem Runnable hem de Callable nesnelerini kabul eder.

submit metodu, Future tipinde bir nesne döndürür. Bu Future nesnesi, görevin tamamlanma durumunu takip etmeye ve görevin sonucunu almak için kullanılabilir. Callable görevleri için, bu sonuç görevin dönüş değeridir.

Genellikle görevin tamamlanma durumunu izlemek veya görevin sonucunu almak istediğiniz durumlarda kullanılır.

Rastgele İçerik

DonanımHaber

© tüm hakları saklıdır
made with by templateszoo