Strings & Primitives¶
Primitive Data Types¶
https://www.programiz.com/java-programming/variables-primitive-data-types https://www.baeldung.com/java-primitives#:\~:text=2.-,Primitive%20Data%20Types,objects%20and%20represent%20raw%20values. https://www.geeksforgeeks.org/data-types-in-java/ https://medium.com/@AlexanderObregon/java-data-types-primitive-vs-non-primitive-417925cee746
| Type | Size (bits) | Minimum | Maximum | Range | Example | Use Case |
|---|---|---|---|---|---|---|
| byte | 8 | -27 | 27– 1 | -128 to 127 | byte b = 100; | Very small integers, memory-efficient in arrays |
| short | 16 | -215 | 215– 1 | 32,768 to 32,767 | short s = 30_000; | Small integers, more memory-efficient than int |
| int | 32 | -231 | 231– 1 | -2,147,483,648 to 2,147,483,647 | int i = 100_000_000; | Default integer type |
| long | 64 | -263 | 263– 1 | -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 | long l = 100_000_000_000_000; | Large integer values |
| float | 32 | -2-149 | (2-2-23)·2127 | Sufficient for storing 6 to 7 decimal digitsIEEE 754 floating-point | float f = 1.456f; | Fractional numbers, less precise than double |
| double | 64 | -2-1074 | (2-2-52)·21023 | Sufficient for storing 15 to 16 decimal digitsIEEE 754 floating-point | double f = 1.456789012345678; | Default for decimal values, high precision |
| char | 16 | 0 | 216– 1 | 0 to 65,535 a single character/letter or ASCII values | char c = ‘c’; | Single Unicode characters |
| boolean | 1 | – | – | true or false values | boolean b = true; | Logical values for conditions and flags |
Integer Implementation¶
In Java, the int data type is a primitive type that represents a 32-bit signed two's complement integer. This means that each int variable occupies 4 bytes (32 bits) of memory. One bit is used to represent the sign of the integer (positive or negative), and the remaining 31 bits are used to represent the magnitude of the integer.
Java also provides the Integer class, which is a wrapper class for the primitive int type. Integer objects are stored on the heap and have additional overhead compared to primitive int variables. Each Integer object typically occupies 16 bytes of memory, which includes the object header, the int value, and some padding.
Double Implementation¶
In Java, the double data type is a primitive type that represents a double-precision 64-bit IEEE 754 floating-point number. It is used to store decimal numbers with a higher degree of precision compared to the float data type.
When a double variable is declared, the Java Virtual Machine (JVM) allocates 64 bits (8 bytes) of memory to store its value. This memory is typically allocated on the heap, which is the area of memory used for dynamic memory allocation in Java.
The 64 bits are divided into three parts: Sign bit (1 bit): Indicates whether the number is positive or negative. Exponent bits (11 bits): Represents the exponent of the number. Significand bits (52 bits): Represents the fractional part of the number.
The value of a double is calculated as follows:
value = (-1)^sign * 2^(exponent - bias) * (1 + significand)
where: sign is the sign bit (0 for positive, 1 for negative). exponent is the value of the exponent bits. bias is a constant value (1023 for double). significand is the value of the significand bits, normalized to be between 0 and 1.
The double data type can represent a wide range of values, from approximately 4.9e-324 to 1.7e+308, with a precision of about 15 decimal digits.
When performing operations on double values, the JVM uses floating-point arithmetic, which can sometimes result in rounding errors. This is because not all decimal numbers can be represented exactly in binary.
Integer Pool¶
To optimize memory usage, Java uses an Integer Pool (or cache) for Integer objects with values in the range of -128 to 127. When an Integer object is created within this range using autoboxing or the valueOf() method, the JVM reuses existing objects from the pool instead of creating new ones. This can significantly reduce memory consumption when working with frequently used integer values.
Integer a = 100; Integer b = 100; System.out.println(a == b); // Output: true (same object from the pool)
Integer c = 200; Integer d = 200; System.out.println(c == d); // Output: false (different objects)
When an int variable is declared, the memory is allocated directly on the stack. When an Integer object is created using the new keyword, the memory is allocated on the heap. The garbage collector automatically reclaims memory occupied by Integer objects that are no longer in use.
String Pool¶
- The string pool in Java is a special area of memory within the heap that stores unique string literals. When you create a string using string literals (e.g., "hello"), Java first checks if an identical string already exists in the string pool. If it does, Java reuses the existing string from the pool instead of creating a new one. This helps conserve memory and improve performance by reducing the number of duplicate strings.
- String literals in Java are automatically interned, meaning they are added to the string pool by default. Additionally, you can explicitly intern strings using the intern() method, which adds the string to the pool if it's not already present and returns a reference to the canonical representation. Eg: String s1 = "hello"; // Added to the string pool String s2 = "hello"; // Reuses the existing string from the pool
System.out.println(s1 == s2); // Output: true (same reference)
String s3 = new String("hello"); // Creates a new string object in the heap String s4 = s3.intern(); // Adds s3 to the string pool and returns a reference to it
System.out.println(s1 == s3); // Output: false (different references) System.out.println(s1 == s4); // Output: true (s4 references the interned string from the pool)
Data Structures¶
Difference Between String, StringBuilder, and StringBuffer¶
https://www.geeksforgeeks.org/string-vs-stringbuilder-vs-stringbuffer-in-java/ https://medium.com/@salvipriya97/string-vs-stringbuilder-vs-stringbuffer-which-one-to-choose-4308dbcc3022 https://www.digitalocean.com/community/tutorials/string-vs-stringbuffer-vs-stringbuilder
| Feature | String | StringBuilder | StringBuffer |
|---|---|---|---|
| Introduction | Introduced in JDK 1.0 | Introduced in JDK 1.5 | Introduced in JDK 1.0 |
| Mutability | Immutable | Mutable | Mutable |
| Thread Safety | Thread Safe | Not Thread Safe | Thread Safe |
| Memory Efficiency | High | Efficient | Less Efficient |
| Performance | High(No-Synchronization) | High(No-Synchronization) | Low(Due to Synchronization) |
| Usage | This is used when we want immutability. | This is used when Thread safety is not required. | This is used when Thread safety is required. |
| Pros | Thread-safe (immutable), making it suitable for use in multi-threaded environments. Predictable behavior in terms of memory usage. | Mutable, making it suitable for building or modifying strings dynamically. Efficient for concatenating or manipulating strings in a loop or when dealing with frequent modifications. | Mutable and thread-safe, making it suitable for multi-threaded applications. Efficient for concatenating or modifying strings when thread safety is required. |
| Cons | Inefficient for frequent string manipulation, as it creates new objects with each modification, leading to performance overhead. | Not thread-safe. If used in a multi-threaded environment, additional synchronization is required (use StringBuffer for thread safety). | Slightly less efficient than StringBuilder due to thread safety overhead. It's generally recommended for multi-threaded scenarios. |
Which to Choose:
- If you need to manipulate strings dynamically and thread safety is not a concern, StringBuilder is the preferred choice due to its efficiency.
- If you require thread safety, especially in a multi-threaded environment, use StringBuffer.
- Use String when you need an immutable string, and you don't anticipate frequent modifications.