Map常见API和小tips
tips:put方法的细节,在添加数据的时候,如果键不存在,那么直接把键值对对象添加到map集合当中,方法返回null;如果键是存在的,那么会把原有的键值对对象覆盖,会把被覆盖的值进行返回。
对象的创建
Map<String,String> map=new HashMap<>();
Map的遍历方式
<1>
增强for
Set<String> keys=map.keySet();//获取所有的键,把这些键放到一个单列集合中
for (String key:keys){
System.out.println(map.get(key));
//利用map集合中的键获取对应的值
String value=map.get(key);
System.out.println(value);
}
<2>
通过键值对对象进行遍历
Set<Map.Entry<String,String>> entries=map.entrySet();
//通过一个方法获取所有的键值对对象,返回一个set集合
for(Map.Entry<String,String> entry:entries){
String key=entry.getKey();
String value=entry.getValue();
System.out.println(key+":"+value);
}
前提:借Arrays工具类初步学习Lambda表达式
tips:binarySearch(二分查找法查找元素),数组中的元素必须是有序的、升序的,如果要查找的元素是存在的,那么返回的是真实索引;如果查找的元素是不存在的,返回的是应该插入点(指该元素在插入后数组仍能保持升序的位置)的负值-1。copyOfRange(指定范围拷贝数组),包头不包尾,包左不包右。sort,
sort升序排列的原理和降序排列的实现
Integer[] arr={4,3,5,6,8,11,9};
//第二个参数是一个接口,在调用方法的时候,需要传递这个接口的实现类对象,作为排序的规则。
//在这里,这个实现类我们只要使用一次,所以就没有必要单独的去写一个类,直接采取匿名内部类的方式。
//原理:插入排序+二分查找的方式进行排序。
//默认把0索引的数据当作有序的序列,1索引到最后认为是无序序列。
//遍历无序序列得到里面每一个元素,假设当前遍历到的元素是A元素。
//把A往有序序列中进行插入,在插入的时候,利用二分查找确定A元素的插入点。
//拿着A元素和插入点的元素进行比较,比较的规则是compare方法的方法体。
//如果方法的返回值是负数,拿着A和前面的数据进行比较。
//如果方法的返回值是正数或0,拿着A和后面的数据进行比较。
//直到能确定A的最终位置。
//compare方法的形参:参数一o1,表示在无序序列中,遍历得到的每一个元素;参数二o2,有序序列中的元素
//返回值:负数,表示当前要插入的元素是小的,放在前面;正数,和负数相反;0,表示当前要插入的元素和现在的元素比是一样的,也要放在后面
//简单理解:o1-o2,升序排列;o2-o1,降序排列
Arrays.sort(arr,new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1-o2;
}
});
知识衍生
1.匿名内部类:就是一个"没有名字"的类,在创建对象时直接定义。(
- 正常类:
class MyComparator implements Comparator<Integer>然后起名MyComparator - 匿名类:直接
new Comparator<Integer>() { ... },没有类名)
匿名内部类就是"临时工",用完就扔,专门解决这种一次性的实现需求。
2.怎么知道哪个接口只有一个抽象方法?
方法1:看文档/源码注解:
csharp
@FunctionalInterface // ← 有这个注解就是函数式接口
public interface Comparator<T> {
int compare(T o1, T o2);
// 可以有多个默认方法、静态方法,但只能有一个抽象方法
}
常见函数式接口(函数式接口(或称为只有一个抽象方法的接口)必须提供具体实现(重写)才能使用):
Comparator<T>- 比较器Runnable- 线程任务(只有一个run()方法)Callable<V>- 带返回值的任务Consumer<T>- 消费者(接收参数无返回值)Supplier<T>- 供应商(无参数有返回值)Function<T,R>- 函数(接收T返回R)Predicate<T>- 断言(判断条件)
方法2:看抽象方法数量:
- 只有一个抽象方法 (不包括
default默认方法和static静态方法) - 比如
Comparator:只有compare是抽象方法,其他如reversed()是默认方法
方法3:用Lambda表达式验证,能用Lambda表达式简写的接口就是函数式接口:
java
// 匿名内部类
Arrays.sort(arr, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1 - o2;
}
});
// Lambda简写(证明Comparator是函数式接口)
Arrays.sort(arr, (o1, o2) -> o1 - o2);
3.接口的实现类对象是什么?
接口就像"标准/合同",Comparator这个接口规定:
vbnet
interface Comparator<Integer> {
int compare(Integer o1, Integer o2);
}
任何想当我"实现类"的,(在这个情境下)必须有一个compare方法,能比较两个数谁大谁小,并返回整数。但接口自己是个空壳,没有具体比较逻辑(不知道是升序还是降序)。
"实现类对象"就是"按合同干活的具体工人"。你要用Arrays.sort排序,Java问: "你想按什么规则排序?" 你不能只说"按Comparator规则",你要真的给一个能干活的具体对象。
new Comparator() { ... }→ 当场造一个"临时工人",这个临时工人实现了Comparator接口(因为有compare方法),这个"临时工人对象"直接被传给sort方法使用,用完后这个"临时工人"就被丢弃(因为只需要这一次)。
Lambda表达式(简化匿名内部类的书写)
函数式编程思想,忽略面向对象的复杂语法,强调做什么,而不是谁去做
格式


typescript
public class methodDemo {
public static void main(String[] args) {
//调用一个方法的时候,如果方法的形参是一个接口,那我们要传递这个接口的实现类对象
//如果实现类对象只要用到一次,就可以用匿名内部类的形式进行书写
method(new swim() {
@Override
public void swimming() {
System.out.println("正在游泳~~~");
}
});
//利用lambda表达式进行改写
method(
()->{
System.out.println("正在游泳~~~");
}
);
}
public static void method(swim s){
s.swimming();
}
interface swim{
public abstract void swimming();
}
}
<3>
Lambda表达式
Map<String,String> map=new HashMap<>();
//forEach其实就是利用第二种方式进行遍历,依次得到每一个键和值
//再调用accept方法
map.forEach(new BiConsumer<String, String>() {
@Override
public void accept(String key, String value) {
System.out.println(key+":"+value);
}
});
map.forEach((key,value)->{
System.out.println(key+":"+value);
});
HashMap的特点
- HashMap是Map里的一个实现类
- 没有额外需要学习的特有方法,直接使用Map里面的方法就行了
- 无序、不重复、无索引
- HashMap和HashSet底层原理是一样的,都是哈希表结构(哈希表是一种高效的 "键-值"存储结构 。)你可以把它想象成一个智能抽屉柜:
核心原理(现实比喻):
假设你有一个抽屉柜(哈希表),每件物品(值)都有一个唯一标签(键) 。
但你不用逐个抽屉找,而是有一个智能计算器(哈希函数) ,输入标签就能直接算出抽屉编号:
markdown
例如:存"张三:电话138xxxx"
1. 对"张三"用哈希函数计算 → 得到编号"3"
2. 直接打开3号抽屉 → 存入电话
3. 取电话时同样计算编号 → 瞬间找到
关键特点:
-
快速存取:理想情况下,无论存多少数据,查找时间几乎恒定(O(1))。
-
哈希碰撞:
如果"李四"也计算出编号3(不同标签算出相同编号),则两个物品会放在同一个抽屉的链表里。
-
动态扩容:抽屉不够时,自动增加抽屉并重新分配物品。
在HashMap中的实现:
- 数组(抽屉柜) :存放数据。
- 链表/红黑树(抽屉内的格子) :解决哈希碰撞。
- 哈希函数(智能计算器) :将键(如
"张三")转换为数组下标。
为什么高效?
普通列表找"张三"需要遍历所有项(O(n)),而哈希表通过计算直接定位 ,跳过了大部分比较步骤。这就是为什么HashMap在Java中广泛用于快速查找、去重和缓存。
LinkedHashMap
- 由键决定:有序、不重复、无索引
- 这里的有序指的是保证存储和取出的元素顺序一致
- 原理:底层数据结构依然是哈希表,只是每个键值对元素又额外多一个双链表的机制记录存储的顺序
TreeMap
- TreeMap和TreeSet底层原理一样,都是红黑树结构
- 由键决定特性:不重复、无索引、可排序
- 可排序:对键进行排序
- 小tips:默认按照键的从小到大进行排序,也可以自己规定键的排序规则
示例1
java
package TreeMapDdemo;
import java.util.Comparator;
import java.util.TreeMap;
public class TreeMapdemo {
public static void main(String[] args) {
//创建集合对象
TreeMap<Integer,String> tm = new TreeMap<>();
//添加元素
tm.put(1,"奥利奥");
tm.put(5,"雪碧");
tm.put(3,"可乐");
tm.put(2,"乐事");
tm.put(4,"好丽友");
System.out.println(tm);//Integer,Double按照键升序排列,String按照键字母在ASCII码表中对应的数字升序进行排列
TreeMap<Integer,String> tm2 = new TreeMap<>(new Comparator<Integer>(){
@Override
public int compare(Integer o1, Integer o2) {
return o2-o1;//降序,o1:当前要添加的元素,o2:当前已经在红黑树中存在的元素
}
});
tm2.put(1,"奥利奥");
tm2.put(5,"雪碧");
tm2.put(3,"可乐");
tm2.put(2,"乐事");
tm2.put(4,"好丽友");
System.out.println(tm2);
}
}
输出结果:
ini
{1=奥利奥, 2=乐事, 3=可乐, 4=好丽友, 5=雪碧}
{5=雪碧, 4=好丽友, 3=可乐, 2=乐事, 1=奥利奥}
示例2
ini
package TreeMapDdemo;
import java.util.TreeMap;
public class TreeMapdemo2 {
public static void main(String[] args) {
TreeMap<Student,String> tm=new TreeMap<>();
Student s1=new Student("zhangsan",19);
Student s2=new Student("lisi",18);
Student s3=new Student("wangwu",20);
tm.put(s1,"江苏");
tm.put(s2,"浙江");
tm.put(s3,"上海");
System.out.println(tm);
}
}
typescript
package TreeMapDdemo;
public class Student implements Comparable<Student>{
private int age;
private String name;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Student(String name, int age) {
this.age = age;
this.name = name;
}
public Student() {
}
@Override
public String toString() {
return "Student [age=" + age + ", name=" + name + "]";
}
@Override
public int compareTo(Student o) {
//比较规则:按照学生年龄升序排列,年龄一样按照姓名的字母排列,同姓名年龄视为同一个人
//this:表示当前要添加的元素
//o:表示已经在红黑树中存在的元素
//返回值:负数表示当前要添加的元素是小的,存左边,整数反之,0表示已经存在,舍弃
int i=this.getAge()-o.getAge();
i=i==0?this.getName().compareTo(o.getName()):i;
return i;
}
}
示例3
arduino
package TreeMapDdemo;
import java.util.TreeMap;
public class TreaaMapdemo3 {
public static void main(String[] args) {
//统计字符串中每一个字符出现的次数
//利用map集合进行统计,HashMAp、TreeMap,键表示要统计的内容,值表示次数
//未要求对结果排序,默认使用HashMap,要求排序使用TreeMap
String str="aabdcbcbbcaabb";
TreeMap<Character,Integer> map=new TreeMap<>();
for (int i = 0; i < str.length(); i++) {
char ch=str.charAt(i);
if(map.containsKey(ch)){
int count=map.get(ch);
count++;
map.put(ch,count);
}
else{
map.put(ch,1);
}
}
//遍历集合,更改格式
StringBuilder sb=new StringBuilder();
map.forEach((k,v)-> sb.append(k).append("(").append(v).append(")"));
System.out.println(sb.toString());
}
}
输出结果:a(4)b(6)c(3)d(1)
知识衍生
StringBuilder、StringJoiner与String的区别与联系
一、String(不可变字符串)
核心特性:字符串常量,一旦创建内容不可改变
ini
String s1 = "hello";
s1 = s1 + " world"; // 看似修改,实际创建了新对象
特点:
- 不可变性:每次"修改"都会创建新对象
- 存储位置:字符串常量池(享元模式)
- 线程安全:因为不可变,天然线程安全
- 效率问题:频繁拼接时性能差,产生大量垃圾对象
二、StringBuilder(可变字符串)
基本特性:
ini
StringBuilder sb = new StringBuilder();
sb.append("Hello");
sb.append(" ");
sb.append("World");
String result = sb.toString(); // 一次性转换为String
核心知识点:
-
可变性:内部维护可变的char数组,直接修改内容
-
非线程安全:不提供同步机制,性能更高
-
初始容量:默认16字符,可自动扩容
-
常用方法:
scssappend() // 追加内容 insert() // 插入内容 delete() // 删除指定范围 reverse() // 反转字符串 toString() // 转换为String
使用场景:
- 循环拼接字符串(图中用法)
- 需要大量字符串操作的场景
- 单线程环境下
性能优势示例:
ini
// 错误:产生大量临时对象
String result = "";
for (int i = 0; i < 1000; i++) {
result += i; // 每次循环创建新String对象
}
// 正确:只创建一个StringBuilder对象
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
sb.append(i); // 修改同一个对象
}
String result = sb.toString();
三、StringJoiner(字符串连接器)
基本特性(Java 8+):
csharp
StringJoiner sj = new StringJoiner(", ", "[", "]");
sj.add("A").add("B").add("C");
String result = sj.toString(); // 结果:[A, B, C]
核心知识点:
-
专门用于连接多个字符串
-
支持分隔符、前缀、后缀
-
底层实现:基于StringBuilder
-
常用方法:
scssadd() // 添加元素 merge() // 合并另一个StringJoiner length() // 当前长度 toString() // 转换为String
使用场景:
- 集合转字符串(完美替代手动拼接)
- 格式化输出
- CSV文件生成
示例对比:
ini
// 传统方式(繁琐)
StringBuilder sb = new StringBuilder("[");
for (String s : list) {
if (sb.length() > 1) sb.append(", ");
sb.append(s);
}
sb.append("]");
// StringJoiner方式(简洁)
StringJoiner sj = new StringJoiner(", ", "[", "]");
list.forEach(sj::add);
四、三者关系与选择策略
关系图:
scss
┌─────────────────────────────────────────┐
│ String(不可变) │
│ toString()方法返回 │
└─────┬────────────────────┬──────────────┘
│ │
↓ ↓
┌─────────────────┐ ┌─────────────────────┐
│ StringBuilder │←──│ StringJoiner │
│ (可变,高效拼接) │ │ (专门用于连接,封装了│
│ │ │ StringBuilder功能)│
└─────────────────┘ └─────────────────────┘
选择策略:
| 场景 | 推荐工具 | 理由 |
|---|---|---|
| 固定的字符串常量 | String | 不可变,线程安全,可缓存 |
| 单线程大量字符串拼接 | StringBuilder | 性能最优,避免对象创建 |
| 多线程字符串操作 | StringBuffer | 线程安全版本 |
| 集合转指定格式字符串 | StringJoiner | 语法简洁,功能专一 |
| 简单连接少量字符串 | String的 + |
代码简洁,编译器优化 |
五、最佳实践总结
1. 性能优先原则
ini
// 避免
String s = "";
for (Data data : list) {
s += data.toString(); // 性能杀手!
}
// 采用
StringBuilder sb = new StringBuilder();
for (Data data : list) {
sb.append(data.toString());
}
String result = sb.toString();
2. 格式化优先原则
ini
// 避免手动拼接分隔符
StringBuilder sb = new StringBuilder();
boolean first = true;
for (String item : items) {
if (!first) sb.append(", ");
sb.append(item);
first = false;
}
// 使用StringJoiner
StringJoiner sj = new StringJoiner(", ");
items.forEach(sj::add);
3. 链式调用与流畅API
sql
String result = new StringJoiner("\n")
.add("姓名: " + name)
.add("年龄: " + age)
.add("地址: " + address)
.toString();
4. 内存优化
scss
// 预估容量,避免频繁扩容
StringBuilder sb = new StringBuilder(1024); // 预分配空间
// 清空重用(注意:new StringBuilder更清晰)
sb.setLength(0); // 清空内容
六、补充知识点
1. StringBuffer
- 线程安全的StringBuilder
- 方法使用synchronized修饰
- 性能略低于StringBuilder
2. 编译器优化
vbscript
// 编译器会将以下代码优化为StringBuilder
String s = "A" + "B" + "C";
// 优化后:new StringBuilder().append("A").append("B").append("C").toString()
3. Java 8+的String增强
vbnet
// 静态join方法
String result = String.join(", ", "A", "B", "C"); // "A, B, C"
String result = String.join(", ", list); // 连接集合
七、一句话总结
- String :字符串常量,只读,适用于固定文本
- StringBuilder :字符串构造器 ,适用于单线程大量拼接
- StringJoiner :字符串连接器 ,适用于格式化拼接集合元素
- 三者最终都要调用
toString()返回真正的String对象