分类
队列(Queue)
特点:先进先出(FIFO, First-In-First-Out)。
用途:适用于需要按顺序处理元素的场景,如任务调度。
操作:主要有入队(enqueue)和出队(dequeue)操作。
实现:
LinkedList:实现了
Queue
接口,提供FIFO队列操作。同时也实现了List
接口。PriorityQueue:一个基于优先级堆的无界优先级队列。
ArrayDeque:基于数组的双端队列,同时可以被用作栈(Stack)使用。
Queue<Integer> queue = new LinkedList<>();
Queue<Integer> priorityQueue = new PriorityQueue<>();
Deque<Integer> deque = new ArrayDeque<>(); // Deque(双端队列)是Queue(队列)的一个扩展,它支持在两端插入和移除元素,因此提供了更大的灵活性。
方法
add(element)
: 将指定元素添加到此队列的尾部。如果队列已满,将抛出IllegalArgumentException
。offer(element)
: 类似于add
,但是当无法添加元素时返回false
而不是抛出异常。remove()
: 检索并移除此队列的头部元素。如果队列为空,将抛出NoSuchElementException
。poll()
: 类似于remove
,但是当队列为空时返回null
。element()
: 返回但不移除此队列的头部元素。如果队列为空,将抛出NoSuchElementException
。peek()
: 类似于element
,但是当队列为空时返回null
列表(List)
特点:元素有序且可重复。
用途:适用于需要随机访问元素的场景。
操作:支持在任意位置添加、删除和访问元素。
实现
ArrayList:基于动态数组实现,支持随机访问。
LinkedList:基于双向链表实现,优于ArrayList在列表中间进行插入和删除操作。
Vector:和ArrayList类似,但是所有操作都是同步的,线程安全的。
List<Integer> arrayList = new ArrayList<>();
List<Integer> linkedList = new LinkedList<>();
List<Integer> vector = new Vector<>();
实现
add(element)
: 将指定元素添加到列表的末尾。add(index, element)
: 在指定位置插入指定元素。get(index)
: 返回指定位置的元素。set(index, element)
: 将指定位置的元素替换为新元素。remove(index)
: 移除指定位置的元素。size()
: 返回列表中的元素数量。isEmpty()
: 判断列表是否为空。clear()
: 清空列表中的所有元素。
集合(Set)
特点:元素无序且唯一。
用途:适用于需要维护一组唯一元素的场景,如去重。
操作:主要有添加元素、删除元素和检查元素是否存在等操作。
实现
HashSet
: 基于哈希表实现,提供了平均时间复杂度为O(1)的添加、删除和查找操作。LinkedHashSet
: 继承自HashSet
,维护了元素的插入顺序。TreeSet
: 基于红黑树实现,提供了排序功能,可以自然排序或使用Comparator
定制排序。Set<Integer> hashSet = new HashSet<>();
Set<Integer> linkedHashSet = new LinkedHashSet<>();
Set<Integer> treeSet = new TreeSet<>();
方法
add(element)
: 添加元素到集合中,如果元素已经存在则返回false
。remove(element)
: 从集合中移除指定元素。contains(element)
: 判断集合是否包含指定元素。size()
: 返回集合中的元素数量。isEmpty()
: 判断集合是否为空。clear()
: 清空集合中的所有元素。
栈(Stack)
特点:后进先出(LIFO, Last-In-First-Out)。
用途:适用于需要后进先出处理的场景,如浏览器的前进后退、函数调用的堆栈。
操作:主要有入栈(push)和出栈(pop)操作。
Stack
: 这个类继承自Vector
,提供了一个后进先出(LIFO)的栈结构。然而,Stack
类现在被认为过时,不推荐使用,因为有更好的替代方案,如Deque
接口的实现。Deque<Integer> stack = new ArrayDeque<>();// 基于数组的, 不支持随机访问,内存开销小
Deque<Integer> stack = new LinkedList<>();//基于双向链表的,支持随机访问,内存开销大
Stack<Integer> stack = new Stack<>();方法:
push(element)
: 将元素压入栈顶。pop()
: 移除并返回栈顶元素。如果栈为空,将抛出EmptyStackException
。peek()
: 返回但不移除栈顶元素。如果栈为空,将抛出EmptyStackException
。search(object)
: 返回对象在栈中的深度,如果对象不在栈中则返回-1。
区别总结
- 顺序性:列表(List)保持元素的插入顺序,队列(Queue)和栈(Stack)分别遵循FIFO和LIFO原则,而集合(Set)不保证元素的顺序。
- 元素唯一性:集合(Set)中的元素唯一,而列表(List)、队列(Queue)和栈(Stack)允许元素重复。
- 数据结构用途:每种数据结构根据其特点适用于不同的场景,如栈适用于实现递归算法,队列适用于任务调度,集合适用于去重,列表适用于需要频繁随机访问元素的场景。
基础操作
字符串操作
- 字符串加减操作:使用
String.valueOf(Integer.parseInt(a) - Integer.parseInt(b))
。
String c = String.valueOf(Integer.parseInt(a) - Integer.parseInt(b)); |
- 字符串列表
List
转字符串:StringUtils.join(listA, ",")
。
StringUtils.join(listA, ","); |
判断字符串集合是否为空:
isEmpty()
方法,可以判断List
、Set
、Queue
、Stack
或者一个字符串是否为空,返回一个Boolean值获取字符串长度:
length()
方法。替换字符串中的字符
replace
String s = "CMFDKCM";
s = s.replace("CM","f");
System.out.println(s); // fFDKf
数组操作
复制数组部分元素:
Arrays.copyOfRange()
。这个方法接受三个参数:原始数组,开始索引和结束索引。它会创建一个新的数组,包含原始数组从开始索引到结束索引(不包括结束索引)的元素。例如,如果我们有一个数组int[] original = {1, 2, 3, 4, 5};
,我们可以使用Arrays.copyOfRange(original, 1, 3);
来创建一个新的数组,包含原始数组的第二个和第三个元素,即{2, 3}
。高效数组复制:
System.arraycopy()
。src
:源数组。srcPos
:源数组中开始复制的位置索引。dest
:目标数组。destPos
:目标数组中开始粘贴的位置索引。length
:要复制的元素数量。
public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length) |
- 数组填充:
Arrays.fill()
。Arrays.fill方法将指定的值分配给数组的每个元素。
boolean[] status = new boolean[n + 1]; |
集合操作
集合添加元素:
add()
方法。add()
方法通常用于向集合(如List、Set、Queue等)添加元素,返回值为boolean获取集合大小:
size()
方法。集合转换字符串:
String.join()
或StringUtils.join()
。判断集合是否为空:
isEmpty()
。集合工具类:
Collections
提供排序、查找等方法。
Map操作
- get方法,返回对应key的值,如果不存在就返回null
map.get("userId"); |
- 添加键值对:
put()
。
Map map = new HashMap(); |
- 获取值或默认值:
getOrDefault()
。用于获取指定键的值,如果此映射中不包含该键的映射关系,则返回默认值。
Map<String, Integer> numbers = new HashMap<>(); |
检查是否存在键:
containsKey()
。遍历Map:
entrySet()
、keySet()
、values()
或 Lambda 表达式。
Map<Integer, List<Integer>> map = new HashMap<>(); |
字符串构建
StringBuffer
(线程安全)与StringBuilder
(非线程安全)用于动态字符串构建。StringBuffer(线程安全):
StringBuffer
是 Java 中的一个类,用于处理可变的字符序列。它和String
的主要区别在于,String
是不可变的,而StringBuffer
是可变的。当你使用String
类型的对象进行字符串连接操作时,每次连接都会创建一个新的String
对象,这在性能上可能会有问题,特别是在大量的字符串操作中。而StringBuffer
类型的对象在进行字符串连接操作时,不会创建新的对象,而是在原有对象的基础上进行修改,因此在进行大量的字符串操作时,使用StringBuffer
通常会更高效。StringBuilder(非线程安全):
StringBuilder
是 Java 中的一个类,用于处理可变的字符序列。它和StringBuffer
的主要区别在于,StringBuilder
不是线程安全的,而StringBuffer
是线程安全的。线程安全:是指在多线程环境下,一个对象在被多个线程访问时,可以保证每次的结果都是正确的,不会出现数据错乱的情况。在
StringBuffer
中,其主要方法(如append
、insert
等)都是同步的,也就是说,在多线程环境下,这些方法在同一时间只能被一个线程访问,其他线程必须等待,这就保证了StringBuffer
的线程安全性。而StringBuilder
的方法则没有进行同步,所以在多线程环境下,如果有多个线程同时访问StringBuilder
的方法,可能会导致数据错乱。但是,由于没有进行同步,StringBuilder
在单线程环境下的性能比StringBuffer
更好。StringBuilder和StringBuffer方法
StringBuilder sb = new StringBuilder("Hello"); |
StringBuilder sb = new StringBuilder("Hello World"); |
StringBuilder sb = new StringBuilder("Hello Beautiful World"); |
// 创建一个 StringBuffer 对象 |
StringBuilder sb = new StringBuilder("Hello World"); |
StringBuilder sb = new StringBuilder("Hello World"); |
进阶操作
字符串处理
- 字符访问:
charAt()
。传递一个整型参数,表示要获取的索引,返回类型为char,两个chart类型之间相减,Java会自动将它们提升为int
类型来进行计算。
String str = "Hello, World!"; |
- 字符串连接:
.join()
。
String[] nums = {"1","2","3","4","6","9","10"}; |
字符串裁剪:
substring()
。substring(int beginIndex)
: 返回一个新字符串,它是此字符串的一个子字符串。子字符串从指定的beginIndex
开始,直到此字符串的末尾。substring(int beginIndex, int endIndex)
: 返回一个新字符串,它是此字符串的一个子字符串。子字符串从指定的beginIndex
开始,直到索引endIndex - 1
的字符。因此,子字符串的长度为endIndex-beginIndex
。String str = "Hello, world!";
String subStr1 = str.substring(7); // 返回"world!"
String subStr2 = str.substring(7, 12); // 返回"world"
System.out.println(subStr1); // 输出: world!
System.out.println(subStr2); // 输出: world
查找字符或子串位置:
indexOf()
、lastIndexOf()
。indexOf
方法返回指定字符或子字符串在字符串中第一次出现的位置的索引。如果没有找到,则返回-1。lastIndexOf
方法返回指定字符或子字符串在字符串中最后一次出现的位置的索引。如果没有找到,则返回-1。String str = "Hello, world!";
int index = str.indexOf('o'); // 返回4,因为'o'第一次出现在索引4的位置
int lastIndex = str.lastIndexOf('o'); // 返回8,因为'o'最后一次出现在索引8的位置
index = str.indexOf("world"); // 返回7,因为"world"第一次出现在索引7的位置
lastIndex = str.lastIndexOf("world"); // 返回7,因为"world"最后一次出现在索引7的位置
index = str.indexOf('x'); // 返回-1,因为'x'在字符串中没有出现
lastIndex = str.lastIndexOf('x'); // 返回-1,因为'x'在字符串中没有出现
去除空白:
trim()
。用于去除字符串两端的空格。字符检查与转换:
Character
类的isDigit()
、toLowerCase()
、toUpperCase()
。
String s = "abcd1"; |
Character.isLetterOrDigit
:判断字符串的某个字符是否为字母或数字Character.isLetter
:判断字符串的某个字符是否为字母String s = ",.asdas,9";
System.out.println(Character.isLetterOrDigit(s.charAt(0)));//false
System.out.println(Character.isLetterOrDigit(s.charAt(3)));// true
System.out.println(Character.isLetterOrDigit(s.charAt(8)));// true两个
char
进行加减操作为ASCII码相加char a = 'A'; // Unicode 码点值为 65
char b = 'B'; // Unicode 码点值为 66
int result = a + b; // 65 + 66 = 131
String s = "eat";
int res = s.charAt(0) + s.charAt(1);// 115 + 101 = 216
位运算
n & 1
。n & 1
是一个位运算表达式,表示对整数n
进行位与运算。&
是Java中的位与运算符,它会比较两个数的二进制表示,对于每一位,只有当两个数的对应位都为1时,结果的对应位才为1,否则为0。
1
的二进制表示只有最低位(也就是最右边的位)为1,其他位都为0。因此,n & 1
的结果只取决于n
的最低位:- 如果
n
的最低位为1,那么n & 1
的结果为1。 - 如果
n
的最低位为0,那么n & 1
的结果为0。
- 如果
因此,
n & 1
常常被用来判断一个整数是否为奇数。如果一个整数是奇数,那么它的二进制表示的最低位一定为1,所以n & 1
的结果为1;如果一个整数是偶数,那么它的二进制表示的最低位一定为0,所以n & 1
的结果为0。
左移运算:
1 << 30
。<<
是Java中的左移运算符,它将左边的数的二进制表示向左移动指定的位数。移动过程中,左边超出的位被丢弃,右边空出的位用0填充。1 << 30
表示将数字1的二进制表示向左移动30位。数字1的二进制表示为1
,向左移动30位后,变为1000000000000000000000000000000
,这是一个1后面跟着30个0的二进制数,对应的十进制数为1073741824。
位操作技巧:
x & -x
获取最低位1的值。可以看Leetcode《只出现一次的数字 III》- 这是因为
-x
是x
的二进制补码,也就是x
的二进制表示取反然后加1
。所以,-x
的二进制表示中,从最低位开始,到第一个1
之前的所有位都与x
相反,第一个1
和所有更高的位都与x
相同。 - 因此,当我们对
x
和-x
进行位与操作时,从最低位开始,到第一个1
之前的所有位都会得到0
,第一个1
会得到1
,所有更高的位都会得到0
。所以,结果就是x
的二进制表示中最低位的1
所对应的值。
- 这是因为
异或操作
a ^ b = c
c ^ b = a
a ^ c = b
算法相关
计算二进制中1的个数:位运算技巧或
Integer.bitCount()
。位运算: 每执行一次 n &= n - 1,n 中的1的个数都会减少1。这是因为n-1二进制对于n来说就是从右往左找到第一个1,然后将1右边的相对于n全部取反。所以n&n-1会将n的最低位的1消除,因为其在n-1中一定是0。
public static int countBits(int n) {
int count = 0;
while (n != 0) {
n &= n - 1; // 通过与操作 n 和 n-1,消除 n 的最低位的1
count++;
}
return count;
}Integer.bitCount
int count = Integer.bitCount(5); // 101
System.out.println(count); // 输出 2
ASCII与字符转换:类型转换
(int) ch
和(char) ascii
。char ch = 'A';
int ascii = (int) ch;
int ascii = 65;
char ch = (char) ascii;
数据结构转换
数组转列表:
Arrays.asList()
。public class Main {
public static void main(String[] args) {
String[] array = {"Apple", "Banana", "Cherry"};
List<String> list = Arrays.asList(array);
System.out.println(list); // 输出: [Apple, Banana, Cherry]
}
}
关键字理解
final和static区别:
final
是用来限制类、方法或变量的修改的,而static
是用来实现类级别的变量和方法的。final
:final
关键字可以用于修饰类、方法和变量。- 当
final
用于类时,表示该类不能被继承。 - 当
final
用于方法时,表示该方法不能在子类中被重写。 - 当
final
用于变量时,表示该变量的值一旦被初始化后就不能被改变。对于引用类型,引用不能改变,但引用的对象是可以修改的。
- 当
static
:static
关键字主要用于修饰成员变量和方法。当
static
用于成员变量时,该变量变成了静态变量,也称为类变量,它属于整个类,而不是类的某个对象。所有的对象都共享同一个静态变量。当
static
用于方法时,该方法变成了静态方法,也称为类方法,它可以在没有创建对象的情况下被调用。public class Student {
static int count = 0; // 静态变量
public Student() {
count++; // 每次创建新的学生对象,数量加一
}
}
public class Main {
public static void main(String[] args) {
Student s1 = new Student();
Student s2 = new Student();
Student s3 = new Student();
System.out.println(Student.count); // 输出 3
}
}public class Student {
int count = 0; // 实例变量
public Student() {
count++; // 每次创建新的学生对象,数量加一
}
}
public class Main {
public static void main(String[] args) {
Student s1 = new Student();
Student s2 = new Student();
Student s3 = new Student();
System.out.println(s1.count); // 输出 1
System.out.println(s2.count); // 输出 1
System.out.println(s3.count); // 输出 1
}
}
特定集合操作
虽然 LinkedList
和 ArrayList
都实现了 List
接口,因此共享了很多通用的列表操作方法,如 add
, remove
, get
, size
, contains
等,但它们也各自提供了一些特有的方法来利用它们底层数据结构的优势。
Linkedlist
- 内部结构:LinkedList 是基于双向链表实现的,每个节点包含数据和指向前后节点的引用。这种结构不需要连续的内存空间。
- 访问速度:由于不是连续存储,通过索引访问元素需要从头或尾开始遍历,直到找到对应的节点,因此访问速度较慢,时间复杂度为 O(n)。
- 增删速度:对于插入和删除操作,尤其是发生在链表的两端,LinkedList 表现出很高的效率,时间复杂度为 O(1),因为只需要改变相邻节点的引用即可,无需移动元素。
ArrayList
- 内部结构:ArrayList 底层是基于动态数组实现的。这意味着它在内存中是一段连续的空间,类似于传统的数组,但数组的大小可以根据需要自动调整(扩容)。
- 访问速度:由于元素在内存中是连续存储的,通过索引访问元素非常快,时间复杂度接近 O(1)。对于随机访问操作(如 get、set)非常高效。
- 增删速度:对于插入和删除操作,尤其是当操作发生在列表中间或头部时,ArrayList 的效率较低,因为可能需要移动大量元素来保持数组的连续性,时间复杂度最坏情况下为 O(n)。
共同的方法
add(E element)
:在列表末尾添加元素。remove(Object o)
:移除列表中首次出现的指定元素。get(int index)
:返回指定位置的元素。size()
:返回列表中的元素数量。clear()
:移除所有元素。contains(Object o)
:如果列表包含指定的元素,则返回 true。
ArrayList 特有的方法
ArrayList 作为基于数组实现的列表,没有太多额外的特有方法,主要是利用数组的特性进行操作,大部分操作都遵循 List
接口的规范。
LinkedList 特有的方法
addFirst(E e)
:在列表的开头添加元素。addLast(E e)
:在列表的末尾添加元素(等价于add(e)
)。removeFirst()
:移除并返回列表的第一个元素。removeLast()
:移除并返回列表的最后一个元素。getFirst()
:返回列表的第一个元素。getLast()
:返回列表的最后一个元素。pollFirst()
:移除并返回列表的第一个元素,如果列表为空则返回 null。pollLast()
:移除并返回列表的最后一个元素,如果列表为空则返回 null。peekFirst()
:查看但不移除列表的第一个元素,如果列表为空则返回 null。peekLast()
:查看但不移除列表的最后一个元素,如果列表为空则返回 null。
这些特有方法利用了链表结构能够高效地在头部和尾部进行插入和删除操作的特点,使得在实现队列或栈这样的数据结构时特别有用。
Stream和collect
Stream
Stream
用于处理集合(如列表、集合等)中的元素。Stream
提供了一种声明性的方法来处理数据,可以进行过滤、映射、排序、聚合等操作。Stream
API 中,操作可以分为两类:中间操作和终端操作。中间操作:中间操作是返回另一个
Stream
的操作,可以链式调用多个中间操作。中间操作是惰性的,只有在终端操作执行时才会实际处理数据。- **
filter(Predicate<T> predicate)
**:过滤流中的元素,只保留满足给定条件的元素。 - **
map(Function<T, R> mapper)
**:将流中的每个元素转换为另一种形式。 - **
flatMap(Function<T, Stream<R>> mapper)
**:将流中的每个元素转换为一个流,然后将多个流合并为一个流。 - **
sorted()
**:对流中的元素进行自然排序。 - **
sorted(Comparator<T> comparator)
**:根据提供的比较器对流中的元素进行排序。 - **
distinct()
**:去除流中的重复元素。 - **
limit(long maxSize)
**:限制流的元素数量,不超过给定的最大值。 - **
skip(long n)
**:跳过流中的前 n 个元素。
- **
终端操作:终端操作会触发流的处理,并返回一个结果或产生副作用。终端操作执行后,流就不能再被使用。
- **
forEach(Consumer<T> action)
**:对流中的每个元素执行给定的操作。 - **
collect(Collector<T, A, R> collector)
**:将流中的元素收集到一个容器中,如列表、集合、映射等。 - **
reduce(BinaryOperator<T> accumulator)
**:将流中的元素组合成一个结果。 - **
count()
**:返回流中元素的数量。 - **
anyMatch(Predicate<T> predicate)
**:判断是否有任意一个元素匹配给定的条件。 - **
allMatch(Predicate<T> predicate)
**:判断是否所有元素都匹配给定的条件。 - **
noneMatch(Predicate<T> predicate)
**:判断是否所有元素都不匹配给定的条件。 - **
findFirst()
**:返回流中的第一个元素。 - **
findAny()
**:返回流中的任意一个元素。
- **
collect
方法是 JavaStream
API 中非常强大的终端操作,用于将流中的元素收集到各种结果容器中。通过与Collectors
类中的静态方法结合使用,可以实现多种收集操作,如将流转换为列表、集合、映射,或进行分组和连接等操作。使用:
public class StreamExample { |
使用工具类判断字符串和集合是否为空
判断字符串
import org.apache.commons.lang3.StringUtils;
public class StringUtilsExample {
public static void main(String[] args) {
String str1 = "";
String str2 = " ";
String str3 = null;
String str4 = "Hello";
System.out.println(StringUtils.isEmpty(str1)); // 输出: true
System.out.println(StringUtils.isEmpty(str2)); // 输出: false
System.out.println(StringUtils.isEmpty(str3)); // 输出: true
System.out.println(StringUtils.isEmpty(str4)); // 输出: false
System.out.println(StringUtils.isBlank(str1)); // 输出: true
System.out.println(StringUtils.isBlank(str2)); // 输出: true
System.out.println(StringUtils.isBlank(str3)); // 输出: true
System.out.println(StringUtils.isBlank(str4)); // 输出: false
}
}判断集合
public class CollectionUtilsExample {
public static void main(String[] args) {
List<String> list1 = new ArrayList<>();
List<String> list2 = null;
List<String> list3 = new ArrayList<>();
list3.add("Hello");
System.out.println(CollectionUtils.isEmpty(list1)); // 输出: true
System.out.println(CollectionUtils.isEmpty(list2)); // 输出: true
System.out.println(CollectionUtils.isEmpty(list3)); // 输出: false
}
}
parseObject
使用
JSON.parseObject
需要导入fastjson
依赖JSON.parseObject
用于将JSON
字符串解析成指定类型的Java
对象,JSON.parseObject(source, Account.class);
用于将source
这个字符串解析成Account
这个对象