JAVA的类的使用

BigInteger

Java 中的 BigInteger 类提供了对任意精度整数的支持,可以进行大数运算。下面简单介绍一下如何使用 BigInteger 类。

导入 BigInteger 类

要使用 BigInteger 类,首先需要导入 java.math.BigInteger 包,例如:

import java.math.BigInteger;

创建 BigInteger 对象

可以使用以下两种方法来创建 BigInteger 对象:

  1. 使用一个整数值创建 BigInteger 对象
BigInteger bigInteger = BigInteger.valueOf(100);
  1. 使用一个字符串值创建 BigInteger 对象
BigInteger bigInteger = new BigInteger("12345678901234567890");

进行运算操作

BigInteger 类提供了一系列方法来进行运算操作,包括加减乘除、取模、取幂、取反等操作,这些方法都返回一个新的 BigInteger 对象,不会修改原始对象。例如:

BigInteger a = new BigInteger("12345678901234567890");
BigInteger b = new BigInteger("98765432109876543210");
BigInteger c = a.add(b); // 加法
BigInteger d = a.multiply(b); // 乘法
BigInteger e = a.mod(b); // 取模

需要注意的是,由于 BigInteger 类使用了大量的内存和 CPU 时间来支持任意精度运算,因此在进行大量运算时需要考虑到性能问题。

转换为基本类型

可以使用 BigInteger 类的 intValue、longValue、floatValue 和 doubleValue 方法将 BigInteger 对象转换为基本类型。例如:

BigInteger a = new BigInteger("12345678901234567890");
int b = a.intValue();
long c = a.longValue();
float d = a.floatValue();
double e = a.doubleValue();

转换为String类型

将 BigInteger 对象转换为 String 类型,可以使用 BigInteger 类的 toString() 方法,该方法返回表示 BigInteger 值的字符串。

BigInteger bigInteger = new BigInteger("12345678901234567890");
String str = bigInteger.toString();

此外,BigInteger 类还提供了一系列重载的 toString() 方法,可以指定进制、符号等参数,来满足不同的需求。例如:

BigInteger bigInteger = new BigInteger("12345678901234567890");
String str1 = bigInteger.toString(16); // 将 BigInteger 转换为 16 进制字符串
String str2 = bigInteger.negate().toString(); // 将 BigInteger 取负后转换为字符串

ArrayList和LinkedList

Java中的ArrayList和LinkedList都是List接口的实现类,它们都可以用来存储一组元素,但它们在实现上有一些不同:

内部数据结构不同
ArrayList内部使用动态数组来实现,也就是说它的底层是一个数组。当元素数量超过当前容量时,会创建一个新的更大的数组,并将原数组中的元素拷贝到新数组中。因此,ArrayList适合随机访问和遍历,但不适合频繁的插入和删除操作。

LinkedList内部使用双向链表来实现,每个节点都包含了元素和指向前驱和后继节点的指针。因此,LinkedList适合频繁的插入和删除操作,但不适合随机访问和遍历。

插入和删除操作的效率不同
ArrayList的插入和删除操作需要移动数组中的元素,如果插入或删除的位置比较靠前,那么需要移动的元素就比较多,效率就比较低。

LinkedList的插入和删除操作只需要改变节点的指针,效率比较高。

随机访问的效率不同
ArrayList支持随机访问,因为它的底层是一个数组,可以通过索引直接访问元素,因此时间复杂度为O(1)。

LinkedList不支持随机访问,只能从头结点或尾结点开始遍历,因此时间复杂度为O(n)。

综上所述,如果需要频繁地进行插入和删除操作,应该使用LinkedList;如果需要频繁地进行随机访问操作,应该使用ArrayList。

他们都不是线程安全类

ArrayList和LinkedList都不是线程安全的,这意味着它们不能保证在多线程环境下的安全性。如果多个线程同时对一个ArrayList或LinkedList进行操作,可能会导致数据不一致的问题。

如果需要在多线程环境下使用ArrayList或LinkedList,可以考虑使用线程安全的类,比如Vector(线程安全的动态数组)和ConcurrentLinkedDeque(线程安全的双向链表)。这些类都是Java提供的线程安全的集合类,可以保证在多线程环境下的安全性。

HashMap & HashTable

HashMap

在 Java 中,HashMap 是一种基于哈希表的实现的键值对映射。它使用哈希函数将键映射到存储桶中,并使用链表或红黑树来处理哈希冲突,以便能够高效地进行插入、查找和删除操作。
HashMap 的实现主要由以下几部分组成:

  1. 数组:HashMap 内部使用一个数组来存储键值对,每个数组元素称为“桶”(Bucket)。HashMap 使用哈希函数将键映射到一个桶的索引上。
  2. 链表/红黑树:当多个键映射到同一个桶时,它们被存储在一个链表或红黑树中,以便能够处理哈希冲突。Java 8 中引入了红黑树,用于优化处理冲突的效率。
  3. 键值对:HashMap 中存储的每个元素都是一个键值对对象,它包含键和值两个属性。
  4. 哈希函数:HashMap 使用哈希函数将键映射到一个桶的索引上,Java 中的哈希函数通常是通过将键的哈希码和数组长度进行计算得出的。
  5. 负载因子和阈值:HashMap 使用负载因子和阈值来控制它的容量。当元素数量超过阈值时,HashMap 会自动进行扩容以避免哈希冲突过多,同时保证负载因子不超过指定的阈值。

由于 HashMap 内部采用了哈希表的实现方式,因此它具有快速的插入、查找和删除操作,时间复杂度为 O(1)。但是,它的性能受到哈希函数和负载因子的影响,因此在使用时需要考虑它们的设置。此外,HashMap 不保证元素的顺序,因为元素在哈希表中的位置是由哈希函数决定的,而不是由插入顺序决定的。

HashTable

HashTable是Java中的一个哈希表数据结构,它继承自Dictionary类,实现了Map接口,提供了一种将键映射到值的方式。Hashtable使用了哈希表算法,可以快速查找数据,插入和删除操作的时间复杂度都是常数级别的。
Hashtable中的键和值都不能为null,如果插入了null的键或值,会抛出NullPointerException异常。Hashtable的实现是线程安全的,其方法都是同步的,所以在多线程环境下可以保证数据的一致性和安全性。但是这也导致了Hashtable在性能上较HashMap差,因为在多线程环境下,需要进行同步操作,而同步操作会增加一定的开销。
Hashtable的底层实现是一个数组,数组中的每个元素都是一个链表,链表中存储了哈希值相同的键值对。Hashtable使用键的哈希值来计算该键在数组中的位置,不同的键可能会有相同的哈希值,这种情况称为哈希冲突。为了解决哈希冲突,Hashtable使用了链表的方式将哈希值相同的键值对串在一起,形成一个链表。当查找某个键值对时,首先计算该键的哈希值,然后找到该哈希值在数组中对应的位置,最后在该位置的链表中查找该键值对。
Hashtable支持put、get、remove等基本操作,还支持元素遍历、清空等操作。由于Hashtable是线程安全的,所以在多线程环境下可以使用它来保证数据的一致性和安全性。但是在单线程环境下,由于需要进行同步操作,所以Hashtable的性能相对较差,不如HashMap。

存储方式

HashMap和HashTable都是使用数组来存储键值对的,每个数组元素都是一个链表或红黑树,用于存储具有相同哈希码的键值对。当哈希冲突发生时,会将键值对插入到相应的链表或红黑树中,以实现快速的键值对查找。

在HashMap和HashTable的实现中,当数组中的链表或红黑树元素过多时,为了提高性能,会进行扩容操作,即创建一个新的更大的数组,并将原数组中的元素重新散列到新数组中。在扩容时,为了尽可能减少哈希冲突,一般将新数组的大小设置为原数组大小的两倍或更多。

需要注意的是,当链表或红黑树中元素数量过多时,查找键值对的效率会降低,因此在使用HashMap和HashTable时,需要根据实际情况调整数组大小和负载因子,以平衡查找效率和空间利用率。

为什么要将链表转换为红黑树?

在数据量大的情况下,HashMap 内部使用链表进行散列冲突处理可能会导致链表过长,查找元素的时间复杂度会退化为 O(n),其中 n 是链表的长度。

为了避免这种情况,Java 8 引入了一种优化:当链表长度超过阈值(默认为 8)时,会将链表转换为红黑树。红黑树的平均查找时间为 O(log n),相比于链表的 O(n) 更快。

红黑树的高效性质主要是由于其自平衡的特性,可以保证在最坏情况下,树的深度不超过 2log(n+1),即在 n 个元素中查找元素的时间复杂度最多为 O(log n)。

不过,值得注意的是,红黑树相对于链表来说也存在一些额外的空间开销和时间开销,因此转换链表为红黑树的代价也是存在的。在实际使用中,需要根据具体情况和性能需求进行权衡和选择。

ConcourrentHashMap

ConcurrentHashMap和HashMap在实现上有很大的不同,主要有以下几点区别:

线程安全性:ConcurrentHashMap是线程安全的,而HashMap不是。在并发情况下,多个线程同时访问HashMap可能会导致数据不一致的问题,而ConcurrentHashMap可以安全地支持多线程并发访问。

容量扩展:当ConcurrentHashMap的容量不足时,会自动进行扩展,而不会像HashMap那样重新创建一个新的数组来复制原有数据。这是因为ConcurrentHashMap的内部结构中每个Segment都有自己的数组,当容量不足时只需要扩展相应Segment的数组即可。

迭代器:ConcurrentHashMap的迭代器是弱一致性的,即可以允许在迭代期间修改ConcurrentHashMap的结构,但不能保证迭代器返回的结果一定是最新的。而HashMap的迭代器是快速失败的,如果在迭代期间发现了其他线程对HashMap的结构进行了修改,就会抛出ConcurrentModificationException异常。

ConcurrentHashMap能够实现线程安全主要是因为采用了分段锁的机制,这样不同的线程可以同时访问不同的Segment,大大提高了并发访问效率。同时,ConcurrentHashMap的实现也对锁的粒度进行了优化,只锁定了一部分数据,而不是整个Map,这也可以避免锁的争用和等待。此外,ConcurrentHashMap的内部结构也针对多线程并发访问做了优化,能够提高并发访问的效率和安全性。

并发安全的实现方式

分段锁机制
ConcurrentHashMap中的内部结构被划分为了多个Segment,每个Segment都是一个独立的HashMap,其中每个HashMap都被加锁来保证并发访问的安全。这样,不同的线程可以同时访问不同的Segment,从而减少了锁的竞争,提高了并发访问的效率。

volatile关键字的使用
在ConcurrentHashMap中,每个Segment都被定义为一个内部类,其中的成员变量和方法都是通过volatile关键字来修饰的。这样做是为了确保各个线程对Segment的操作是可见的,从而避免出现线程不一致的问题。

CAS操作
ConcurrentHashMap中的put()和remove()等操作,都是通过原子操作CAS(Compare and Swap)来实现的。在并发情况下,CAS可以避免多个线程同时修改同一位置的数据,从而保证并发访问的正确性。

自适应并发级别
ConcurrentHashMap的内部结构中包含一个表示并发级别的参数concurrencyLevel,这个参数可以根据实际的并发情况进行自适应调整。具体地,当ConcurrentHashMap中的线程数量大于等于concurrencyLevel时,会对内部结构进行扩容,以提高并发访问的效率。

Segment示例代码

当创建ConcurrentHashMap实例时,ConcurrentHashMap内部会创建若干个Segment实例,每个Segment维护了一部分HashMap中的键值对,同时Segment内部的成员变量都是用volatile关键字修饰的。以下是一个简化版的Segment实例代码:

class Segment<K,V> extends ReentrantLock implements Serializable {
    private static final long serialVersionUID = 2249069246763182397L;

    transient volatile int count;

    transient Node<K,V>[] table;

    // 将指定的键值对添加到Segment中
    final V put(K key, int hash, V value, boolean onlyIfAbsent) {
        // ...
    }

    // 根据指定的键值移除对应的元素
    final V remove(Object key, int hash, Object value) {
        // ...
    }

    // 返回指定键的对应值,若不存在返回null
    final V get(Object key, int hash) {
        // ...
    }

    // 判断Segment中是否包含指定键的元素
    final boolean containsKey(Object key, int hash) {
        // ...
    }

    // 遍历Segment中所有的元素,并执行指定的操作
    final void forEach(ConcurrentHashMap.BiAction<? super K, ? super V> action) {
        // ...
    }
}

从上面的代码可以看出,每个Segment维护了一个count计数器,用于记录Segment中键值对的数量。同时,Segment中维护了一个Node数组table,用于存储键值对的具体信息。

在put()、remove()、get()等操作中,Segment会根据键的哈希值来定位对应的Node节点,然后在该Node节点上执行对应的操作。需要注意的是,在每个操作的过程中,Segment都会对自己加锁,从而避免出现多线程并发访问的问题。这样一来,不同的线程就可以同时访问不同的Segment,从而实现高效的并发访问。

当然,上面的代码只是一个简化版的Segment实例,实际上ConcurrentHashMap的Segment内部结构比较复杂,涉及到了各种优化技巧和算法实现,具体的实现细节还需要根据不同版本的JDK源码进行分析。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值