String
String代表字符串对象,可以用来封装字符串数据,并提供了很多操作字符串的方法。
String是Java中用于表示文本字符串的类,它是Java标准库(java.lang包)中一个不可变的、线程安全的字符序列。
特性
1. 不可变(Immutable):一旦创建了一个String对象,其内容就不能被修改。对String对象的任何修改操作(如拼接、替换、截取等)都会返回一个新的String对象,而原始对象保持不变。这种特性使得String对象在多线程环境下安全且易于缓存。
2. 字符序列:String对象包含一个字符数组(char[]),并提供了一系列方法来访问和操作其中的字符。
3. UTF-16编码:String中的字符采用Unicode编码,具体实现为UTF-16编码,每个字符占用2个字节(或4个字节,对于增补字符)。
创建String对象的方式
1. 字面量:直接使用双引号("")包围的文本创建String对象,如 String str = "Hello, World!";。
2. 构造函数:使用String类的构造函数创建对象,如 String str = new String("Hello, World!");。
3. 字符串串联:使用+或StringBuilder/StringBuffer的append()方法将多个字符串或字符拼接成一个新String对象。
常用方法
•获取信息:
•length():返回字符串的长度(字符数)。
•charAt(int index):返回指定索引处的字符。
•isEmpty():判断字符串是否为空。
•equals(Object anObject) / equalsIgnoreCase(String anotherString):比较两个字符串是否相等,后者忽略大小写。
•查找子串:
•indexOf(String str) / lastIndexOf(String str):返回指定子串在当前字符串中首次出现/最后一次出现的索引。
•contains(CharSequence s):判断当前字符串是否包含指定子串。
•截取子串:
•substring(int beginIndex) / substring(int beginIndex, int endIndex):返回从指定索引开始到结束索引(含)的子串。
•替换:
•replace(char oldChar, char newChar) / replace(CharSequence target, CharSequence replacement):替换字符串中的字符或子串。
•分割:
•split(String regex):根据给定正则表达式将字符串分割为子串数组。
•转大小写:
•toLowerCase() / toUpperCase():将字符串转换为全小写/全大写。
•trim:
•trim():移除字符串两端的空白字符。
•其他:
•getBytes():将字符串编码为字节数组。
•format(String format, Object... args):使用指定格式和参数生成格式化字符串。
Stringbuffer
import java.lang.StringBuffer;
StringBuffer sb = new StringBuffer();
1. 线程安全:StringBuffer 类的所有方法都被声明为 synchronized,这意味着在同一时刻,只有一个线程能够访问这些方法,从而保证了在多线程环境下对 StringBuffer 实例的操作是安全的。同步机制确保了在并发修改时不会出现数据不一致的问题,但这也带来了额外的性能开销,如线程上下文切换和锁竞争。
2. 适用场景:由于线程安全特性,StringBuffer 通常用于多线程环境中需要共享且频繁修改同一个字符串的情况。尽管单线程应用中也可以使用 StringBuffer,但如果线程安全不是必需,则使用 StringBuilder 可能更合适。
Stringbuilder
import java.lang.StringBuilder;
StringBuilder sb = new StringBuilder();
1. 线程不安全:StringBuilder 类的方法没有进行同步处理,因此在多线程环境下使用时,如果不采取额外的同步措施,可能会导致数据不一致或其他并发问题。然而,这种设计使得 StringBuilder 在单线程环境下的操作具有更高的性能,因为它省去了同步带来的开销。
2. 适用场景:StringBuilder 主要适用于单线程程序中需要高效地进行字符串操作的场合。当字符串构建或修改任务仅在一个线程内进行,或者能确保多个线程间不存在共享的 StringBuilder 实例时,应优先选择 StringBuilder,以充分利用其无锁带来的性能优势。
集合
有数组,为啥还学习集合?
数组定义完成并启动后,类型确定、长度固定。
集合大小可变,提供的功能更为丰富,开发中用的更多。
Collection(单列集合)
List(有序,可重复)
vector(数组结构,线程安全)
在Java中,Vector类是早期集合框架中提供的一个线程安全的动态数组实现。它继承自AbstractList,实现了List接口,内部使用数组来存储元素,并通过同步机制确保线程安全。
特性
1. 线程安全:Vector的所有操作方法(如add(), remove(), get(), size()等)都被声明为synchronized,这意味着在同一时刻,只有一个线程能够访问这些方法,从而保证了在多线程环境下对Vector实例的操作是安全的。同步机制确保了在并发修改时不会出现数据不一致的问题,但这也带来了额外的性能开销,如线程上下文切换和锁竞争。
2. 动态数组:Vector类似于数组,其内部元素存储在一块连续的内存空间中,支持随机访问(通过索引)和动态扩容。当向Vector中添加元素,且当前容量不足以容纳新元素时,Vector会自动增加其容量。
3. 增长策略:Vector的增长策略是按需增加容量,每次扩容时一般将容量翻倍,以减少频繁扩容导致的性能损耗。可以通过构造函数指定初始容量和增长因子。
import java.util.Vector;
public class VectorExample {
public static void main(String[] args) {
Vector<String> vector = new Vector<>();
// 添加元素
vector.add("Apple");
vector.add("Banana");
vector.add("Cherry");
// 访问元素
System.out.println(vector.get(0)); // 输出 "Apple"
// 遍历元素
for (String fruit : vector) {
System.out.println(fruit);
}
// 修改元素
vector.set(1, "Mango");
System.out.println(vector.get(1)); // 输出 "Mango"
// 查询大小
System.out.println("Size: " + vector.size());
// 移除元素
vector.remove("Cherry");
}
}
Arraytlist(数组结构,非线程安全)
在Java中,ArrayList是集合框架中提供的一个非线程安全的动态数组实现。它继承自AbstractList,实现了List接口,内部使用数组来存储元素。与Vector不同,ArrayList并未进行同步处理,这意味着在多线程环境下并发访问ArrayList时,如果不采取适当的同步措施,可能会导致数据不一致或并发问题。
特性
1. 非线程安全:ArrayList的所有操作方法(如add(), remove(), get(), size()等)都没有进行同步处理,因此在多线程环境下同时访问或修改ArrayList可能会导致数据不一致、死锁或竞态条件等问题。
2. 动态数组:ArrayList类似于数组,其内部元素存储在一块连续的内存空间中,支持随机访问(通过索引)和动态扩容。当向ArrayList中添加元素,且当前容量不足以容纳新元素时,ArrayList会自动增加其容量。
3. 增长策略:ArrayList的增长策略是按需增加容量,每次扩容时一般将容量翻倍,以减少频繁扩容导致的性能损耗。可以通过构造函数指定初始容量,但不支持直接设置增长因子。
import java.util.ArrayList;
public class ArrayListExample {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
// 添加元素
list.add("Apple");
list.add("Banana");
list.add("Cherry");
// 访问元素
System.out.println(list.get(0)); // 输出 "Apple"
// 遍历元素
for (String fruit : list) {
System.out.println(fruit);
}
// 修改元素
list.set(1, "Mango");
System.out.println(list.get(1)); // 输出 "Mango"
// 查询大小
System.out.println("Size: " + list.size());
// 移除元素
list.remove("Cherry");
}
}
LinkedList(链表结构,非线程安全)
在Java中,LinkedList类是集合框架中提供的一个基于链表结构实现的非线程安全的列表类。它继承自AbstractSequentialList,实现了List接口,同时也实现了Deque接口,这意味着LinkedList既可以作为双向链表使用,也可以作为双端队列使用。
特性
1. 非线程安全:LinkedList的所有操作方法(如add(), remove(), get(), size()等)都没有进行同步处理,因此在多线程环境下同时访问或修改LinkedList可能会导致数据不一致、死锁或竞态条件等问题。
2. 链表结构:LinkedList内部使用双向链表(每个节点包含前驱和后继节点的引用)来存储元素,不保证元素的物理存储顺序与逻辑顺序一致。这意味着在内存中,元素并非存储在连续的内存块中,而是分散在内存的不同位置,通过节点间的引用连接起来。
3. 插入和删除效率:由于链表结构的特点,LinkedList在元素的插入(包括在列表中间或两端)和删除操作上具有较高的效率,时间复杂度为O(1),尤其是在频繁进行插入和删除的场景下,相比于数组结构(如ArrayList)更具优势。
4. 随机访问效率:由于链表不具备随机访问能力,访问特定位置的元素需要从头或尾部开始逐个遍历,时间复杂度为O(n),在需要频繁进行随机访问的场景下,性能不如数组结构。
import java.util.LinkedList;
public class LinkedListExample {
public static void main(String[] args) {
LinkedList<String> list = new LinkedList<>();
// 添加元素
list.add("Apple");
list.add("Banana");
list.addFirst("Cherry"); // 在头部添加元素
// 访问元素
System.out.println(list.get(1)); // 输出 "Banana"
// 遍历元素
for (String fruit : list) {
System.out.println(fruit);
}
// 修改元素
list.set(1, "Mango");
System.out.println(list.get(1)); // 输出 "Mango"
// 查询大小
System.out.println("Size: " + list.size());
// 移除元素
list.removeLast(); // 从尾部移除元素
}
}
ArrayList 和 LinkedList 的区别
底层数据结构:ArrayList是动态数组的数据结构实现 LinkedList是双向链表的数据结构实现
操作效率:查询ArrayList效率高,添加和删除LinkedList效率高
内存空间占用:
ArrayList底层是数组,内存连续,节省内存
LinkedList 是双向链表需要存储数据,和两个指针,更占用内存
线程安全:ArrayList和LinkedList都不是线程安全的
ArrayList 扩容机制
ArrayList 内部使用一个可动态扩容的数组来存储元素。当添加元素导致现有数组容量不足时,ArrayList 会自动进行扩容。其扩容机制如下:
1. 初始容量:创建一个空的 ArrayList 时,默认容量为 10。也可以通过构造函数指定初始容量。
2. 扩容阈值:每当需要扩容时,新的容量会设置为当前容量的 1.5 倍 加上 1(即 newCapacity = oldCapacity + (oldCapacity >> 1) + 1)。这样做的目的是在大多数情况下,一次扩容操作可以应对接下来多次的元素添加,减少频繁扩容的性能损耗。
3. 扩容操作:创建一个新的数组,其容量为计算得到的新容量。然后将原数组中的所有元素复制到新数组中。这个过程的时间复杂度为 O(n),其中 n 为原数组中元素的数量。
完成复制后,更新内部引用,使 ArrayList 指向新的数组,原来的数组将被垃圾回收。
LinkedList 扩容机制
相比之下,LinkedList 不需要进行扩容,因为它的底层实现是双向链表。链表的每个节点(Node)除了存储数据外,还持有前驱节点和后继节点的引用。添加、删除元素时,只需要修改相关节点的引用关系即可,无需考虑容量问题。
因此:
1. 无固定容量:LinkedList 没有固定的容量概念,它可以根据需要动态地在链表头部、尾部或中间插入节点,不需要预先分配固定大小的内存空间。
2. 添加元素:添加元素时,创建一个新的节点,将其与链表中的相应位置建立引用关系。对于添加到头部或尾部的操作,时间复杂度为 O(1);对于在中间插入节点,需要找到插入位置的前驱节点,时间复杂度为 O(n)。
Set(无序,不可重复)
HashSet
HashSet 是 Java 集合框架中 Set 接口的一个重要实现类,它基于哈希表(HashMap)实现,提供了一种不允许包含重复元素的集合。
特性
1. 不允许重复元素:HashSet 通过调用元素的 hashCode() 方法计算哈希值,并根据哈希值将元素映射到哈希表的桶(bucket)中。如果两个元素的哈希值相同,进一步调用 equals() 方法判断是否相等。只有当两个元素的哈希值不同或虽哈希值相同但 equals() 方法返回 false 时,元素才能成功添加到 HashSet 中。因此,HashSet 保证了集合中元素的唯一性。
2. 无序:HashSet 不保证元素的顺序,即元素的添加顺序与迭代输出的顺序可能不一致。如果需要保持元素的插入顺序,可以使用 LinkedHashSet。
3. 高效查找、添加和删除:HashSet 通过哈希表实现,提供了接近 O(1) 平均时间复杂度的查找、添加和删除操作。这使得 HashSet 在处理大量数据且需要频繁进行这些操作时表现出色。
HashSet<E>()
HashSet<E>(int initialCapacity)
HashSet<E>(int initialCapacity, float loadFactor)
HashSet<E>(Collection<? extends E> c)
HashSet===>LinkedHashSet
特性:
1. 不允许重复元素:如同 HashSet,LinkedHashSet 通过哈希表和元素的 hashCode()、equals() 方法确保集合中元素的唯一性。
2. 有序:LinkedHashSet 维护了一个内部的双向链表,用于记录元素的插入顺序。因此,尽管它仍然是基于哈希表实现的,但其迭代输出的顺序与元素的插入顺序一致。
LinkedHashSet<E>()
LinkedHashSet<E>(int initialCapacity)
LinkedHashSet<E>(int initialCapacity, float loadFactor)
LinkedHashSet<E>(Collection<? extends E> c)
TreeSet
特性:
1. 不允许重复元素:TreeSet 通过红黑树(一种自平衡二叉搜索树)实现,使用元素的 compareTo()(对于实现了 Comparable 接口的元素)或提供的 Comparator 实现来确保集合中元素的唯一性。元素需符合排序规则,即对于任何两个元素 a 和 b,要么 a.compareTo(b) < 0,要么 a.compareTo(b) > 0,要么 a.compareTo(b) == 0。
2. 有序:TreeSet 根据元素的自然排序(如果元素实现 Comparable 接口)或提供的 Comparator 排序规则对元素进行排序。迭代输出的顺序与元素的排序顺序一致。
TreeSet<E>()
TreeSet<E>(Comparator<? super E> comparator)
TreeSet<E>(Collection<? extends E> c)
TreeSet<E>(SortedSet<E> s)
Map(双列集合)
HashTable
特性:
1. 线程安全:Hashtable 是早期实现的线程安全的哈希表,所有的操作方法都使用 synchronized 关键字进行了同步,确保在多线程环境下对键值对的操作是线程安全的。
2. 无序:Hashtable 不保证键值对的迭代顺序,即迭代输出的顺序可能随操作变化而变化,且与插入顺序无关。
3. 兼容性:由于历史原因,Hashtable 仍然存在于 Java 集合框架中,但自从 Java 1.2 引入了 HashMap 后,一般情况下应优先使用 HashMap。Hashtable 主要用于向后兼容或与遗留代码交互。
构造方法:
Hashtable()
Hashtable(int initialCapacity)
Hashtable(int initialCapacity, float loadFactor)
Hashtable(Map<? extends K, ? extends V> t)
常用方法:Hashtable 提供的方法与 HashMap 类似,继承自 Map 接口。
示例:
Hashtable<String, Integer> hashtable = new Hashtable<>();
hashtable.put("Apple", 1);
hashtable.put("Banana", 2);
hashtable.put("Cherry", 3);
System.out.println(hashtable.get("Apple")); // 输出 1
System.out.println(hashtable.size()); // 输出 3
hashtable.remove("Banana");
System.out.println(hashtable); // 输出 {Apple=1, Cherry=3} 或其他顺序
HashMap
特性:
1. 非线程安全:HashMap 是非线程安全的哈希表,适用于单线程环境。在多线程环境下,如果不进行适当的同步控制,可能会导致数据不一致或并发问题。
2. 无序:HashMap 不保证键值对的迭代顺序,即迭代输出的顺序可能随操作变化而变化,且与插入顺序无关。
3. 高效:相对于 Hashtable,HashMap 在单线程环境下提供了更高的性能,因为它不需要进行同步操作。在大多数场景下,应优先使用 HashMap。
构造方法:
HashMap()
HashMap(int initialCapacity)
HashMap(int initialCapacity, float loadFactor)
HashMap(Map<? extends K, ? extends V> m)
LinkedHashMap
LinkedHashMap 是 Java 集合框架中 java.util 包下的一个哈希表实现,用于存储键值对(Map.Entry<K, V>)。LinkedHashMap 继承自 HashMap,并添加了链表用于维护键值对的插入顺序或访问顺序。
特性
1. 非线程安全:LinkedHashMap 是非线程安全的,适用于单线程环境。在多线程环境下,如果不进行适当的同步控制,可能会导致数据不一致或并发问题。
2. 有序:LinkedHashMap 通过维护一个双向链表,保证了键值对的迭代顺序。有两种顺序模式可供选择:
•插入顺序(默认):键值对的迭代顺序与它们插入到映射中的顺序相同。即先插入的键值对先被迭代。
•访问顺序(通过构造方法指定):键值对的迭代顺序反映了最近访问它们的顺序。即最近访问的键值对先被迭代。每当通过 get()、put() 或 putIfAbsent() 方法访问键值对时,它们会被移动到链表的尾部。
构造方法:
LinkedHashMap()
LinkedHashMap(int initialCapacity)
LinkedHashMap(int initialCapacity, float loadFactor)
LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder)
LinkedHashMap(Map<? extends K, ? extends V> m)
ConcurrentHashMap
ConcurrentHashMap 是 Java 集合框架中 java.util.concurrent 包下的一个线程安全、高性能的哈希表实现,用于存储键值对(Map.Entry<K, V>)。ConcurrentHashMap 在保证线程安全的同时,通过锁分段技术(Segment Locking)尽可能地降低锁的粒度,从而提高在多线程环境下的并发性能。特性
1. 线程安全:ConcurrentHashMap 通过内部的锁分段机制确保在多线程环境下对键值对的操作是线程安全的。它将整个哈希表分为若干个独立的段(Segment),每一段都由一把锁保护。当多个线程同时对不同段进行操作时,可以并发执行,互不影响。
2. 高并发性能:由于锁分段的设计,ConcurrentHashMap 在并发访问时能够有效减少锁竞争,提高吞吐量。特别是在数据量较大且并发访问模式下,性能通常优于 Collections.synchronizedMap(Map<K, V> m) 返回的同步包装器。
3. 无序:ConcurrentHashMap 不保证键值对的迭代顺序,即迭代输出的顺序可能随操作变化而变化,且与插入顺序无关。
ConcurrentHashMap()
ConcurrentHashMap(int initialCapacity)
ConcurrentHashMap(int initialCapacity, float loadFactor)
ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel)
ConcurrentHashMap(Map<? extends K, ? extends V> m)
•无参构造方法:创建一个空的 ConcurrentHashMap,初始容量为 16,加载因子为 0.75,段数为默认值(根据系统属性和CPU核心数计算得出)。
•指定初始容量构造方法:创建一个空的 ConcurrentHashMap,并指定初始容量,加载因子为 0.75,段数为默认值。
•指定初始容量和加载因子构造方法:创建一个空的 ConcurrentHashMap,并指定初始容量和加载因子,段数为默认值。加载因子决定了何时进行扩容,当元素数量超过容量与加载因子的乘积时,会触发扩容操作。
•指定初始容量、加载因子和段数构造方法:创建一个空的 ConcurrentHashMap,并指定初始容量、加载因子和段数。段数直接影响锁的粒度,一般情况下,根据预期的并发级别设置即可。
•从映射构造方法:创建一个包含指定映射元素的 ConcurrentHashMap,初始容量、加载因子和段数与无参构造方法相同。
TreeMap
特性:
1. 线程不安全:TreeMap 是非线程安全的,适用于单线程环境。在多线程环境下,如果不进行适当的同步控制,可能会导致数据不一致或并发问题。
2. 有序:TreeMap 是基于红黑树(一种自平衡二叉搜索树)实现的,它按照键的自然排序(如果键实现 Comparable 接口)或提供的 Comparator 排序规则对键值对进行排序。迭代输出的顺序与键的排序顺序一致。
构造方法:
TreeMap<String, Integer> treeMap = new TreeMap<>();
treeMap.put("Apple", 1);
treeMap.put("Banana", 2);
treeMap.put("Cherry", 3);
System.out.println(treeMap.get("Apple")); // 输出 1
System.out.println(treeMap.size()); // 输出 3
treeMap.remove("Banana");
System.out.println(treeMap); // 输出 {Apple=1, Cherry=3},按照字母顺序排序
总结
•Hashtable:早期实现的线程安全哈希表,适用于多线程环境且与遗留代码交互。由于同步开销较大,一般情况下建议使用 ConcurrentHashMap 替代。
•HashMap:非线程安全、高效的哈希表,适用于单线程环境下的键值对存储。在多线程环境下,需自行添加同步控制或使用 ConcurrentHashMap。
•LinkedHashMap: 非线程安全、有序的哈希表实现,它通过维护一个双向链表来保持键值对的插入顺序或访问顺序。根据实际需求选择合适的顺序模式,LinkedHashMap 适用于需要有序键值对存储的场景。在多线程环境下,需自行添加同步控制或使用 ConcurrentSkipListMap。
•TreeMap:基于红黑树实现的有序哈希表,适用于需要键按照特定排序规则排列的场景。在多线程环境下,需自行添加同步控制或使用 ConcurrentSkipListMap。