20230727-随笔

目录

  • List删除满足条件的元素,并且避免索引错误或并发修改异常常用方法
    • 使用迭代器删除元素
    • 通过逆向循环删除元素
    • Java8+ 的 removeIf()方法
  • 获取不到日志内容问题排查
    • 尝试解决
    • 最终解决

List删除满足条件的元素,并且避免索引错误或并发修改异常常用方法

使用迭代器删除元素

List list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
    String element = iterator.next();
    if (element.equals("B")) {
        iterator.remove(); // 使用迭代器的 remove() 方法删除元素
    }
}

通过逆向循环删除元素

List list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
for (int i = list.size() - 1; i >= 0; i--) {
    if (list.get(i).equals("B")) {
    	list.remove(i); // 通过逆向循环删除元素
    }
}

Java8+ 的 removeIf()方法

  • 简单的,使用 Lambda 表达式
List list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
list.removeIf(element -> element.equals("B"));
  • 复杂的,通过使用匿名类实现 Predicate 接口的方式
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
list.add("D");
// 假如 List tests,则为new Predicate()
list.removeIf(new Predicate<String>() {
    @Override
    public boolean test(String element) {
        // 在这里编写复杂的逻辑来判断是否删除元素
        // 返回 true 表示删除该元素,返回 false 表示保留该元素
        return element.startsWith("A") || element.endsWith("D");
    }
});

获取不到日志内容问题排查

通过FileUtils.readFileToString()读取日志文件获取文件内容,日志文件有数据,但是获取不到内容,可能原因:

  • 文件路径问题:请确保logFile参数指定的文件路径是正确的路径,并且可以访问该文件。您可以在代码中添加一些调试语句,输出logFile的路径,然后验证该路径是否正确。
  • 文件读取权限:确保您正在以足够的权限运行代码,以便能够读取指定的日志文件。如果您在一个受限制的环境中运行代码,则可能需要提升您的权限或更改文件的权限设置。
  • 文件内容编码问题:如果日志文件使用了特定的编码方式进行编码(例如UTF-8),请确保在使用FileUtils.readFileToString()时指定正确的编码方式。可以尝试使用FileUtils.readFileToString(logFile, “UTF-8”)来明确指定编码方式。
  • 文件访问冲突:如果您的代码与其他程序同时访问该日志文件,可能发生访问冲突或文件锁定导致无法读取文件内容。在调试期间,确保没有其他程序或进程锁定或占用了该日志文件。

尝试解决

  • 改成异步方法:日志依然读取不到,但是作业状态确实是完结状态,日志应该已经写完了(springboot异步方法配置:启动类添加@EnableAsync,异步方法上添加@Async注解
  • 判断文件写入完成后再进行读取操作:依然无效,而且假如中间有几秒没有写入日志,但是写文件并没有结束,容易误判结束
import java.io.File;

public class LogFileReader {
    private static final int CHECK_INTERVAL_MS = 1000; // 检查间隔时间,单位为毫秒
    public static void main(String[] args) {
        String filePath = "path/to/logfile.txt"; // 替换为您的日志文件路径
        File logFile = new File(filePath);
        // 获取初始文件长度和修改时间
        long initialSize = logFile.length();
        long lastModified = logFile.lastModified();
        while (true) {
            // 等待指定时间间隔
            try {
                Thread.sleep(CHECK_INTERVAL_MS);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 检查文件是否发生变化
            long currentSize = logFile.length();
            long currentModified = logFile.lastModified();

            if (currentSize == initialSize && currentModified == lastModified) {
                // 文件大小和修改时间未发生变化,文件写入完成
                String logContent = FileUtils.readFileToString(logFile, "UTF-8");
                System.out.println(logContent);
                break; // 退出循环
            }
            // 更新初始文件长度和修改时间
            initialSize = currentSize;
            lastModified = currentModified;
        }
    }

}
  • 替换FileUtils.readFileToString(),通过BufferedReader读取日志:BufferedReader 和 FileReader 来逐行读取文件的内容。通过调用 readLine() 方法,可以一次读取一行内容,并将其存储到 line 变量中。然后可以对每行内容进行处理,例如打印出来或进行其他操作。使用这种方式,即使文件未完全写入,也能读取到已经写入的部分内容。但是结果是依然读取不到。
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;

public class LogFileReader {
    public static void main(String[] args) {
        String filePath = "path/to/logfile.txt"; // 替换为您的日志文件路径
        File logFile = new File(filePath);

        try (BufferedReader reader = new BufferedReader(new FileReader(logFile))) {
            String line;
            while ((line = reader.readLine()) != null) {
                // 处理每行日志内容
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

最终解决

没有调用项目中日志处理类的end()方法导致的。对于BufferedReader来说,在文件正在写入的过程中,它是可以读取到已经写入部分内容的。之所以依然查看不到日志内容,是因为没有调用 end() 方法来完成日志的写入操作和关闭文件流的操作,可能导致读取不到已经写入的内容:

  • 写入缓冲区未刷新:使用FileWriter来写入文件,而FileWriter内部使用了写入缓冲区,它会先将数据写入缓冲区,然后根据一定条件将缓冲区的数据刷新到文件中。如果没有调用end()方法,缓冲区的数据可能还未被刷新到文件中,从而导致BufferedReader读取不到正确的文件内容。
  • 文件流未关闭: 在调用end()方法时,会关闭文件流(FileWriter),关闭文件流会将缓冲区的剩余数据刷新到文件中,并释放系统资源。如果没有调用end()方法关闭文件流,可能会导致缓冲区中的数据没有被刷新到文件中,从而无法读取到正确的文件内容。

以下为日志处理类源码,供参考分析:

import com.google.common.base.Throwables;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.map.HashedMap;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 日志处理器.
 */
@Slf4j
@Component
public class LogHandler {

    private static Map<Long, List<String>> logMap = new HashedMap();
    private static Map<Long, FileWriter> fileWriterMap = new HashMap<>();

    private static String taskLogPath;

    @PostConstruct
    public void init() {
        write();
    }

    @PreDestroy
    public void destory() {
        fileWriterMap.forEach((batchId, fw) -> {
            try {
                fw.flush();
                fw.close();
            } catch (IOException e) {
                log.error("关闭流失败:{}", e);
            }
        });
    }

    public static synchronized void start(Long batchId) {
        try {
            if (logMap.containsKey(batchId)) {
                log.info("已存在批次号:{}的任务!", batchId);
                return;
            }

            File logFile = new File(getLogPath(batchId));
            if (logFile.exists()) {
                log.info("批次号:{}已存在日志文件,不能重复提交!", batchId);
                return;
            }

            logFile.createNewFile();
            logMap.put(batchId, new ArrayList<String>());
            fileWriterMap.put(batchId, new FileWriter(logFile, true));
        } catch (Exception e) {
            log.error("批次号:{}开始记录任务失败:{}", batchId, e);
        }
    }

    /**
     * 记录日志.
     */
    public static void log(Long batchId, String logContent) {
        if (null != logMap.get(batchId)) {
            logMap.get(batchId).add(DateUtils.formatDateTime(DateUtils.now()) + " 批次号:{" + batchId + "} " + logContent);
        }
    }

    /**
     * 记录异常.
     */
    public static void exception(Long batchId, Exception e) {
        if (null != logMap.get(batchId)) {
            logMap.get(batchId).add(DateUtils.formatDateTime(DateUtils.now()) + " " + Throwables.getStackTraceAsString(e));
        }
    }

    /**
     * 日志记录到文件中.
     */
    private static void write() {
        Thread logWriteThread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    logMap.forEach((batchId, logContentList) -> {
                        if (CollectionUtils.isEmpty(logContentList)) {
                            return;
                        }

                        try {
                            FileWriter fw = fileWriterMap.get(batchId);
                            if (null != fw) {
                                writeLog2File(fw, logContentList, true);
                                logContentList.clear();
                            }
                        } catch (Exception e) {
                            log.error("批次号为:{}的日志写入文件报错:{}", batchId, e);
                        }
                    });

                    sleep(15);
                }
            }
        });
        logWriteThread.start();
    }

    /**
     * 该batchId的日志记录结束.
     */
    public static synchronized void end(Long batchId) {
        if (!fileWriterMap.containsKey(batchId)) {
            log.info("批次号为:{}的输出流不存在", batchId);
            return;
        }

        FileWriter fw = fileWriterMap.get(batchId);
        try {
            List<String> logContentList = logMap.get(batchId);
            if (CollectionUtils.isNotEmpty(logContentList)) {
                try {
                    writeLog2File(fw, logContentList, true);
                    logContentList = null;
                } catch (Exception e) {
                    log.error("批次号为:{}的日志写入文件报错:{}", batchId, e);
                }
            }
            logMap.remove(batchId);
            fileWriterMap.remove(batchId);
        } finally {
            try {
                fw.close();
            } catch (IOException e) {
                log.error("关闭流错误:{}", e);
            }
        }
    }

    private static void writeLog2File(FileWriter fw, List<String> logContentList, boolean flush) throws Exception {
        for (String logContent : logContentList) {
            fw.write(logContent);
            fw.write("\r\n");
        }

        if (flush) {
            fw.flush();
        }
    }

    public static String getLogPath(Long batchId) {
        return taskLogPath + batchId + ".log";
    }

    private static void sleep(long timeOut) {
        try {
            Thread.sleep(timeOut * 1000);
        } catch (InterruptedException e) {
            log.error("sleep error:{}", e);
        }
    }

    @Value("${log_path}")
    public void setTaskLogPath(String taskLogPath) {
        LogHandler.taskLogPath = taskLogPath;
    }

}

你可能感兴趣的:(#,Java,Daily,Record,list删除元素,读取不到日志内容,判断文件是否写入结束,BufferedReader)