Java 编程问题:六、Java I/O 路径、文件、缓冲区、扫描和格式化6

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: Java 编程问题:六、Java I/O 路径、文件、缓冲区、扫描和格式化

通过FileFilter过滤


FileFilter是另一个可以用来过滤文件和文件夹的函数式接口。例如,让我们只过滤文件夹:

File[] folders = path.toFile().listFiles(new FileFilter() {
  @Override
  public boolean accept(File file) {
    return file.isDirectory();
  }
});


我们可以在函数式风格上做同样的事情:

File[] folders = path.toFile().listFiles((File file) 
  -> file.isDirectory());


让我们更简洁一点:

File[] folders = path.toFile().listFiles(f -> f.isDirectory());


最后,我们可以通过成员引用:

File[] folders = path.toFile().listFiles(File::isDirectory);



145 循环字节缓冲区


JavaNIO.2API 附带了一个名为java.nio.ByteBuffer的字节缓冲区的实现。基本上,这是一个字节数组(byte[]),由一组专门用于操作该数组的方法包装(例如,get()、put()等等)。循环缓冲区(循环缓冲区、环形缓冲区或循环队列)是端到端连接的固定大小的缓冲区。下图显示了循环队列的外观:


循环缓冲区依赖于预先分配的数组(预先分配的容量),但某些实现可能也需要调整大小的功能。元素写入/添加到后面(尾部),从前面删除/读取(头部),如下图所示:


对于主操作,即读(获取)和写(设置),循环缓冲区维护一个指针(读指针和写指针)。两个指针都围绕着缓冲区容量。我们可以找出有多少元素可以读取,有多少空闲的插槽可以随时写入。此操作发生在O(1)。


循环字节缓冲区是字节的循环缓冲区;它可以是字符或其他类型。这正是我们要在这里实现的。我们可以从编写实现的存根开始,如下所示:

public class CircularByteBuffer {
  private int capacity;
  private byte[] buffer;
  private int readPointer;
  private int writePointer;
  private int available;
  CircularByteBuffer(int capacity) {
    this.capacity = capacity;
    buffer = new byte[capacity];
  }
  public synchronized int available() {
    return available;
  }
  public synchronized int capacity() {
    return capacity;
  }
  public synchronized int slots() {
    return capacity - available;
  }
  public synchronized void clear() {
    readPointer = 0;
    writePointer = 0;
    available = 0;
  }
  ...
}


现在,让我们集中精力放置(写入)新字节和读取(获取)现有字节。例如,容量为 8 的循环字节缓冲区可以表示为:


让我们看看每一步都发生了什么:


   循环字节缓冲区为空,两个指针都指向插槽 0(第一个插槽)。

   我们将hello对应的 5 个字节放在缓冲区中,readPointer保持不变,而writePointer指向插槽 5。

   我们得到与h对应的字节,所以readPointer移动到插槽 1。

   最后,我们尝试将world的字节放入缓冲区。这个字由 5 个字节组成,但在达到缓冲区容量之前,我们只有 4 个空闲插槽。这意味着我们只能写与world对应的字节。


现在,让我们看一下下图中的场景:


从左到右,步骤如下:


   前两个步骤与前一个场景中的步骤相同。

   我们得到了hell的字节。这将把readPointer移动到位置 4。

   最后,我们将world的字节放入缓冲区。这一次,字放入缓冲区,writePointer移动到槽 2。


基于此流程,我们可以轻松实现一种方法,将一个字节放入缓冲区,另一个从缓冲区获取一个字节,如下所示:

public synchronized boolean put(int value) {
  if (available == capacity) {
    return false;
  }
  buffer[writePointer] = (byte) value;
  writePointer = (writePointer + 1) % capacity;
  available++;
  return true;
}
public synchronized int get() {
  if (available == 0) {
    return -1;
  }
  byte value = buffer[readPointer];
  readPointer = (readPointer + 1) % capacity;
  available--;
  return value;
}


如果我们检查 JavaNIO.2ByteBufferAPI,我们会注意到它公开了get()和put()方法的几种风格。例如,我们应该能够将一个byte[]传递给get()方法,这个方法应该将一系列元素从缓冲区复制到这个byte[]中。从当前的readPointer开始从缓冲区读取元素,从指定的offset开始在给定的byte[]中写入元素。

下图显示了writePointer大于readPointer的情况:


在左边,我们正在读 3 个字节。这将readPointer从其初始插槽 1 移动到插槽 4。在右边,我们正在读取 4 个(或超过 4 个)字节。由于只有 4 个字节可用,readPointer从其初始插槽移动到与writePointer相同的插槽(插槽 5)。

现在,我们来分析一个例子,writePointer小于readPointer:


在左边,我们正在读 3 个字节。这将readPointer从其初始插槽 6 移动到插槽 1。在右边,我们正在读取 4 个(或超过 4 个)字节。这会将readPointer从其初始插槽 6 移动到插槽 2(与writePointer相同的插槽)。


既然我们已经考虑到了这两个用例,我们可以编写一个get()方法,以便将一系列字节从缓冲区复制到给定的byte[],如下所示(该方法尝试从缓冲区读取len字节,然后将它们写入给定的byte[],从给定的offset开始):

public synchronized int get(byte[] dest, int offset, int len) {
  if (available == 0) {
    return 0;
  }
  int maxPointer = capacity;
  if (readPointer < writePointer) {
    maxPointer = writePointer;
  }
  int countBytes = Math.min(maxPointer - readPointer, len);
  System.arraycopy(buffer, readPointer, dest, offset, countBytes);
  readPointer = readPointer + countBytes;
  if (readPointer == capacity) {
    int remainingBytes = Math.min(len - countBytes, writePointer);
    if (remainingBytes > 0) {
      System.arraycopy(buffer, 0, dest,
        offset + countBytes, remainingBytes);
      readPointer = remainingBytes;
      countBytes = countBytes + remainingBytes;
    } else {
      readPointer = 0;
    }
  }
  available = available - countBytes;
  return countBytes;
}


现在,让我们集中精力将给定的byte[]放入缓冲区。从指定的offset开始从给定的byte[]读取元素,并从当前的writePointer开始写入缓冲区。下图显示了writePointer大于readPointer的情况:


在左边,我们有缓冲区的初始状态。所以,readPointer指向插槽 2,writePointer指向插槽 5。在写入 4 个字节(右侧)之后,我们可以看到,readPointer没有受到影响,writePointer指向插槽 1。

另一个用例假设readPointer大于writePointer:


在左边,我们有缓冲区的初始状态。所以,readPointer指向插槽 4,writePointer指向插槽 2。在写入 4 个字节(右侧)之后,我们可以看到,readPointer没有受到影响,writePointer指向插槽 4。请注意,只有两个字节被成功写入。这是因为我们在写入所有 4 个字节之前已经达到了缓冲区的最大容量。


既然我们已经考虑到了这两个用例,我们可以编写一个put()方法,以便将给定的byte[]中的一系列字节复制到缓冲区中,如下(该方法尝试从给定的offset开始从给定的byte[]读取len字节,并尝试从当前的writePointer开始将其写入缓冲区):

public synchronized int put(byte[] source, int offset, int len) {
  if (available == capacity) {
    return 0;
  }
  int maxPointer = capacity;
  if (writePointer < readPointer) {
    maxPointer = readPointer;
  }
  int countBytes = Math.min(maxPointer - writePointer, len);
  System.arraycopy(source, offset, buffer, writePointer, countBytes);
  writePointer = writePointer + countBytes;
  if (writePointer == capacity) {
    int remainingBytes = Math.min(len - countBytes, readPointer);
    if (remainingBytes > 0) {
      System.arraycopy(source, offset + countBytes,
        buffer, 0, remainingBytes);
      writePointer = remainingBytes;
      countBytes = countBytes + remainingBytes;
    } else {
      writePointer = 0;
    }
  }
  available = available + countBytes;
  return countBytes;
}


如前所述,有时需要调整缓冲区的大小。例如,我们可能希望通过简单地调用resize()方法将其大小增加一倍。基本上,这意味着将所有可用字节(元素)复制到一个容量加倍的新缓冲区中:

public synchronized void resize() {
  byte[] newBuffer = new byte[capacity * 2];
  if (readPointer < writePointer) {
    System.arraycopy(buffer, readPointer, newBuffer, 0, available);
  } else {
    int bytesToCopy = capacity - readPointer;
    System.arraycopy(buffer, readPointer, newBuffer, 0, bytesToCopy);
    System.arraycopy(buffer, 0, newBuffer, bytesToCopy, writePointer);
  }
  buffer = newBuffer;
  capacity = buffer.length;
  readPointer = 0;
  writePointer = available;
}

查看本书附带的源代码,看看它是如何完整工作的。



146 分词文件


文件中的内容并不总是以可以立即处理的方式接收,并且需要一些额外的步骤,以便为处理做好准备。通常,我们需要对文件进行标记,并从不同的数据结构(数组、列表、映射等)中提取信息。

例如,让我们考虑一个文件,clothes.txt

Path path = Paths.get("clothes.txt");


其内容如下:

Top|white\10/XXL&amp;Swimsuit|black\5/L
Coat|red\11/M&amp;Golden Jacket|yellow\12/XLDenim|Blue\22/M


此文件包含一些服装物品及其详细信息,这些物品以&amp;字符分隔。单一条款如下:

article name | color \ no. available items / size


这里,我们有几个分隔符(&amp;、|、\、/)和一个非常具体的格式。


现在,让我们来看看几个解决方案,它们将从这个文件中提取信息并将其标记为一个List。我们将在工具类FileTokenizer中收集这些信息。


在List中获取物品的一种解决方案依赖于String.split()方法。基本上,我们必须逐行读取文件并对每行应用String.split()。每行分词的结果通过List.addAll()方法收集在List中:


public static List<String> get(Path path, 
    Charset cs, String delimiter) throws IOException {
  String delimiterStr = Pattern.quote(delimiter);
  List<String> content = new ArrayList<>();
  try (BufferedReader br = Files.newBufferedReader(path, cs)) {
    String line;
    while ((line = br.readLine()) != null) {
      String[] values = line.split(delimiterStr);
      content.addAll(Arrays.asList(values));
    }
  }
  return content;
}


使用&amp;分隔符调用此方法将产生以下输出:

[Top|white\10/XXL, Swimsuit|black\5/L, Coat|red\11/M, Golden Jacket|yellow\12/XL, Denim|Blue\22/M]


上述溶液的另一种风味可以依赖于Collectors.toList()而不是Arrays.asList()

public static List<String> get(Path path, 
    Charset cs, String delimiter) throws IOException {
  String delimiterStr = Pattern.quote(delimiter);
  List<String> content = new ArrayList<>();
  try (BufferedReader br = Files.newBufferedReader(path, cs)) {
    String line;
    while ((line = br.readLine()) != null) {
      content.addAll(Stream.of(line.split(delimiterStr))
        .collect(Collectors.toList()));
    }
  }
  return content;
}


或者,我们可以通过Files.lines()以惰性方式处理内容:

public static List<String> get(Path path, 
    Charset cs, String delimiter) throws IOException {
  try (Stream<String> lines = Files.lines(path, cs)) {
    return lines.map(l -> l.split(Pattern.quote(delimiter)))
      .flatMap(Arrays::stream)
      .collect(Collectors.toList());
  }
}


对于相对较小的文件,我们可以将其加载到内存中并进行相应的处理:

Files.readAllLines(path, cs).stream()
  .map(l -> l.split(Pattern.quote(delimiter)))
  .flatMap(Arrays::stream)
  .collect(Collectors.toList());


另一种解决方案可以依赖于 JDK8 的Pattern.splitAsStream()方法。此方法从给定的输入序列创建流。为了便于修改,这次我们通过Collectors.joining(";")收集结果列表:

public static List<String> get(Path path, 
    Charset cs, String delimiter) throws IOException {
  Pattern pattern = Pattern.compile(Pattern.quote(delimiter));
  List<String> content = new ArrayList<>();
  try (BufferedReader br = Files.newBufferedReader(path, cs)) {
    String line;
    while ((line = br.readLine()) != null) {
      content.add(pattern.splitAsStream(line)
        .collect(Collectors.joining(";")));
    }
  }
  return content;
}


我们用&amp;分隔符调用这个方法:

List<String> tokens = FileTokenizer.get(
  path, StandardCharsets.UTF_8, "&amp;");


结果如下:

[Top|white\10/XXL;Swimsuit|black\5/L, Coat|red\11/M;Golden Jacket|yellow\12/XL, Denim|Blue\22/M]


到目前为止,提出的解决方案通过应用一个分隔符来获得文章列表。但有时,我们需要应用更多的分隔符。例如,假设我们希望获得以下输出(列表):

[Top, white, 10, XXL, Swimsuit, black, 5, L, Coat, red, 11, M, Golden Jacket, yellow, 12, XL, Denim, Blue, 22, M]


为了获得这个列表,我们必须应用几个分隔符(&amp;|\/。这可以通过使用String.split()并将基于逻辑OR运算符(x|y)的正则表达式传递给它来实现:

public static List<String> getWithMultipleDelimiters(
    Path path, Charset cs, String...delimiters) throws IOException {
  String[] escapedDelimiters = new String[delimiters.length];
  Arrays.setAll(escapedDelimiters, t -> Pattern.quote(delimiters[t]));
  String delimiterStr = String.join("|", escapedDelimiters);
  List<String> content = new ArrayList<>();
  try (BufferedReader br = Files.newBufferedReader(path, cs)) {
    String line;
    while ((line = br.readLine()) != null) {
      String[] values = line.split(delimiterStr);
      content.addAll(Arrays.asList(values));
    }
  }
  return content;
}


让我们用分隔符(&amp;、|、\和/调用此方法以获得所需的结果:


List<String> tokens = FileTokenizer.getWithMultipleDelimiters(
  path, StandardCharsets.UTF_8, 
    new String[] {"&amp;", "|", "\\", "/"});


好的,到目前为止,很好!所有这些解决方案都基于String.split()和Pattern.splitAsStream()。另一组解决方案可以依赖于StringTokenizer类(它在性能方面并不出色,所以请小心使用它)。此类可以对给定的字符串应用一个(或多个)分隔符,并公开控制它的两个主要方法,即hasMoreElements()和nextToken():


public static List<String> get(Path path,
    Charset cs, String delimiter) throws IOException {
  StringTokenizer st;
  List<String> content = new ArrayList<>();
  try (BufferedReader br = Files.newBufferedReader(path, cs)) {
    String line;
    while ((line = br.readLine()) != null) {
      st = new StringTokenizer(line, delimiter);
      while (st.hasMoreElements()) {
        content.add(st.nextToken());
      }
    }
  }
  return content;
}

也可与Collectors配合使用:

public static List<String> get(Path path, 
    Charset cs, String delimiter) throws IOException {
  List<String> content = new ArrayList<>();
  try (BufferedReader br = Files.newBufferedReader(path, cs)) {
    String line;
    while ((line = br.readLine()) != null) {
      content.addAll(Collections.list(
          new StringTokenizer(line, delimiter)).stream()
        .map(t -> (String) t)
        .collect(Collectors.toList()));
    }
  }
  return content;
}


如果我们使用//分隔多个分隔符,则可以使用多个分隔符:

public static List<String> getWithMultipleDelimiters(
    Path path, Charset cs, String...delimiters) throws IOException {
  String delimiterStr = String.join("//", delimiters);
  StringTokenizer st;
  List<String> content = new ArrayList<>();
  try (BufferedReader br = Files.newBufferedReader(path, cs)) {
    String line;
    while ((line = br.readLine()) != null) {
      st = new StringTokenizer(line, delimiterStr);
      while (st.hasMoreElements()) {
        content.add(st.nextToken());
      }
    }
  }
  return content;
}

为了获得更好的性能和正则表达式支持(即高灵活性),建议使用String.split()而不是StringTokenizer。从同一类别中,也考虑“使用扫描器”部分。



147 将格式化输出直接写入文件


假设我们有 10 个数字(整数和双精度)并且我们希望它们在一个文件中被很好地格式化(有缩进、对齐和一些小数,以保持可读性和有用性)。


在我们的第一次尝试中,我们像这样将它们写入文件(没有应用格式):

Path path = Paths.get("noformatter.txt");
try (BufferedWriter bw = Files.newBufferedWriter(path,
    StandardCharsets.UTF_8, StandardOpenOption.CREATE,
      StandardOpenOption.WRITE)) {
  for (int i = 0; i < 10; i++) {
    bw.write("| " + intValues[i] + " | " + doubleValues[i] + " | ");
    bw.newLine();
  }
}


前面代码的输出类似于下图左侧所示:


但是,我们希望得到上图右侧所示的结果。为了解决这个问题,我们需要使用String.format()方法。此方法允许我们将格式规则指定为符合以下模式的字符串:


%[flags][width][.precision]conversion-character


现在,让我们看看这个模式的每个组成部分是什么:


   [flags]是可选的,包括修改输出的标准方法。通常,它们用于格式化整数和浮点数。


   [width]是可选的,并设置输出的字段宽度(写入输出的最小字符数)。


   [.precision]可选,指定浮点值的精度位数(或从String中提取的子串长度)。


   conversion-character是强制的,它告诉我们参数的格式。最常用的转换字符如下:


       s:用于格式化字符串

       d:用于格式化十进制整数

       f:用于格式化浮点数

       t:用于格式化日期/时间值


作为行分隔符,我们可以使用%n。


有了这些格式化规则的知识,我们可以得到如下所示(%6s用于整数,%.3f用于双精度):

Path path = Paths.get("withformatter.txt");
try (BufferedWriter bw = Files.newBufferedWriter(path,
    StandardCharsets.UTF_8, StandardOpenOption.CREATE, 
      StandardOpenOption.WRITE)) {
  for (int i = 0; i<10; i++) {
    bw.write(String.format("| %6s | %.3f |",
      intValues[i], doubleValues[i]));
    bw.newLine();
  }
}

可以通过Formatter类提供另一种解决方案。此类专用于格式化字符串,并使用与String.format()相同的格式化规则。它有一个format()方法,我们可以用它覆盖前面的代码片段:

Path path = Paths.get("withformatter.txt");
try (Formatter output = new Formatter(path.toFile())) {
  for (int i = 0; i < 10; i++) {
    output.format("| %6s | %.3f |%n", intValues[i], doubleValues[i]);
  }
}


只格式化整数的数字怎么样?


好吧,我们可以通过应用一个DecimalFormat和一个字符串格式化程序来获得它,如下所示:


Path path = Paths.get("withformatter.txt");
DecimalFormat formatter = new DecimalFormat("###,### bytes");
try (Formatter output = new Formatter(path.toFile())) {
 for (int i = 0; i < 10; i++) {
   output.format("%12s%n", formatter.format(intValues[i]));
 }
}




148 使用扫描器


Scanner公开了一个 API,用于解析字符串、文件、控制台等中的文本。解析是将给定的输入分词并根据需要返回它的过程(例如,整数、浮点、双精度等)。默认情况下,Scanner使用空格(默认分隔符)解析给定的输入,并通过一组nextFoo()方法(例如,next()、nextLine()、nextInt()、nextDouble()等)公开令牌。


从同一类问题出发,也考虑“分词文件”部分。


例如,假设我们有一个文件(doubles.txt),其中包含由空格分隔的双数,如下图所示:


如果我们想获得这个文本作为双精度文本,那么我们可以读取它并依赖于一段意大利面代码来标记并将其转换为双精度文本。或者,我们可以依赖于Scanner及其nextDouble()方法,如下所示:

try (Scanner scanDoubles = new Scanner(
    Path.of("doubles.txt"), StandardCharsets.UTF_8)) {
  while (scanDoubles.hasNextDouble()) {
    double number = scanDoubles.nextDouble();
    System.out.println(number);
  }
}

上述代码的输出如下:

23.4556
1.23
...



但是,文件可能包含不同类型的混合信息。例如,下图中的文件(people.txt)包含由不同分隔符(逗号和分号)分隔的字符串和整数:


Scanner公开了一个名为useDelimiter()的方法。此方法采用String或Pattern类型的参数,以指定应用作正则表达式的分隔符:

try (Scanner scanPeople = new Scanner(Path.of("people.txt"),
    StandardCharsets.UTF_8).useDelimiter(";|,")) {
  while (scanPeople.hasNextLine()) {
    System.out.println("Name: " + scanPeople.next().trim());
    System.out.println("Surname: " + scanPeople.next());
    System.out.println("Age: " + scanPeople.nextInt());
    System.out.println("City: " + scanPeople.next());
  }
}

使用此方法的输出如下:

Name: Matt
Surname: Kyle
Age: 23
City: San Francisco
...


从 JDK9 开始,Scanner公开了一个名为tokens()的新方法。此方法返回来自Scanner的分隔符分隔的令牌流。例如,我们可以用它来解析people.txt文件并在控制台上打印出来,如下所示:

try (Scanner scanPeople = new Scanner(Path.of("people.txt"),
    StandardCharsets.UTF_8).useDelimiter(";|,")) {
  scanPeople.tokens().forEach(t -> System.out.println(t.trim()));
}


使用上述方法的输出如下:

Matt
Kyle
23
San Francisco
...



或者,我们可以通过空格连接令牌:

try (Scanner scanPeople = new Scanner(Path.of("people.txt"),
    StandardCharsets.UTF_8).useDelimiter(";|,")) {
  String result = scanPeople.tokens()
    .map(t -> t.trim())
    .collect(Collectors.joining(" "));
}


在“大文件搜索”部分中,有一个示例说明如何使用此方法搜索文件中的某一段文本。

使用上述方法的输出如下:

Matt Kyle 23 San Francisco Darel Der 50 New York ...


tokens()方法方面,JDK9 还附带了一个名为findAll()的方法。这是一种非常方便的方法,用于查找所有与某个正则表达式相关的标记(以StringPattern形式提供)。此方法返回一个Stream<MatchResult>,可以这样使用:

try (Scanner sc = new Scanner(Path.of("people.txt"))) {
  Pattern pattern = Pattern.compile("4[0-9]");
  List<String> ages = sc.findAll(pattern)
    .map(MatchResult::group)
    .collect(Collectors.toList());
  System.out.println("Ages: " + ages);
}


前面的代码选择了所有表示 40-49 岁年龄的标记,即 40、43 和 43。

如果我们希望解析控制台中提供的输入,Scanner是一种方便的方法:

Scanner scanConsole = new Scanner(System.in);
String name = scanConsole.nextLine();
String surname = scanConsole.nextLine();
int age = scanConsole.nextInt();
// an int cannot include "\n" so we need
//the next line just to consume the "\n"
scanConsole.nextLine();
String city = scanConsole.nextLine();


注意,对于数字输入(通过nextInt()、nextFloat()等读取),我们也需要使用换行符(当我们点击Enter时会出现这种情况)。基本上,Scanner在解析一个数字时不会获取这个字符,因此它将进入下一个标记。如果我们不通过添加一个nextLine()代码行来消耗它,那么从这一点开始,输入将变得不对齐,并导致InputMismatchException类型的异常或过早结束。


JDK10 中引入了支持字符集的Scanner构造器。


我们来看看Scanner和BufferedReader的区别。




扫描器与BufferedReader


那么,我们应该使用Scanner还是BufferedReader?好吧,如果我们需要解析这个文件,那么Scanner就是最好的方法,否则BufferedReader更合适,对它们进行一个正面比较,会发现:


BufferedReader比Scanner快,因为它不执行任何解析操作。


BufferedReader在阅读方面优于Scanner在句法分析方面优于BufferedReader。

默认情况下,BufferedReader使用 8KB 的缓冲区,Scanner使用 1KB 的缓冲区。

BufferedReader非常适合读取长字符串,而Scanner更适合于短输入。

BufferedReader同步,但Scanner不同步。

Scanner可以使用BufferedReader,反之则不行。如下代码所示:


try (Scanner scanDoubles = new Scanner(Files.newBufferedReader(
    Path.of("doubles.txt"), StandardCharsets.UTF_8))) { 
  ...
}



总结

我们已经到了本章的结尾,在这里我们讨论了各种特定于 I/O 的问题,从操作、行走和监视路径到流文件以及读/写文本和二进制文件的有效方法,我们已经讨论了很多。

相关文章
|
8天前
|
Java 开发者
Java多线程编程中的常见误区与最佳实践####
本文深入剖析了Java多线程编程中开发者常遇到的几个典型误区,如对`start()`与`run()`方法的混淆使用、忽视线程安全问题、错误处理未同步的共享变量等,并针对这些问题提出了具体的解决方案和最佳实践。通过实例代码对比,直观展示了正确与错误的实现方式,旨在帮助读者构建更加健壮、高效的多线程应用程序。 ####
|
10天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
39 2
|
14天前
|
JSON Java Apache
非常实用的Http应用框架,杜绝Java Http 接口对接繁琐编程
UniHttp 是一个声明式的 HTTP 接口对接框架,帮助开发者快速对接第三方 HTTP 接口。通过 @HttpApi 注解定义接口,使用 @GetHttpInterface 和 @PostHttpInterface 等注解配置请求方法和参数。支持自定义代理逻辑、全局请求参数、错误处理和连接池配置,提高代码的内聚性和可读性。
|
7天前
|
Java 开发者
Java多线程编程的艺术与实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的技术文档,本文以实战为导向,通过生动的实例和详尽的代码解析,引领读者领略多线程编程的魅力,掌握其在提升应用性能、优化资源利用方面的关键作用。无论你是Java初学者还是有一定经验的开发者,本文都将为你打开多线程编程的新视角。 ####
|
6天前
|
存储 安全 Java
Java多线程编程中的并发容器:深入解析与实战应用####
在本文中,我们将探讨Java多线程编程中的一个核心话题——并发容器。不同于传统单一线程环境下的数据结构,并发容器专为多线程场景设计,确保数据访问的线程安全性和高效性。我们将从基础概念出发,逐步深入到`java.util.concurrent`包下的核心并发容器实现,如`ConcurrentHashMap`、`CopyOnWriteArrayList`以及`BlockingQueue`等,通过实例代码演示其使用方法,并分析它们背后的设计原理与适用场景。无论你是Java并发编程的初学者还是希望深化理解的开发者,本文都将为你提供有价值的见解与实践指导。 --- ####
|
9天前
|
安全 Java 开发者
Java多线程编程中的常见问题与解决方案
本文深入探讨了Java多线程编程中常见的问题,包括线程安全问题、死锁、竞态条件等,并提供了相应的解决策略。文章首先介绍了多线程的基础知识,随后详细分析了每个问题的产生原因和典型场景,最后提出了实用的解决方案,旨在帮助开发者提高多线程程序的稳定性和性能。
|
12天前
|
监控 安全 Java
Java中的多线程编程:从入门到实践####
本文将深入浅出地探讨Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的摘要形式,本文将以一个简短的代码示例作为开篇,直接展示多线程的魅力,随后再详细解析其背后的原理与实现方式,旨在帮助读者快速理解并掌握Java多线程编程的基本技能。 ```java // 简单的多线程示例:创建两个线程,分别打印不同的消息 public class SimpleMultithreading { public static void main(String[] args) { Thread thread1 = new Thread(() -> System.out.prin
|
14天前
|
存储 缓存 安全
在 Java 编程中,创建临时文件用于存储临时数据或进行临时操作非常常见
在 Java 编程中,创建临时文件用于存储临时数据或进行临时操作非常常见。本文介绍了使用 `File.createTempFile` 方法和自定义创建临时文件的两种方式,详细探讨了它们的使用场景和注意事项,包括数据缓存、文件上传下载和日志记录等。强调了清理临时文件、确保文件名唯一性和合理设置文件权限的重要性。
36 2
|
6月前
|
消息中间件 前端开发 Java
java学习路径
【4月更文挑战第9天】java学习路径
84 1
|
6月前
|
设计模式 前端开发 安全
Java是一种广泛使用的编程语言,其学习路径可以大致分为以下几个阶段
【4月更文挑战第9天】Java是一种广泛使用的编程语言,其学习路径可以大致分为以下几个阶段
48 1
下一篇
无影云桌面