Skip to content

2024年3月随笔

Java实现遍历的几种方式

在Java中,并没有内置的 "fo" 循环结构,你可能指的是 "for" 循环。在Java中,实现 "for" 循环主要有以下几种方式:

  1. 基本for循环:这是最常见的for循环形式,用于遍历一个固定次数的序列。
java
for (int i = 0; i < 10; i++) {
    System.out.println(i);
}
  1. 增强型for循环(也称为"foreach"循环):这种循环主要用于遍历数组或集合中的元素。
java
int[] numbers = {1, 2, 3, 4, 5};
for (int number : numbers) {
    System.out.println(number);
}

或者对于集合:

java
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
for (String name : names) {
    System.out.println(name);
}
  1. 标签化for循环:在Java 7及更高版本中,你可以使用标签来标识循环,并在循环内部使用 breakcontinue 语句与标签一起控制循环。
java
outer: for (int i = 0; i < 5; i++) {
    for (int j = 0; j < 5; j++) {
        if (j == 2) {
            break outer;  // 这将终止外部循环
        }
        System.out.println(i + ", " + j);
    }
}
  1. 无限循环:这种循环没有明确的终止条件,通常与其他控制结构(如 break 语句)一起使用以在某个条件满足时终止循环。
java
while (true) {
    // 循环体
    if (someCondition) {
        break;  // 当someCondition为真时,终止循环
    }
}

注意,虽然Java没有内置的 "fo" 循环结构,但上述的for循环结构在大多数编程语言中都是通用的,包括 "for", "foreach", "标签化for" 和 "无限循环"。

Java字符串拼接的几种方式

在Java中,有多种方法可以拼接(连接)字符串。以下是一些常用的方法:

  1. 使用加号(+)操作符: 这是最简单的方法,但对于大量的字符串拼接,它可能不是最高效的。

    java
    String str1 = "Hello";
    String str2 = "World";
    String result = str1 + " " + str2; // "Hello World"
  2. 使用StringBuilderStringBuilder是一个可变对象,适用于在循环中拼接大量字符串,因为它比使用加号操作符更有效率。

    java
    StringBuilder sb = new StringBuilder();
    sb.append("Hello");
    sb.append(" ");
    sb.append("World");
    String result = sb.toString(); // "Hello World"
  3. 使用StringBufferStringBufferStringBuilder类似,但它是线程安全的,因此适用于多线程环境。然而,由于同步的开销,它在单线程环境下通常比StringBuilder慢。

    java
    StringBuffer sb = new StringBuffer();
    sb.append("Hello");
    sb.append(" ");
    sb.append("World");
    String result = sb.toString(); // "Hello World"
  4. 使用String.format()方法: 这是一个格式化字符串的方法,可以用来拼接带有格式化占位符的字符串。

    java
    String str1 = "Hello";
    String str2 = "World";
    String result = String.format("%s %s", str1, str2); // "Hello World"
  5. 使用String.join()方法: 从Java 8开始,String类提供了一个静态方法join(),它可以使用指定的分隔符将元素连接成一个字符串。

    java
    String delimiter = " ";
    String[] words = {"Hello", "World"};
    String result = String.join(delimiter, words); // "Hello World"
  6. 使用字符串插值(Java 15及以上): 从Java 15开始,可以使用文本块和字符串插值来拼接字符串,这是一种新的、简洁的语法。

    java
    String str1 = "Hello";
    String str2 = "World";
    String result = str1 + " " + str2; // "Hello World"
    
    // 使用文本块和字符串插值
    String interpolated = """
        Hello
        World
    """;
    // 结果为:
    // Hello
    // World

在选择拼接字符串的方法时,应考虑性能、线程安全性和代码可读性。对于简单的字符串拼接,使用加号操作符通常是可以的。然而,在需要拼接大量字符串或需要高性能的场景下,建议使用StringBuilderStringBuffer。如果你需要格式化的字符串输出,String.format()String.join()可能是更好的选择。而文本块和字符串插值则是Java 15及以后版本中提供的新特性,可以使代码更加清晰和简洁。

Java中HashMap如何更新某个key

在Java中,HashMap 不直接支持修改键(key)的值。如果你想修改HashMap中某个键的值,你需要先删除旧的键值对,然后添加一个新的键值对。

下面是一个简单的示例,演示了如何在HashMap中更新键的值:

java
import java.util.HashMap;

public class HashMapExample {
    public static void main(String[] args) {
        // 创建一个HashMap
        HashMap<String, String> map = new HashMap<>();
        
        // 添加一些键值对
        map.put("key1", "value1");
        map.put("key2", "value2");
        
        // 打印原始HashMap
        System.out.println("原始HashMap: " + map);
        
        // 假设我们想要更新key1的值
        String oldKey = "key1";
        String newValue = "newValue1";
        
        // 如果键存在于HashMap中,删除旧的键值对
        if (map.containsKey(oldKey)) {
            map.remove(oldKey);
        }
        
        // 添加新的键值对
        map.put(oldKey, newValue);
        
        // 打印更新后的HashMap
        System.out.println("更新后的HashMap: " + map);
    }
}

在这个示例中,我们首先创建了一个HashMap,并向其中添加了一些键值对。然后,我们想要更新key1的值。我们首先检查key1是否存在于HashMap中,如果存在,我们就删除它。然后,我们添加一个新的键值对,其中键是key1,值是newValue1。最后,我们打印出更新后的HashMap

请注意,这种方法在并发环境中可能会导致问题,因为在你检查键是否存在和删除键之间,其他线程可能已经修改了HashMap。在这种情况下,你可能需要使用ConcurrentHashMap或其他并发控制机制来确保数据的一致性。

Java中如何判断HashMap中是否包含某个key

在Java中,要判断HashMap是否包含某个特定的键(key),你可以使用HashMap类的containsKey()方法。这个方法会返回一个布尔值,如果HashMap中包含指定的键,那么返回true;如果不包含,则返回false

下面是一个简单的示例代码,演示了如何使用containsKey()方法来检查HashMap中是否包含某个键:

java
import java.util.HashMap;

public class HashMapExample {
    public static void main(String[] args) {
        // 创建一个HashMap
        HashMap<String, String> map = new HashMap<>();
        
        // 向HashMap中添加一些键值对
        map.put("key1", "value1");
        map.put("key2", "value2");
        map.put("key3", "value3");
        
        // 要检查的键
        String keyToCheck = "key2";
        
        // 使用containsKey()方法检查HashMap中是否包含该键
        if (map.containsKey(keyToCheck)) {
            System.out.println("HashMap中包含键: " + keyToCheck);
        } else {
            System.out.println("HashMap中不包含键: " + keyToCheck);
        }
    }
}

在这个示例中,我们创建了一个HashMap并添加了一些键值对。然后,我们定义了一个要检查的键keyToCheck,并使用containsKey()方法来检查HashMap中是否包含这个键。根据containsKey()方法的返回值,我们打印出相应的消息。

请注意,containsKey()方法的时间复杂度通常是O(1),这意味着它在大多数情况下都很快,因为它直接使用了哈希表的数据结构来查找键。然而,在极端情况下(例如当哈希函数导致所有键都映射到同一个位置时),性能可能会降低。

Java中遍历HashMap中的key

在Java中,Map接口的keySet()方法用于获取映射中所有键的Set视图。这个Set视图上的迭代器会按照映射中键的顺序(如果映射本身有顺序的话)进行迭代。

keySet()方法返回的是一个Set对象,它包含了映射中的所有键。由于这个Set是映射的视图,所以对Set进行的任何修改都会反映到原始映射中,反之亦然。这意味着,如果你通过keySet()方法获得的Set对象删除了一个键,那么映射中相应的键值对也会被删除。

下面是一个使用keySet()方法的例子:

java
import java.util.HashMap;
import java.util.Set;

public class MapExample {
    public static void main(String[] args) {
        // 创建一个HashMap
        HashMap<String, String> map = new HashMap<>();
        
        // 向HashMap中添加一些键值对
        map.put("key1", "value1");
        map.put("key2", "value2");
        map.put("key3", "value3");
        
        // 使用keySet()方法获取所有键的Set视图
        Set<String> keys = map.keySet();
        
        // 遍历键的Set视图
        for (String key : keys) {
            System.out.println("键: " + key + ", 值: " + map.get(key));
        }
        
        // 通过keySet()方法删除一个键
        keys.remove("key2");
        
        // 再次遍历以展示key2已被删除
        System.out.println("删除键'key2'后的映射:");
        for (String key : map.keySet()) {
            System.out.println("键: " + key + ", 值: " + map.get(key));
        }
    }
}

在上面的例子中,我们首先创建了一个HashMap并向其中添加了一些键值对。然后,我们调用keySet()方法获取所有键的Set视图,并遍历它来打印每个键及其对应的值。接下来,我们通过keySet()方法返回的Set对象删除了键"key2",然后再次遍历映射以显示键"key2"已被删除。

需要注意的是,虽然keySet()方法返回的Set视图上的迭代器会按照映射中键的顺序进行迭代,但这个顺序并不一定是插入顺序。如果需要保持插入顺序,可以考虑使用LinkedHashMap代替普通的HashMap

Spring Cloud两种定义过滤器的方式区别

通过 implements GlobalFilter, Ordered 接口和继承 AbstractGatewayFilterFactory 实现自定义全局 filter 的这两种方法在 Spring Cloud Gateway 中有着不同的用途和适用场景,下面我将详细解释它们的异同。

1. 继承 AbstractGatewayFilterFactory

用途: 这种方式主要用于创建可配置的过滤器工厂,这些工厂可以生成应用于特定路由的过滤器。这些过滤器工厂可以在路由配置文件中通过 YAML 或 Properties 文件进行配置。

特点

  • 可配置性AbstractGatewayFilterFactory 允许你定义配置参数,这些参数可以在路由配置中设置,使得过滤器更加灵活和可重用。
  • 路由级别:通过 AbstractGatewayFilterFactory 创建的过滤器是应用于特定路由的,而不是全局的。
  • 灵活性:由于使用了工厂模式,你可以创建多个不同的过滤器工厂,每个工厂生成具有不同行为的过滤器。

示例

java
@Component
public class CustomGatewayFilterFactory extends AbstractGatewayFilterFactory<CustomGatewayFilterFactory.Config> {

    public CustomGatewayFilterFactory() {
        super(Config.class);
    }

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            // 自定义过滤逻辑
            return chain.filter(exchange);
        };
    }

    public static class Config {
        // 配置参数
    }
}

2. 实现 GlobalFilter, Ordered 接口

用途: 这种方式用于创建全局过滤器,这些过滤器将应用于通过 Spring Cloud Gateway 的所有请求,无论它们是否匹配特定的路由配置。

特点

  • 全局性:全局过滤器不依赖于路由配置,它们对所有经过 Gateway 的请求都生效。
  • 顺序性:由于实现了 Ordered 接口,你可以控制全局过滤器的执行顺序。
  • 固定逻辑:与 AbstractGatewayFilterFactory 相比,全局过滤器的逻辑通常是固定的,不依赖于外部配置。

示例

java
@Component
public class CustomGlobalFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 自定义全局过滤逻辑
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        // 定义过滤器的执行顺序
        return -1;
    }
}

异同点总结

相同点

  • 两者都是 Spring Cloud Gateway 的过滤器实现方式,用于处理进入网关的请求和从网关发出的响应。
  • 两者都可以包含自定义的业务逻辑,以实现特定的功能,如认证、日志记录、请求修改等。

不同点

  • 作用范围AbstractGatewayFilterFactory 创建的过滤器是路由级别的,而实现 GlobalFilter 的过滤器是全局级别的。
  • 配置性AbstractGatewayFilterFactory 允许通过配置文件来定义过滤器的行为,而全局过滤器通常是硬编码的,没有外部配置。
  • 复用性:通过工厂模式创建的过滤器工厂可以被多个路由复用,而全局过滤器则是单例的,对所有请求生效。
  • 执行顺序:虽然两者都可以通过实现 Ordered 接口来控制执行顺序,但全局过滤器的顺序控制通常更加重要,因为它们会影响所有请求。

在实际应用中,根据需求选择适合的过滤器实现方式。如果你需要基于路由配置来动态调整过滤器的行为,那么使用 AbstractGatewayFilterFactory 是更好的选择。如果你需要实现一些对所有请求都生效的固定逻辑,比如全局的认证或日志记录,那么实现 GlobalFilter 更为合适。

使用div标签实现输入框并设置placeholder

之前前端项目中有一个需求,需要自定义一个输入框,这个输入框有很多定制需求,当时是采用div + contenteditable 属性来实现的,并且要实现placeholder输入提示符的效果

html
<div class="editable" contenteditable="true" data-placeholder="请输入内容..."></div>
css
.editable {  
  min-height: 20px;  
  border: 1px solid #ccc;  
  padding: 5px;  
  position: relative;  
  overflow: hidden; /* 隐藏超出容器的内容 */  
  white-space: nowrap; /* 防止文本换行 */  
  text-overflow: ellipsis; /* 超出时显示省略号 */  
}  
  
.editable[contenteditable="true"]:empty:before {  
  content: attr(data-placeholder);  
  color: #ccc;  
  position: absolute;  
  pointer-events: none;  
  /* 以下属性确保占位符在超出容器时不会破坏样式 */  
  white-space: nowrap;  
  overflow: hidden;  
  text-overflow: ellipsis;  
  max-width: 100%; /* 确保占位符宽度不超过容器 */  
  box-sizing: border-box; /* 考虑padding和border */  
  padding: inherit; /* 继承容器的padding */  
  margin-left: -5px; /* 修正由于padding导致的偏移 */  
}

Css中通过attr属性就可以获取到绑定这个类名的Html内容

Java实现将时间戳字符串转为年月日字符串

java
import java.text.SimpleDateFormat;
import java.util.Date;

public class TimestampFormatter {
    public static void main(String[] args) {
        // 假设这是你的时间戳字符串,表示毫秒数
        String timestampString = "1691110640560";

        // 将字符串转换为long类型的时间戳
        long timestamp = Long.parseLong(timestampString);

        // 使用时间戳创建一个Date对象
        Date date = new Date(timestamp);

        // 创建SimpleDateFormat对象并指定日期格式
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");

        // 使用SimpleDateFormat格式化Date对象为字符串
        String formattedDate = simpleDateFormat.format(date);

        // 输出格式化后的日期字符串
        System.out.println(formattedDate);
    }
}

Es使用聚合查询时,结果值如何返回指定的额外字段

可以使用 aggs键用于定义子聚合,子聚合中可以使用 includes指定包含的字段

json
{  
  "aggregations": {  
    "dateGroup": {  
      "date_histogram": {  
        "field": "@timestamp",  
        "format": "yyyy-MM-dd",  
        "interval": "1d"  
      },  
      "aggs": { // 注意这里使用的是 "aggs" 而不是 "agg" 或其他  
        "top_result_name": { // 聚合名称可以自定义  
          "top_hits": {  
            "size": 1,  
            "_source": {  
              "includes": ["result.name"] // 仅返回 result.name 字段  
            }  
          }  
        }  
      }  
    }  
  }  
}

Es查询中使用脚本修改查询条件

最近在排查公司的一个Es查询bug的时候用到了脚本查询的功能,特此记录下来,下面是原本的查询语句:

GET /agg/_search

json
{
  "size": 0,
  "query": {
    "bool": {
      "must": [
        {
          "wildcard": {
            "result.events.segmentation.agg.early_warning_status": {
              "wildcard": "11*",
              "boost": 1
            }
          }
        },
        {
          "range": {
            "result.events.timestamp": {
              "from": 1675699200,
              "to": 1709827200,
              "include_lower": true,
              "include_upper": true,
              "boost": 1
            }
          }
        },
        {
          "terms": {
            "result.device_id.keyword": [
              "34f31d67611f453fa9e56e15bd8279fa12",
              "xxxxxx",
              "xxxxxx"
            ],
            "boost": 1
          }
        },
        {
          "bool": {
            "adjust_pure_negative": true,
            "boost": 1
          }
        }
      ],
      "adjust_pure_negative": true,
      "boost": 1
    }
  },
  "aggregations": {
    "statusGroup": {
      "terms": {
        "field": "result.events.segmentation.agg.early_warning_status.keyword",
        "size": 100000000,
        "min_doc_count": 1,
        "shard_min_doc_count": 0,
        "show_term_doc_count_error": false,
        "order": [
          {
            "_count": "desc"
          },
          {
            "_key": "asc"
          }
        ]
      }
    }
  }
}

可以发现result.events.timestamp字段查询的时间戳是13位的,也就是毫秒级的,但是我的索引中,result.events.timestamp可能是10位的的(秒级),也可能是13位(毫秒)的,这就导致了部分数据没有匹配到。

接下来使用查询脚本编写判断语句,关键点在script

GET /agg/_search

json
{
  "size": 0,
  "query": {
    "bool": {
      "should": [
        {
          "range": {
            "result.events.timestamp": {
              "from": 1675699200000,
              "to": 1709827200000,
              "include_lower": true,
              "include_upper": true,
              "boost": 1
            }
          }
        },
        {
          "script": {
            "script": {
              "lang": "painless",
              "source": "long timestamp = doc['result.events.timestamp'].value; if (timestamp < 10000000000L) { timestamp *= 1000; }  if (timestamp >= params.fromTimestamp && timestamp <= params.toTimestamp) {return true;}return false;",
              "params": {
                "fromTimestamp": 1675699200000,
                "toTimestamp": 1709827200000
              }
            }
          }
        }
      ],
      "minimum_should_match": 1, // 确保至少有一个should子句匹配
      "must": [
        {
          "wildcard": {
            "result.events.segmentation.agg.early_warning_status": {
              "wildcard": "11*",
              "boost": 1
            }
          }
        },
        {
          "terms": {
            "result.device_id.keyword": [
              "8QX1ADNVQVQL8QX1ADNVQVQNSSNNMAKL1SO1SSNNMAKL1SOO",
              "xxxxxxxxxxx",
            ],
            "boost": 1
          }
        },
        {
          "bool": {
            "adjust_pure_negative": true,
            "boost": 1
          }
        }
      ],
      "adjust_pure_negative": true,
      "boost": 1
    }
  },
  "aggregations": {
    "statusGroup": {
      "terms": {
        "field": "result.events.segmentation.agg.early_warning_status.keyword",
        "size": 100000000,
        "min_doc_count": 1,
        "shard_min_doc_count": 0,
        "show_term_doc_count_error": false,
        "order": [
          {
            "_count": "desc"
          },
          {
            "_key": "asc"
          }
        ]
      }
    }
  }
}

我使用的是RestHighLevelClient 库,顺便记录使用RestHighLevelClient如何创建脚本查询器:

java
import org.elasticsearch.action.search.SearchRequest;  
import org.elasticsearch.action.search.SearchResponse;  
import org.elasticsearch.client.RequestOptions;  
import org.elasticsearch.client.RestHighLevelClient;  
import org.elasticsearch.index.query.BoolQueryBuilder;  
import org.elasticsearch.index.query.QueryBuilders;  
import org.elasticsearch.index.query.RangeQueryBuilder;  
import org.elasticsearch.index.query.ScriptQueryBuilder;  
import org.elasticsearch.script.Script;  
import org.elasticsearch.script.ScriptType;  
import org.elasticsearch.search.builder.SearchSourceBuilder;  
  
// 假设restHighLevelClient已经初始化并可用  
RestHighLevelClient restHighLevelClient;  
  
try {  
    // 创建范围查询构建器  
    RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("result.events.timestamp")  
            .from(1675699200000L)  
            .to(1709827200000L)  
            .includeLower(true)  
            .includeUpper(true);  
  
    // 创建脚本查询构建器  
    ScriptQueryBuilder scriptQueryBuilder = new ScriptQueryBuilder(  
        new Script(  
            ScriptType.INLINE,   
            "painless",   
            "long timestamp = doc['result.events.timestamp'].value;" +  
            "if (timestamp < 10000000000L) {" +  
            "  timestamp *= 1000;" + // 将秒转换为毫秒  
            "}" +  
            "return (timestamp >= params.from && timestamp <= params.to);",  
            Collections.singletonMap("from", 1675699200000L),  
            Collections.singletonMap("to", 1709827200000L)  
        )  
    );  
  
    // 创建bool查询构建器,将范围查询和脚本查询作为should子句  
    BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery()  
            .should(rangeQueryBuilder)  
            .should(scriptQueryBuilder)  
            .minimumShouldMatch(1); // 至少匹配一个should子句  
  
    // 创建搜索源构建器并设置查询  
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();  
    searchSourceBuilder.query(boolQueryBuilder);  
  
    // 创建搜索请求并设置搜索源  
    SearchRequest searchRequest = new SearchRequest("your_index_name"); // 替换为你的索引名  
    searchRequest.source(searchSourceBuilder);  
  
    // 执行搜索请求  
    SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);  
  
    // 处理搜索结果...  
    // 例如:打印查询到的文档数量  
    System.out.println("Total hits: " + searchResponse.getHits().getHits().length);  
  
} catch (Exception e) {  
    // 处理异常...  
    e.printStackTrace();  
}

记一次Mysql密码修改失效问题

在MySQL 5.7中,mysql.user表中的host列决定了用户可以从哪些主机登录。localhost通常指的是本地登录,而%是一个通配符,表示从任何主机都可以登录。

如果你的mysql.user表中root用户既有localhost又有%作为host,那么:

  1. root@localhost 允许root用户从MySQL服务器本地登录。
  2. root@% 允许root用户从任何远程主机登录。

要修改密码,你应该考虑你的安全需求。如果你只想允许本地登录,那么只需要修改root@localhost的密码。如果你也想允许远程登录,那么应该修改root@%的密码。

但是,出于安全考虑,通常建议:

  • 保留root@localhost,这样你可以从服务器本地进行管理。
  • 创建一个新的用户账号,并为其分配必要的权限,然后允许这个用户从特定的远程主机登录(而不是使用root)。

如果你确实需要修改root的密码,你可以使用以下SQL命令:

  1. 修改root@localhost的密码:
sql
SET PASSWORD FOR 'root'@'localhost' = PASSWORD('新密码');

或者使用MySQL 5.7.6及更高版本的语法:

sql
ALTER USER 'root'@'localhost' IDENTIFIED BY '新密码';
  1. 修改root@%的密码:
sql
SET PASSWORD FOR 'root'@'%' = PASSWORD('新密码');

或者使用MySQL 5.7.6及更高版本的语法:

sql
ALTER USER 'root'@'%' IDENTIFIED BY '新密码';

我使用了ALTER修改了root@localhost的密码,但没有修改到root@%的密码,就导致密码并未生效

上次更新于: