厦门450元网站建设公司,品牌型网站成功案例图片,企业网站托管代运营,Wordpress卡片主题1. 面向对象
1.1 基本概念
OOD#xff1a;代表“面向对象设计”#xff08;Object-Oriented Design#xff09;是一种编程设计方法学#xff0c;基于面向对象编程#xff08;OOP#xff09;的概念和原则#xff0c;如封装、继承和多态。OOD的核心在于使用对象#xf…1. 面向对象
1.1 基本概念
OOD代表“面向对象设计”Object-Oriented Design是一种编程设计方法学基于面向对象编程OOP的概念和原则如封装、继承和多态。OOD的核心在于使用对象具有属性和行为的实体来模拟真实世界中的事物和交互。
OOP代表面向对象编程Object-Oriented Programming是一种编程范式它使用“对象”来设计软件。在OOP中对象是包含数据和方法的实体用于模拟现实世界中的事物和概念。面向对象编程的主要特点包括封装、继承和多态。
1.2 三大特性 封装Encapsulation 封装是将数据属性和操作这些数据的代码方法捆绑在一起的过程。这样做的目的是隐藏对象的内部细节和实现只暴露必要的操作接口。这有助于降低代码复杂性提高安全性。 例子 想象一下一个咖啡机。你不需要知道它内部是如何工作的你只需要知道如何操作它比如按下按钮来制作咖啡。咖啡机的内部机制被封装起来而用户界面提供了与之交互的方法。 优点提高数据安全性简化接口降低耦合度重用性和模块化。缺点过度封装可能导致性能开销增加代码复杂性测试有难度。 继承Inheritance 继承是一种允许新创建的对象继承现有对象的属性和方法的机制。这有助于减少代码重复增强代码的可重用性。 例子 假设我们有一个“动物”类包含所有动物共有的属性和方法如“呼吸”和“移动”。我们可以创建一个“狗”类继承自“动物”类。这样“狗”类就自动拥有了“呼吸”和“移动”的能力而我们也可以为“狗”添加专有的属性和方法如“汪汪叫”。 优点减少代码重复建立继承体系。缺点继承体系容易过于复杂、混乱重写功能可能导致方法作用不明确。 可以使用组合关系代替继承关系。 多态Polymorphism 多态是指允许不同类的对象对同一消息做出响应的能力即同一个接口可以被不同的对象以不同的方式实现。一个对象在运行时的多种形态。接口与实现类 例子 继续上面的“动物”类的例子假设有一个方法叫“发出声音”。不同的动物发出的声音是不同的狗会“汪汪叫”猫会“喵喵叫”。多态允许我们对不同的动物对象调用同一个“发出声音”的方法但每个动物会以它自己的方式来响应。 优点解耦缺点初学者难以理解动态绑定影响性能测试复杂性 结合例子理解面向对象思想 想象你正在开发一个模拟动物园的软件。你创建了一个基类“动物”它定义了所有动物共有的属性和行为如年龄、体重和“移动”的方法。然后你创建了几个继承自“动物”的子类如“狮子”、“大象”和“长颈鹿”每个子类有其特有的属性和行为。例如“狮子”类可能有一个“吼叫”的方法而“长颈鹿”类可能有一个“吃树叶”的方法。这里封装使得每个类的实现细节对外部是隐藏的继承让你可以重用“动物”类的代码而多态允许你以统一的方式处理不同类型的动物。 面向对象编程的这些特性使得代码更易于理解、维护和扩展。通过模拟现实世界的实体和概念它允许开发者以更自然的方式思考和解决问题。
1.3 重载与重写
重载Overloading和重写Overriding是面向对象编程中两个基本且重要的概念尽管它们听起来相似但它们在功能和用途上有显著的区别。
重载Overloading
重载是指在同一个类中有多个同名方法但这些方法的参数列表不同参数的类型、个数或顺序不同。
特点
发生在同一个类中 重载发生在一个类的内部。方法名相同 重载的方法共享同一个名称。参数列表不同 重载的方法必须有不同的参数列表。返回类型可以不同 重载的方法可以有不同的返回类型但仅改变返回类型不足以构成重载。编译时决定 Java编译器根据方法签名在编译时就确定了要调用哪个方法。
示例
public class Example {public void display(String s) {System.out.println(String: s);}public void display(String s, int n) {for (int i 0; i n; i) {System.out.println(String: s);}}
}在这个例子中display 方法被重载了。两个display方法有相同的名称但参数列表不同。
重写Overriding
重写是指子类重新定义父类中的某个方法的实现。子类的方法必须和父类被重写的方法具有相同的方法名称、返回类型和参数列表。
特点
发生在父子类之间 重写发生在继承关系的两个类之间。方法名、返回类型和参数列表相同 重写的方法在子类中必须与父类中的方法完全相同。访问权限不能更严格 重写的方法不能比父类方法具有更严格的访问权限。运行时决定 哪个方法被调用是在运行时基于对象的实际类型决定的。
示例
class Animal {public void makeSound() {System.out.println(Some sound);}
}class Dog extends Animal {Overridepublic void makeSound() {System.out.println(Bark);}
}class Cat extends Animal {Overridepublic void makeSound() {System.out.println(Meow);}
}在这个例子中Dog 和 Cat 类重写了它们从 Animal 类继承的 makeSound 方法。
总结
重载 是指同一个类中的多个同名方法具有不同的参数列表。重写 是子类重定义父类的某个方法。
重载使得同一个方法可以根据不同的参数执行不同的功能而重写则是用于实现多态即同一个接口的不同实现。
1.4 this和super
在Java中super 和 this 关键字用于引用对象的当前实例和其父类的属性或方法。
this 关键字
this 用于引用当前对象的实例。它通常用于以下几个方面
区分实例变量和局部变量 当方法的参数名与类的实例变量名相同可以使用 this 来区分它们。在一个构造器中调用另一个构造器 使用 this() 调用当前类中的另一个构造器。返回当前对象的引用 在方法中返回当前对象。
示例代码
public class MyClass {private int var;public MyClass(int var) {this.var var; // 使用 this 区分实例变量和构造器参数}public void setVar(int var) {this.var var; // 使用 this 区分实例变量和方法参数}public int getVar() {return this.var; // 使用 this 引用当前对象的变量}public MyClass getInstance() {return this; // 返回当前对象实例}
}super 关键字
super 用于引用当前对象的父类。这在子类覆盖父类的方法重写或者想要访问父类的属性时非常有用。
调用父类的构造器 使用 super() 调用父类的构造器。访问被子类覆盖的父类方法 使用 super.methodName() 访问。访问父类的属性 如果子类和父类有同名的属性可以使用 super 来引用父类的属性。
示例代码
class ParentClass {protected String name;public ParentClass(String name) {this.name name;}public void display() {System.out.println(Name in ParentClass: name);}
}class ChildClass extends ParentClass {public ChildClass(String name) {super(name); // 调用父类的构造器}Overridepublic void display() {super.display(); // 调用父类的 display 方法System.out.println(Name in ChildClass: name);}
}public class Test {public static void main(String[] args) {ChildClass obj new ChildClass(Test);obj.display();}
}在这个例子中ChildClass 继承自 ParentClass。ChildClass 的构造器通过 super(name) 调用了 ParentClass 的构造器。同样ChildClass 的 display() 方法覆盖了 ParentClass 的 display() 方法并通过 super.display() 调用了父类的方法。
总结
this 是对当前对象实例的引用。super 是对当前对象的父类的引用。
它们都用于访问对象的属性和方法但 this 引用当前类的成员而 super 引用父类的成员。这在处理继承时特别有用可以帮助区分子类和父类中同名的属性和方法。
1.5 继承和实现的区别
继承 是“是一个is-a”关系例如 Dog 是一个 Animal。实现 是“能做什么can-do”关系例如 Circle 能够进行绘制Drawable。
继承和实现都是实现代码重用和功能扩展的重要手段。在Java中继承是单继承即每个类只能继承一个父类而实现则是多重的即一个类可以实现多个接口。这两种机制共同工作为Java编程提供了强大的灵活性和表达力。
1.6 Java Bean与内省 Java Bean 类必须public 修饰 必须有一个无参构造器 所有字段私有化且提供getter/setter方法 扩展 字段类中的变量称之为字段 属性类中的getter/setter只要存在之一getXxx中的xxx就是属性名称 内省机制 Java提供的一套更便于操作Java Bean属性的API相较于反射更容易操作Java Bean属性的API。
2. 常用类
2.1 String
Java中的String类包含许多用于操作字符串的常用方法。以下是一些常用的String方法及其简要说明 length() 返回字符串的长度。示例hello.length() 返回 5。 charAt(int index) 返回指定索引处的字符。示例hello.charAt(1) 返回 e。 substring(int beginIndex), substring(int beginIndex, int endIndex) 返回字符串的一个子串。示例hello.substring(1) 返回 ellohello.substring(1, 3) 返回 el。 contains(CharSequence s) 检查字符串是否包含指定的字符序列。示例hello.contains(ll) 返回 true。 equals(Object anotherObject), equalsIgnoreCase(String anotherString) 比较两个字符串是否相等。equalsIgnoreCase 忽略大小写。示例Hello.equals(hello) 返回 falseHello.equalsIgnoreCase(hello) 返回 true。 startsWith(String prefix), endsWith(String suffix) 检查字符串是否以指定的前缀开始或以指定的后缀结束。示例hello.startsWith(he) 返回 truehello.endsWith(lo) 返回 true。 toLowerCase(), toUpperCase() 将字符串转换为全部小写或大写。示例Hello.toLowerCase() 返回 hellohello.toUpperCase() 返回 HELLO。 trim() 返回一个新字符串它是通过移除原始字符串开头和结尾的空白字符获得的。示例 hello .trim() 返回 hello。 replace(char oldChar, char newChar), replace(CharSequence target, CharSequence replacement) 替换字符串中的字符或字符序列。示例hello.replace(l, p) 返回 heppohello.replace(ll, yy) 返回 heyyo。 split(String regex) 根据匹配给定的正则表达式来分割字符串。示例a,b,c.split(,) 返回数组 [a, b, c]。 indexOf(int ch), indexOf(String str), lastIndexOf(int ch), lastIndexOf(String str) 返回指定字符或字符串在该字符串中首次出现处的索引lastIndexOf 返回最后一次出现的索引。示例hello.indexOf(l) 返回 2hello.lastIndexOf(l) 返回 3。 concat(String str) 将指定字符串连接到此字符串的末尾。示例Hello, .concat(world!) 返回 Hello, world!。
这些方法是处理字符串时非常常用的操作能够满足大多数基本的字符串处理需求。
2.2 对象比较
在Java中Comparable 和 Comparator 接口都用于实现对象的排序但它们在用法和目的上有一些关键的区别。
2.2.1 Comparable 接口
Comparable 接口用于定义对象自然排序的方式。类通过实现 Comparable 接口的 compareTo 方法来定义其对象的排序逻辑。
自然排序 当一个类实现了 Comparable 接口它的对象集合可以直接使用 Collections.sort 或 Arrays.sort 进行排序。compareTo 方法 这个方法返回一个整数表示调用者对象相对于传入参数的排序顺序。
示例
public class Person implements ComparablePerson {private int age;public Person(int age) {this.age age;}Overridepublic int compareTo(Person other) {return this.age - other.age; // 年龄的升序排序}
}在这个例子中Person 类实现了 Comparable 接口以年龄进行自然排序。
2.2.2 Comparator 接口
Comparator 接口用于定义一个外部的排序逻辑它允许定义多种排序规则或者在类没有实现 Comparable 接口的情况下提供排序逻辑。
自定义排序 可以创建多个不同的 Comparator 实现类来定义不同的排序规则。compare 方法 这个方法接受两个参数返回一个整数表示它们的排序顺序。
示例
public class AgeComparator implements ComparatorPerson {Overridepublic int compare(Person p1, Person p2) {return p1.getAge() - p2.getAge(); // 年龄的升序排序}
}在这个例子中AgeComparator 提供了 Person 对象按年龄排序的规则。
2.2.3 主要区别 实现位置 Comparable 是在类的内部实现的定义了对象的自然排序顺序。Comparator 是在类的外部实现的定义了一种外部的排序规则。 方法数量 Comparable 只包含一个方法 compareTo。Comparator 包含一个方法 compare。 控制权 使用 Comparable 时类的设计者控制着其排序逻辑。使用 Comparator 时类的用户可以定义自己的排序逻辑。 灵活性 Comparable 提供单一的自然排序不够灵活。Comparator 可以提供多种排序逻辑更加灵活。
根据不同的需求选择使用 Comparable 或 Comparator。如果一个类有一个明确的、自然的排序逻辑如数字、字母顺序则使用 Comparable如果需要多种排序方式或者类本身不具备自然排序逻辑或者你无法修改类的源代码那么使用 Comparator 是更好的选择。
2.3 单例模式
懒汉式用到才会创建对象饿汉式直接创建对象直接使用普通版 - 同步锁 - 双重检查 - volatile - 枚举 - 静态内部类
双检加锁机制
if(){ // 第一次检查synchronized(obj){ // 检查if(){ // 第二次检查}}
}3. 集合
3.1 List
3.1.1 ArrayList
ArrayList 是 Java 中一个非常重要的集合类属于 Java 集合框架Java Collections Framework的一部分。它基于动态数组的概念实现提供了一种以数组方式存储元素的列表实现。
主要特点 动态数组 ArrayList 内部使用数组来存储元素。当元素超出当前数组容量时ArrayList 会创建一个新的更大的数组并将所有元素复制到这个新数组中称为“扩容”。 随机访问 由于基于数组实现ArrayList 提供快速的随机访问功能可以通过索引在常数时间内访问元素get(int index) 和 set(int index, E element) 方法。 有序且可重复 ArrayList 保持元素插入的顺序并允许插入重复的元素。 非同步的 ArrayList 不是线程安全的。如果在多线程环境中使用需要外部同步。
主要方法 添加元素 add(E e) 方法用于在列表末尾添加一个元素add(int index, E element) 方法用于在指定位置添加元素。 访问元素 get(int index) 方法用于访问指定位置的元素。 设置元素 set(int index, E element) 方法用于替换指定位置的元素。 删除元素 remove(int index) 方法用于移除指定位置的元素remove(Object o) 方法用于移除第一次出现的指定元素。 列表大小 size() 方法返回列表中的元素数量。 遍历列表 可以通过迭代器Iterator或增强的 for 循环来遍历 ArrayList。 判断是否包含 contains(Object o) 方法用于判断列表是否包含指定的元素。
示例代码
import java.util.ArrayList;public class ArrayListExample {public static void main(String[] args) {ArrayListString list new ArrayList();list.add(Apple);list.add(Banana);list.add(Cherry);System.out.println(ArrayList: list);String fruit list.get(1);System.out.println(Accessed Element: fruit);list.set(1, Blueberry);System.out.println(Modified ArrayList: list);list.remove(Cherry);System.out.println(ArrayList after removal: list);}
}使用注意事项 自动扩容机制 ArrayList 的自动扩容可能会影响性能。如果预先知道存储元素的数量可以通过初始化时指定容量来优化性能。 非线程安全 在多线程环境下建议使用 Collections.synchronizedList 方法来同步 ArrayList或者使用线程安全的替代品如 Vector 或 CopyOnWriteArrayList。
ArrayList 由于其灵活性和易用性是 Java 中使用最广泛的集合之一。它是实现列表功能的优选特别是当需要频繁访问列表中的元素时。
3.1.2 LinkedList
LinkedList 是 Java 集合框架的一部分是一个基于双向链表实现的列表类。与基于动态数组实现的 ArrayList 相比LinkedList 提供了更好的插入和删除元素的性能但在随机访问元素方面表现较差。
主要特点 链表数据结构 LinkedList 内部使用双向链表来存储元素。每个元素节点都包含数据和两个引用一个指向前一个元素一个指向后一个元素。 动态大小 LinkedList 的大小是动态的可以根据需要添加或删除节点。 顺序访问 访问元素时需要从头节点或尾节点开始遍历因此随机访问效率低。 实现了List和Deque接口 LinkedList 不仅实现了 List 接口还实现了双端队列Deque接口因此它还可以作为栈、队列或双端队列使用。
主要方法 添加元素 add(E e) 在列表末尾添加元素add(int index, E element) 在指定位置添加元素addFirst(E e) 和 addLast(E e) 分别在列表头部和尾部添加元素。 访问元素 get(int index) 获取指定位置的元素getFirst() 和 getLast() 分别获取第一个和最后一个元素。 删除元素 remove(int index) 移除指定位置的元素remove(Object o) 移除第一次出现的指定元素removeFirst() 和 removeLast() 分别移除第一个和最后一个元素。 列表大小 size() 方法返回列表中的元素数量。 判断和搜索 contains(Object o) 判断列表是否包含指定元素indexOf(Object o) 和 lastIndexOf(Object o) 分别返回元素首次和最后一次出现的位置。
示例代码
import java.util.LinkedList;public class LinkedListExample {public static void main(String[] args) {LinkedListString list new LinkedList();list.add(Apple);list.add(Banana);list.addFirst(Strawberry);list.addLast(Cherry);System.out.println(LinkedList: list);String firstElement list.getFirst();System.out.println(First Element: firstElement);list.removeLast();System.out.println(LinkedList after removing last: list);}
}使用注意事项 性能考虑 对于需要频繁插入和删除元素的场景LinkedList 是一个好选择。但对于需要频繁随机访问元素的场景ArrayList 可能更合适。 内存占用 由于每个元素都需要额外的空间存储前后节点的引用LinkedList 比 ArrayList 占用更多内存。 迭代器 使用 ListIterator 可以在 LinkedList 中向前和向后遍历。
LinkedList 由于其在列表两端插入和删除操作上的高效性经常被用作队列、栈或双端队列。但是如果你需要频繁地随机访问列表中的元素那么 ArrayList 可能是一个更好的选择。
3.1.3 区别及场景
ArrayList 和 LinkedList 都是 Java 中的 List 接口的实现但它们在内部数据结构和性能特性上有显著的不同。了解这些差异有助于选择最适合特定场景的数据结构。
ArrayList
ArrayList 基于动态数组实现适用于频繁的读取操作。
特点 随机访问快 ArrayList 支持快速的随机访问因为它允许直接通过索引访问元素时间复杂度为 O(1)。 修改慢 在列表中间插入或删除元素比较慢因为这可能涉及移动元素以维护数组的连续性时间复杂度为 O(n)。 内存占用 相对较高的内存开销因为它在数组的基础上维护容量capacity且扩容操作涉及复制元素到新的数组。
使用场景
频繁地通过索引访问元素如随机访问。添加元素通常发生在列表末尾。需要频繁地调整列表大小。
LinkedList
LinkedList 基于双向链表实现适用于频繁的插入和删除操作。
特点 插入和删除快 在 LinkedList 中添加或删除元素不需要移动其它元素时间复杂度为 O(1)特别是在列表的开头或结尾。 随机访问慢 访问元素需要从头节点或尾节点开始遍历因此随机访问效率较低时间复杂度为 O(n)。 内存占用 每个元素都需要额外的空间来存储前后节点的引用因此内存占用比 ArrayList 更高。
使用场景
需要频繁地在列表中间添加或删除元素。实现栈、队列或双端队列。不需要频繁地随机访问元素。
示例 ArrayList 示例 实现一个数字列表需要频繁地读取和更新元素但很少在中间插入或删除元素。代码示例ListInteger numbers new ArrayList();
numbers.add(1);
numbers.add(2);
int number numbers.get(0); // 快速随机访问LinkedList 示例 实现一个待办事项列表需要频繁地在列表的开头或中间添加和删除任务。代码示例ListString todoList new LinkedList();
todoList.addFirst(Wake up);
todoList.addLast(Go to bed);
todoList.removeFirst(); // 快速插入和删除总结来说ArrayList 适合读取操作频繁的场景而 LinkedList 更适合于插入和删除操作频繁的场景。正确选择两者之一可以显著提高程序的性能和效率。
3.2 Set
3.2.1 HashSet
HashSet 是 Java 中一个非常重要的集合类它实现了 Set 接口。HashSet 内部是基于 HashMap 实现的它提供了对集合元素的快速查找并确保集合中不会有重复元素。
主要特点 唯一性 HashSet 不允许存储重复元素每个值在 HashSet 中只能出现一次。 无序集合 HashSet 不保证集合的迭代顺序它的顺序可能随时间的推移而变化。 空值 HashSet 允许存储一个 null 元素。 性能 提供了常数时间复杂度的添加、删除、包含以及大小操作假设哈希函数将元素正确地分散在桶中。
主要方法 添加元素 add(E e) 方法用于向 HashSet 中添加元素。 删除元素 remove(Object o) 方法用于从 HashSet 中删除元素。 查找元素 contains(Object o) 方法用于检查 HashSet 是否包含特定元素。 集合大小 size() 方法用于获取 HashSet 中的元素数量。 清空集合 clear() 方法用于移除 HashSet 中所有元素。 遍历集合 可以使用增强的 for 循环或迭代器来遍历 HashSet。
示例代码
import java.util.HashSet;
import java.util.Set;public class HashSetExample {public static void main(String[] args) {SetString set new HashSet();set.add(Apple);set.add(Banana);set.add(Cherry);set.add(Apple); // 重复元素不会被添加System.out.println(HashSet: set);if (set.contains(Banana)) {System.out.println(HashSet contains Banana);}set.remove(Banana);System.out.println(HashSet after removal: set);}
}使用注意事项 哈希函数 HashSet 的性能依赖于哈希函数的质量。一个好的哈希函数能够均匀地分布元素减少哈希冲突。 null 元素 HashSet 允许存储一个 null 元素但要注意使用时的空指针异常。 迭代性能 迭代 HashSet 的时间复杂度与 HashSet 的容量成正比因此在设置初始容量时要考虑到迭代性能。 线程安全 HashSet 不是线程安全的。如果在多线程环境中使用需要进行外部同步。
HashSet 由于其在处理大量数据时的高效性和简便性是实现集合操作的首选特别是当不需要元素排序或重复元素时。
3.2.2 TreeSet
TreeSet 是 Java 集合框架的一部分它实现了 SortedSet 接口。与 HashSet 不同TreeSet 是基于红黑树一种自平衡二叉搜索树实现的。TreeSet 维护着其元素的有序状态无论是添加还是删除元素都保证元素处于排序状态。
主要特点 元素排序 在 TreeSet 中元素按自然排序或者根据构造时提供的 Comparator 进行排序。 唯一性 与 HashSet 一样TreeSet 不允许存储重复元素。 性能 添加、删除和查找元素的时间复杂度为 O(log n)。 范围查找操作 提供了丰富的方法来对有序集合进行操作如 first(), last(), headSet(), tailSet() 等。
主要方法 添加元素 add(E e) 方法用于向 TreeSet 添加新元素。 删除元素 remove(Object o) 方法用于从 TreeSet 中删除指定元素。 查找元素 contains(Object o) 方法用于检查 TreeSet 是否包含特定元素。 迭代元素 可以使用迭代器或增强的 for 循环按排序顺序遍历 TreeSet。 首尾元素 first() 和 last() 方法分别返回集合中最小和最大的元素。 子集操作 如 headSet(toElement), tailSet(fromElement) 和 subSet(fromElement, toElement) 方法用于获取 TreeSet 的子集。
示例代码
import java.util.TreeSet;public class TreeSetExample {public static void main(String[] args) {TreeSetString treeSet new TreeSet();treeSet.add(Banana);treeSet.add(Apple);treeSet.add(Cherry);System.out.println(TreeSet: treeSet);// 获取并输出第一个最小的元素String first treeSet.first();System.out.println(First Element: first);// 获取并输出最后一个最大的元素String last treeSet.last();System.out.println(Last Element: last);// 删除元素treeSet.remove(Apple);System.out.println(TreeSet after removal: treeSet);}
}使用注意事项 元素排序 TreeSet 要求存储的元素必须实现 Comparable 接口或者在创建 TreeSet 时提供一个 Comparator。 空值处理 如果使用自然排序TreeSet 不能包含 null 元素。如果使用自定义比较器Comparator则该比较器的实现决定是否可以包含 null。 性能考虑 对于需要频繁进行添加、删除和包含操作的大量元素的场景TreeSet 的性能可能低于 HashSet。
TreeSet 由于其元素的有序性适用于需要有序集合的场景例如实现排行榜、范围查找或者维护一个按特定顺序排序的唯一元素集合。
3.3 Map
3.3.1 HashMap
HashMap 是 Java 中一种非常常用的集合类它实现了 Map 接口。基于哈希表的实现HashMap 存储键值对Key-Value映射提供了快速的查找、插入和删除操作。
主要特点 键值对存储 HashMap 存储的是键Key和值Value的映射。 键的唯一性 每个键在 HashMap 中必须是唯一的。 值的可重复性 不同的键可以映射到相同的值。 无序集合 HashMap 中的元素没有特定的顺序。 空值和空键 HashMap 允许存储一个 null 键和多个 null 值。 性能 提供了常数时间复杂度的基本操作如获取和插入前提是哈希函数将键均匀分布在桶中。
主要方法 插入元素 put(Key k, Value v) 方法用于向 HashMap 中添加键值对。 获取元素 get(Object key) 方法用于根据键获取对应的值。 删除元素 remove(Object key) 方法用于删除指定键的键值对。 检查键存在 containsKey(Object key) 方法用于检查 HashMap 中是否包含指定的键。 遍历映射 可以通过 keySet(), values(), 和 entrySet() 方法来遍历 HashMap 中的键、值或键值对。
示例代码
import java.util.HashMap;
import java.util.Map;public class HashMapExample {public static void main(String[] args) {MapString, Integer map new HashMap();// 添加键值对map.put(Apple, 10);map.put(Banana, 20);map.put(Cherry, 30);// 访问值int value map.get(Apple);System.out.println(Value for Apple: value);// 遍历映射for (Map.EntryString, Integer entry : map.entrySet()) {System.out.println(entry.getKey() : entry.getValue());}// 删除元素map.remove(Banana);System.out.println(Map after removal: map);}
}使用注意事项 哈希冲突 当不同的键有相同的哈希值时会发生哈希冲突这可能导致访问和插入操作的时间复杂度增加。 键的不可变性 作为键的对象应该是不可变的以保证哈希值的一致性。例如常用的键类型有 String 和各种包装类型如 Integer、Long 等。 线程安全 HashMap 不是线程安全的。在多线程环境中可以考虑使用 ConcurrentHashMap 或外部同步机制。
HashMap 由于其出色的平均性能表现是实现映射的首选。它在处理大量数据时非常有效特别是当你需要快速地查找、更新或删除键值对时。
HashMap的put()原理
HashMap 在 Java 中是基于哈希表的 Map 实现它提供了快速的插入、查找和删除操作。让我们详细了解 HashMap 的插入元素的过程 哈希函数 当向 HashMap 中插入一个键值对时首先会使用哈希函数处理键对象以确定该键值对应存储在哈希表的哪个位置也称为桶。 计算哈希码 HashMap 使用 key.hashCode() 方法来计算键对象的哈希码。哈希函数 为了减少哈希冲突HashMap 对哈希码进行哈希函数处理确定最终的桶索引。这通常涉及到将哈希码与数组大小相关联。 处理哈希冲突 由于哈希表的大小有限不同的键可能会映射到同一个桶哈希冲突。HashMap 使用链表在 Java 8 及以后版本中当链表长度超过一定阈值时使用红黑树来处理冲突。 链地址法 如果已经有一个或多个键值对存储在计算出的桶索引位置则新的键值对会被存储在该位置的链表或红黑树中。 插入元素 新键值对 如果桶索引位置为空新的键值对作为第一个元素插入如果位置不为空则按照链表或红黑树的方式添加到该位置。键的唯一性 如果插入的键在 HashMap 中已存在则新的值将覆盖旧值。 扩容 负载因子和容量 HashMap 有一个负载因子默认为 0.75它是容量和当前键值对数量的比率。当 HashMap 中的元素数量超过容量与负载因子的乘积时哈希表将被扩容通常是翻倍。重新哈希 扩容后现有的键值对需要根据新的数组大小重新计算哈希并重新分配到新的桶中。 插入过程的注意事项 键的不变性 键对象一旦用作 HashMap 的键就不应该修改。因为任何对键对象的改变都可能影响其哈希码从而使得无法在 HashMap 中正确定位该键。null 值处理 HashMap 允许键和值为 null。键为 null 的元素总是映射到哈希表的第一个位置。 总的来说HashMap 的插入过程涉及计算哈希码、处理哈希冲突、可能的扩容和重新哈希。这个过程优化了速度和内存使用使 HashMap 成为在大多数情况下处理键值对映射的高效选择。
3.3.2 HashTable
Hashtable 是 Java 中的一种基本的集合类它实现了 Map 接口。Hashtable 与 HashMap 类似都提供了基于键的存储和快速检索的能力。然而两者之间存在一些重要的差异。
主要特点 同步性 Hashtable 是同步的。这意味着它是线程安全的多个线程可以同时访问 Hashtable 而不会引起并发问题。但这也意味着相对于非同步的 HashMapHashtable 在性能上可能会有所下降。 不允许 null 键或值 与 HashMap 不同Hashtable 不允许使用 null 作为键或值。 遗留类 Hashtable 是 Java 早期版本的一部分自 Java 1.0 起。随着 Java 集合框架的引入HashMap 成为了更加现代化的选择提供了类似的功能但更高的性能。
主要方法 添加元素 put(Key k, Value v) 方法用于向 Hashtable 中添加键值对。 获取元素 get(Object key) 方法用于根据键获取对应的值。 删除元素 remove(Object key) 方法用于删除指定键的键值对。 遍历映射 可以通过 keySet(), values(), 和 entrySet() 方法来遍历 Hashtable 中的键、值或键值对。
示例代码
import java.util.Hashtable;public class HashtableExample {public static void main(String[] args) {HashtableString, Integer table new Hashtable();table.put(Apple, 40);table.put(Banana, 10);table.put(Cherry, 30);// 获取键对应的值int value table.get(Apple);System.out.println(Value for Apple: value);// 删除键值对table.remove(Banana);System.out.println(Hashtable after removal: table);}
}使用注意事项 线程安全 尽管 Hashtable 是线程安全的但在多线程环境中更推荐使用 ConcurrentHashMap因为它提供了更高的并发性。 性能考虑 由于 Hashtable 的所有公共方法都是同步的这可能会导致在高负载时的性能问题。在单线程应用或不需要同步的场景下HashMap 通常是更好的选择。 遗留类 考虑到 Hashtable 是较早的 Java 集合类新的代码应更倾向于使用 HashMap 或 ConcurrentHashMap。
总的来说虽然 Hashtable 在历史上是 Java 集合框架的重要组成部分但现在通常推荐使用更现代的 HashMap 或 ConcurrentHashMap除非你需要一个线程安全的实现且无法使用 ConcurrentHashMap。
3.3.3 ConcurrentHashMap
ConcurrentHashMap 是 Java 中的一个线程安全的 Map 实现它是专门为高并发场景设计的。ConcurrentHashMap 在 Java 5 中引入作为替代旧的线程安全类 Hashtable 和同步的 Collections.synchronizedMap 的一种更高效的方案。
主要特点 线程安全 ConcurrentHashMap 通过使用分段锁在 Java 8 中改进为使用 CAS 操作、同步块和内部锁提供线程安全这意味着多个线程可以同时安全地访问 ConcurrentHashMap 实例。 高并发性能 相比于 Hashtable 和 Collections.synchronizedMapConcurrentHashMap 提供了更好的并发性能。它在内部对数据进行分段每个段可以独立加锁从而允许多个线程并发地读写。 无锁读取 ConcurrentHashMap 的读操作一般不需要加锁因此读取操作非常快。 弱一致性迭代器 迭代器具有弱一致性而不是快速失败fail-fast。
主要方法
ConcurrentHashMap 实现了 Map 接口因此它提供了类似于其他 Map 实现的方法例如 put, get, remove, containsKey 等。
示例代码
import java.util.concurrent.ConcurrentHashMap;public class ConcurrentHashMapExample {public static void main(String[] args) {ConcurrentHashMapString, Integer map new ConcurrentHashMap();map.put(Key1, 10);map.put(Key2, 20);// 获取值int value map.get(Key1);System.out.println(Value for Key1: value);// 替换值map.replace(Key1, 15);System.out.println(Updated Value for Key1: map.get(Key1));// 迭代映射for (String key : map.keySet()) {System.out.println(key : map.get(key));}}
}使用注意事项 并发修改 在迭代过程中ConcurrentHashMap 允许插入和删除操作迭代器反映了映射的状态可能不反映所有最近的修改。 null 值和键 ConcurrentHashMap 不允许使用 null 作为键或值。 大小估计 size 方法提供的映射大小是一个近似值因为映射可能在计算大小时发生更改。 性能考虑 虽然 ConcurrentHashMap 提供了优异的并发性能但在不需要高并发的场景中使用普通的 HashMap 可能更为高效。
ConcurrentHashMap 是处理高并发应用程序中的映射需求的理想选择尤其是在多个线程需要频繁读写映射时。通过允许同时读取和写入操作ConcurrentHashMap 显著提高了性能同时保持了线程安全。