欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Log4j定時(shí)打印日志及添加模塊名配置的Java代碼實(shí)例

 更新時(shí)間:2016年01月20日 08:51:43   作者:coder_xia  
這篇文章主要介紹了Log4j定時(shí)打印日志及添加模塊名配置的Java代碼實(shí)例,Log4j是Apache的一個(gè)開源Java日志項(xiàng)目,需要的朋友可以參考下

配置間隔時(shí)間,定時(shí)打印日志
 接到個(gè)需求,通過(guò)log4j定時(shí)打印日志,需求描述如下:需要能夠定時(shí)打印日志,時(shí)間間隔可配。說(shuō)到定時(shí),首先想到了DailyRollingFileAppender類,各種定時(shí),根據(jù)datePattern,這個(gè)可以參考類SimpleDateFormat類,常見的一些定時(shí)設(shè)置如下:

  • '.'yyyy-MM: 每月 
  • '.'yyyy-ww: 每周  
  • '.'yyyy-MM-dd: 每天 
  • '.'yyyy-MM-dd-a: 每天兩次 
  • '.'yyyy-MM-dd-HH: 每小時(shí) 
  • '.'yyyy-MM-dd-HH-mm: 每分鐘  

    通過(guò)觀察發(fā)現(xiàn)沒(méi)有n分鐘類似的日期格式,因此,在DailyRollingFileAppender類基礎(chǔ)上進(jìn)行自定義類的編寫。過(guò)程如下:

  1)拷貝DailyRollingFileAppender類源碼并并改名MinuteRollingAppender,為了在log4j.xml中配置,增加配置項(xiàng)intervalTime并添加set、get方法;

private int intervalTime = 10; 

  2)由于DailyRollingFileAppender類使用了RollingCalendar類來(lái)計(jì)算下一次間隔時(shí)間,而需要傳遞參數(shù)intervalTime,因此修改RollingCalendar類為內(nèi)部類;由于其方法就是根據(jù)datePattern來(lái)計(jì)算下一次rollOver動(dòng)作的時(shí)間,此時(shí)不需要其他的時(shí)間模式,修改方法如下:

public Date getNextCheckDate(Date now) 
{ 
 this.setTime(now); 
 this.set(Calendar.SECOND, 0); 
 this.set(Calendar.MILLISECOND, 0); 
 this.add(Calendar.MINUTE, intervalTime); 
 return getTime(); 
} 

  3)按照分鐘可配時(shí),時(shí)間模式就需要禁用了,將其改為static final,響應(yīng)的去掉其get、set方法和MinuteRollingAppender構(gòu)造函數(shù)中的datePattern參數(shù)

private static String DATEPATTERN = "'.'yyyy-MM-dd-HH-mm'.log'"; 

    同樣,服務(wù)于多種datePattern的方法computeCheckPeriod()也可以刪除; 至此改造就完成了,成品類如下:

package net.csdn.blog; 
 
import java.io.File; 
import java.io.IOException; 
import java.io.InterruptedIOException; 
import java.text.SimpleDateFormat; 
import java.util.Calendar; 
import java.util.Date; 
import java.util.GregorianCalendar; 
 
import org.apache.log4j.FileAppender; 
import org.apache.log4j.Layout; 
import org.apache.log4j.helpers.LogLog; 
import org.apache.log4j.spi.LoggingEvent; 
 
/** 
 * 按分鐘可配置定時(shí)appender 
 * 
 * @author coder_xia 
 * 
 */ 
public class MinuteRollingAppender extends FileAppender 
{ 
 /** 
  * The date pattern. By default, the pattern is set to "'.'yyyy-MM-dd" 
  * meaning daily rollover. 
  */ 
 private static String DATEPATTERN = "'.'yyyy-MM-dd-HH-mm'.log'"; 
 /** 
  * 間隔時(shí)間,單位:分鐘 
  */ 
 private int intervalTime = 10; 
 
 /** 
  * The log file will be renamed to the value of the scheduledFilename 
  * variable when the next interval is entered. For example, if the rollover 
  * period is one hour, the log file will be renamed to the value of 
  * "scheduledFilename" at the beginning of the next hour. 
  * 
  * The precise time when a rollover occurs depends on logging activity. 
  */ 
 private String scheduledFilename; 
 
 /** 
  * The next time we estimate a rollover should occur. 
  */ 
 private long nextCheck = System.currentTimeMillis() - 1; 
 
 Date now = new Date(); 
 
 SimpleDateFormat sdf; 
 
 RollingCalendar rc = new RollingCalendar(); 
 
 /** 
  * The default constructor does nothing. 
  */ 
 public MinuteRollingAppender() 
 { 
 } 
 
 /** 
  * Instantiate a <code>MinuteRollingAppender</code> and open the file 
  * designated by <code>filename</code>. The opened filename will become the 
  * ouput destination for this appender. 
  */ 
 public MinuteRollingAppender(Layout layout, String filename) 
   throws IOException 
 { 
  super(layout, filename, true); 
  activateOptions(); 
 } 
 
 /** 
  * @return the intervalTime 
  */ 
 public int getIntervalTime() 
 { 
  return intervalTime; 
 } 
 
 /** 
  * @param intervalTime 
  *   the intervalTime to set 
  */ 
 public void setIntervalTime(int intervalTime) 
 { 
  this.intervalTime = intervalTime; 
 } 
 
 @Override 
 public void activateOptions() 
 { 
  super.activateOptions(); 
  if (fileName != null) 
  { 
   now.setTime(System.currentTimeMillis()); 
   sdf = new SimpleDateFormat(DATEPATTERN); 
   File file = new File(fileName); 
   scheduledFilename = fileName 
     + sdf.format(new Date(file.lastModified())); 
 
  } 
  else 
  { 
   LogLog 
     .error("Either File or DatePattern options are not set for appender [" 
       + name + "]."); 
  } 
 } 
 
 /** 
  * Rollover the current file to a new file. 
  */ 
 void rollOver() throws IOException 
 { 
  String datedFilename = fileName + sdf.format(now); 
  // It is too early to roll over because we are still within the 
  // bounds of the current interval. Rollover will occur once the 
  // next interval is reached. 
  if (scheduledFilename.equals(datedFilename)) 
  { 
   return; 
  } 
 
  // close current file, and rename it to datedFilename 
  this.closeFile(); 
 
  File target = new File(scheduledFilename); 
  if (target.exists()) 
  { 
   target.delete(); 
  } 
 
  File file = new File(fileName); 
  boolean result = file.renameTo(target); 
  if (result) 
  { 
   LogLog.debug(fileName + " -> " + scheduledFilename); 
  } 
  else 
  { 
   LogLog.error("Failed to rename [" + fileName + "] to [" 
     + scheduledFilename + "]."); 
  } 
 
  try 
  { 
   // This will also close the file. This is OK since multiple 
   // close operations are safe. 
   this.setFile(fileName, true, this.bufferedIO, this.bufferSize); 
  } 
  catch (IOException e) 
  { 
   errorHandler.error("setFile(" + fileName + ", true) call failed."); 
  } 
  scheduledFilename = datedFilename; 
 } 
 
 /** 
  * This method differentiates MinuteRollingAppender from its super class. 
  * 
  * <p> 
  * Before actually logging, this method will check whether it is time to do 
  * a rollover. If it is, it will schedule the next rollover time and then 
  * rollover. 
  * */ 
 @Override 
 protected void subAppend(LoggingEvent event) 
 { 
  long n = System.currentTimeMillis(); 
  if (n >= nextCheck) 
  { 
   now.setTime(n); 
   nextCheck = rc.getNextCheckMillis(now); 
   try 
   { 
    rollOver(); 
   } 
   catch (IOException ioe) 
   { 
    if (ioe instanceof InterruptedIOException) 
    { 
     Thread.currentThread().interrupt(); 
    } 
    LogLog.error("rollOver() failed.", ioe); 
   } 
  } 
  super.subAppend(event); 
 } 
 
 /** 
  * RollingCalendar is a helper class to MinuteRollingAppender. Given a 
  * periodicity type and the current time, it computes the start of the next 
  * interval. 
  * */ 
 class RollingCalendar extends GregorianCalendar 
 { 
  private static final long serialVersionUID = -3560331770601814177L; 
 
  RollingCalendar() 
  { 
   super(); 
  } 
 
  public long getNextCheckMillis(Date now) 
  { 
   return getNextCheckDate(now).getTime(); 
  } 
 
  public Date getNextCheckDate(Date now) 
  { 
   this.setTime(now); 
   this.set(Calendar.SECOND, 0); 
   this.set(Calendar.MILLISECOND, 0); 
   this.add(Calendar.MINUTE, intervalTime); 
   return getTime(); 
  } 
 } 
} 

測(cè)試配置文件如下:

<?xml version="1.0" encoding="UTF-8"?> 
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd"> 
 
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/"> 
 
 <appender name="myFile" class="net.csdn.blog.MinuteRollingAppender">  
  <param name="File" value="log4jTest.log" /> 
  <param name="Append" value="true" /> 
  <param name="intervalTime" value="2"/> 
  <layout class="org.apache.log4j.PatternLayout"> 
   <param name="ConversionPattern" value="%p %d (%c:%L)- %m%n" /> 
  </layout> 
 </appender> 
 
 <root> 
  <priority value="debug"/> 
  <appender-ref ref="myFile"/>  
 </root> 
 
</log4j:configuration> 

      關(guān)于定時(shí)實(shí)現(xiàn),還可以采用java提供的Timer實(shí)現(xiàn),也就免去了每次記錄日志時(shí)計(jì)算并且比較時(shí)間,區(qū)別其實(shí)就是自己起個(gè)線程與調(diào)用rollOver方法,實(shí)現(xiàn)如下:

package net.csdn.blog; 
 
import java.io.File; 
import java.io.IOException; 
import java.text.SimpleDateFormat; 
import java.util.Date; 
import java.util.Timer; 
import java.util.TimerTask; 
 
import org.apache.log4j.FileAppender; 
import org.apache.log4j.Layout; 
import org.apache.log4j.helpers.LogLog; 
 
public class TimerTaskRollingAppender extends FileAppender 
{ 
 /** 
  * The date pattern. By default, the pattern is set to "'.'yyyy-MM-dd" 
  * meaning daily rollover. 
  */ 
 private static final String DATEPATTERN = "'.'yyyy-MM-dd-HH-mm'.log'"; 
 
 /** 
  * 間隔時(shí)間,單位:分鐘 
  */ 
 private int intervalTime = 10; 
 
 SimpleDateFormat sdf = new SimpleDateFormat(DATEPATTERN); 
 
 /** 
  * The default constructor does nothing. 
  */ 
 public TimerTaskRollingAppender() 
 { 
 } 
 
 /** 
  * Instantiate a <code>TimerTaskRollingAppender</code> and open the file 
  * designated by <code>filename</code>. The opened filename will become the 
  * ouput destination for this appender. 
  */ 
 public TimerTaskRollingAppender(Layout layout, String filename) 
   throws IOException 
 { 
  super(layout, filename, true); 
  activateOptions(); 
 } 
 
 /** 
  * @return the intervalTime 
  */ 
 public int getIntervalTime() 
 { 
  return intervalTime; 
 } 
 
 /** 
  * @param intervalTime 
  *   the intervalTime to set 
  */ 
 public void setIntervalTime(int intervalTime) 
 { 
  this.intervalTime = intervalTime; 
 } 
 
 @Override 
 public void activateOptions() 
 { 
  super.activateOptions(); 
  Timer timer = new Timer(); 
  timer.schedule(new LogTimerTask(), 1000, intervalTime * 60000); 
 } 
 
 class LogTimerTask extends TimerTask 
 { 
  @Override 
  public void run() 
  { 
   String datedFilename = fileName + sdf.format(new Date()); 
   closeFile(); 
   File target = new File(datedFilename); 
   if (target.exists()) 
    target.delete(); 
   File file = new File(fileName); 
   boolean result = file.renameTo(target); 
   if (result) 
    LogLog.debug(fileName + " -> " + datedFilename); 
   else 
    LogLog.error("Failed to rename [" + fileName + "] to [" 
      + datedFilename + "]."); 
   try 
   { 
    setFile(fileName, true, bufferedIO, bufferSize); 
   } 
   catch (IOException e) 
   { 
    errorHandler.error("setFile(" + fileName 
      + ", true) call failed."); 
   } 
  } 
 } 
} 

    不過(guò),以上實(shí)現(xiàn),存在2個(gè)問(wèn)題:

   1)并發(fā)

    并發(fā)問(wèn)題可能發(fā)生的一個(gè)地方在run()中調(diào)用closeFile();后,正好subAppend()方法寫日志,此刻文件已關(guān)閉,則會(huì)報(bào)以下錯(cuò)誤:

java.io.IOException: Stream closed 
 at sun.nio.cs.StreamEncoder.ensureOpen(Unknown Source) 
 at sun.nio.cs.StreamEncoder.write(Unknown Source) 
 at sun.nio.cs.StreamEncoder.write(Unknown Source) 
 at java.io.OutputStreamWriter.write(Unknown Source) 
 at java.io.Writer.write(Unknown Source) 
.............................. 

   解決方法比較簡(jiǎn)單,直接讓整個(gè)run()方法為同步的,加上synchronized關(guān)鍵字即可;不過(guò)目前樓主沒(méi)有解決如果真要寫,而且寫的速度夠快的情況下可能丟失日志的情況;
   2)性能

    使用Timer實(shí)現(xiàn)比較簡(jiǎn)單,但是Timer里面的任務(wù)如果執(zhí)行時(shí)間太長(zhǎng),會(huì)獨(dú)占Timer對(duì)象,使得后面的任務(wù)無(wú)法幾時(shí)的執(zhí)行,解決方法也比較簡(jiǎn)單,采用線程池版定時(shí)器類ScheduledExecutorService,實(shí)現(xiàn)如下:

/** 
 * 
 */ 
package net.csdn.blog; 
 
import java.io.File; 
import java.io.IOException; 
import java.text.SimpleDateFormat; 
import java.util.Date; 
import java.util.concurrent.Executors; 
import java.util.concurrent.TimeUnit; 
 
import org.apache.log4j.FileAppender; 
import org.apache.log4j.Layout; 
import org.apache.log4j.helpers.LogLog; 
 
/** 
 * @author coder_xia 
 *   <p> 
 *   采用ScheduledExecutorService實(shí)現(xiàn)定時(shí)配置打印日志 
 *   <p> 
 * 
 */ 
public class ScheduledExecutorServiceAppender extends FileAppender 
{ 
 /** 
  * The date pattern. By default, the pattern is set to "'.'yyyy-MM-dd" 
  * meaning daily rollover. 
  */ 
 private static final String DATEPATTERN = "'.'yyyy-MM-dd-HH-mm'.log'"; 
 
 /** 
  * 間隔時(shí)間,單位:分鐘 
  */ 
 private int intervalTime = 10; 
 
 SimpleDateFormat sdf = new SimpleDateFormat(DATEPATTERN); 
 
 /** 
  * The default constructor does nothing. 
  */ 
 public ScheduledExecutorServiceAppender() 
 { 
 } 
 
 /** 
  * Instantiate a <code>ScheduledExecutorServiceAppender</code> and open the 
  * file designated by <code>filename</code>. The opened filename will become 
  * the ouput destination for this appender. 
  */ 
 public ScheduledExecutorServiceAppender(Layout layout, String filename) 
   throws IOException 
 { 
  super(layout, filename, true); 
  activateOptions(); 
 } 
 
 /** 
  * @return the intervalTime 
  */ 
 public int getIntervalTime() 
 { 
  return intervalTime; 
 } 
 
 /** 
  * @param intervalTime 
  *   the intervalTime to set 
  */ 
 public void setIntervalTime(int intervalTime) 
 { 
  this.intervalTime = intervalTime; 
 } 
 
 @Override 
 public void activateOptions() 
 { 
  super.activateOptions(); 
  Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate( 
    new LogTimerTask(), 1, intervalTime * 60000, 
    TimeUnit.MILLISECONDS); 
 } 
 
 class LogTimerTask implements Runnable 
 { 
  @Override 
  public void run() 
  { 
   String datedFilename = fileName + sdf.format(new Date()); 
   closeFile(); 
   File target = new File(datedFilename); 
   if (target.exists()) 
    target.delete(); 
   File file = new File(fileName); 
   boolean result = file.renameTo(target); 
   if (result) 
    LogLog.debug(fileName + " -> " + datedFilename); 
   else 
    LogLog.error("Failed to rename [" + fileName + "] to [" 
      + datedFilename + "]."); 
   try 
   { 
    setFile(fileName, true, bufferedIO, bufferSize); 
   } 
   catch (IOException e) 
   { 
    errorHandler.error("setFile(" + fileName 
      + ", true) call failed."); 
   } 
  } 
 } 
} 

      關(guān)于定時(shí)的實(shí)現(xiàn),差不多就到這里了,采用的都默認(rèn)是10分鐘產(chǎn)生一個(gè)新的日志文件,在配置時(shí)可以自行設(shè)置,不過(guò)存在的一個(gè)隱患,萬(wàn)一配置的人不知道時(shí)間間隔是分鐘,如果以為是秒,配了個(gè)600,又開了debug,產(chǎn)生上G的日志文件,這肯定是個(gè)災(zāi)難,下面的改造就是結(jié)合RollingFileAppender的最大大小和最多備份文件個(gè)數(shù)可配,再次進(jìn)行完善,下次繼續(xù)描述改造過(guò)程。

添加模塊名配置
在前面講到了log4j定時(shí)打印的定制類實(shí)現(xiàn),就不講指定大小和指定備份文件個(gè)數(shù)了,從RollingFileAppender類copy代碼到前面的定制類中添加即可,唯一需要解決的是并發(fā)問(wèn)題,即文件關(guān)閉rename文件時(shí),發(fā)生了記錄日志事件時(shí),會(huì)報(bào)output stream closed的錯(cuò)誤。

    現(xiàn)在有這樣一種應(yīng)用場(chǎng)景,而且經(jīng)常有:

    1.項(xiàng)目包含有多個(gè)不同的工程;

    2.同一工程包含不同的模塊。

    對(duì)第一種情況,可以通過(guò)配置log4j<catogery=“Test”>,再在產(chǎn)生Logger時(shí)使用類似如下方式:

Logger logger=Logger.getLogger("Test"); 

    對(duì)第二種情況,我們希望能夠?qū)⒉煌K打印到同一個(gè)日志文件中,不過(guò)希望能夠在日志中打印出模塊名以便出問(wèn)題時(shí)定位問(wèn)題,因此便有了本文需要的在Appender類中添加配置ModuleName,下面開始改造,與定時(shí)打印不同,我們采用RollingFileAppender類為基類進(jìn)行改造。

    首先,添加配置項(xiàng)moduleName,并增加get、set方法;

    由于繼承自RollingFileAppender,所以只需要在subAppend()中格式化LoggingEvent中的數(shù)據(jù),添加formatInfo方法格式化數(shù)據(jù),代碼略;

    最終的成品類如下:

package net.csdn.blog; 
 
import org.apache.log4j.Category; 
import org.apache.log4j.RollingFileAppender; 
import org.apache.log4j.spi.LoggingEvent; 
 
/** 
 * @author coder_xia 
 * 
 */ 
public class ModuleAppender extends RollingFileAppender 
{ 
 private String moduleName; 
 
 /** 
  * @return the moduleName 
  */ 
 public String getModuleName() 
 { 
  return moduleName; 
 } 
 
 /** 
  * @param moduleName 
  *   the moduleName to set 
  */ 
 public void setModuleName(String moduleName) 
 { 
  this.moduleName = moduleName; 
 } 
 
 /** 
  * 格式化打印內(nèi)容 
  * 
  * @param event 
  *   event 
  * @return msg 
  */ 
 private String formatInfo(LoggingEvent event) 
 { 
  StringBuilder sb = new StringBuilder(); 
  if (moduleName != null) 
  { 
   sb.append(moduleName).append("|"); 
   sb.append(event.getMessage()); 
  } 
  return sb.toString(); 
 } 
 
 @Override 
 public void subAppend(LoggingEvent event) 
 { 
  String msg = formatInfo(event); 
  super.subAppend(new LoggingEvent(Category.class.getName(), event 
    .getLogger(), event.getLevel(), msg, null)); 
 } 
} 

相關(guān)文章

  • SpringBoot使用@Async注解可能會(huì)遇到的8大坑點(diǎn)匯總

    SpringBoot使用@Async注解可能會(huì)遇到的8大坑點(diǎn)匯總

    SpringBoot中,@Async注解可以實(shí)現(xiàn)異步線程調(diào)用,用法簡(jiǎn)單,體驗(yàn)舒適,但是你一定碰到過(guò)異步調(diào)用不生效的情況,今天,我就列出90%的人都可能會(huì)遇到的8大坑點(diǎn),需要的朋友可以參考下
    2023-09-09
  • Java開啟JMX遠(yuǎn)程監(jiān)控服務(wù)配置

    Java開啟JMX遠(yuǎn)程監(jiān)控服務(wù)配置

    這篇文章主要為大家介紹了Java開啟JMX遠(yuǎn)程監(jiān)控的服務(wù)配置,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-05-05
  • Mybatis如何構(gòu)建SQL語(yǔ)句

    Mybatis如何構(gòu)建SQL語(yǔ)句

    這篇文章主要介紹了Mybatis如何構(gòu)建SQL語(yǔ)句問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-12-12
  • Java日常練習(xí)題,每天進(jìn)步一點(diǎn)點(diǎn)(61)

    Java日常練習(xí)題,每天進(jìn)步一點(diǎn)點(diǎn)(61)

    下面小編就為大家?guī)?lái)一篇Java基礎(chǔ)的幾道練習(xí)題(分享)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧,希望可以幫到你
    2021-08-08
  • 利用棧使用簡(jiǎn)易計(jì)算器(Java實(shí)現(xiàn))

    利用棧使用簡(jiǎn)易計(jì)算器(Java實(shí)現(xiàn))

    這篇文章主要為大家詳細(xì)介紹了Java利用棧實(shí)現(xiàn)簡(jiǎn)易計(jì)算器,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-09-09
  • 基于@GetMapping注解攜帶參數(shù)的方式

    基于@GetMapping注解攜帶參數(shù)的方式

    這篇文章主要介紹了基于@GetMapping注解攜帶參數(shù)的方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-05-05
  • 熟悉maven:使java開發(fā)變得更高效

    熟悉maven:使java開發(fā)變得更高效

    在日常的開發(fā)過(guò)程中,maven是很常見的項(xiàng)目構(gòu)建工具。maven可以極大的提高我們的開發(fā)效率,幫助我們簡(jiǎn)化開發(fā)過(guò)程中一些解決依賴和項(xiàng)目部署的相關(guān)問(wèn)題,所以學(xué)習(xí)掌握maven的相關(guān)知識(shí)是非常有必要的
    2021-06-06
  • JAVA后端學(xué)習(xí)精華之網(wǎng)絡(luò)通信項(xiàng)目進(jìn)階

    JAVA后端學(xué)習(xí)精華之網(wǎng)絡(luò)通信項(xiàng)目進(jìn)階

    不同項(xiàng)目之間的通信方式分為,http、socket、webservice;其中socket通信的效率最高,youtube就采用的是原始的socket通信,他們信奉的原則是簡(jiǎn)單有效
    2021-09-09
  • 簡(jiǎn)單了解Java方法的定義和使用實(shí)現(xiàn)詳解

    簡(jiǎn)單了解Java方法的定義和使用實(shí)現(xiàn)詳解

    這篇文章主要介紹了簡(jiǎn)單了解Java方法的定義和使用實(shí)現(xiàn)詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-12-12
  • Java去掉字符串最后一個(gè)逗號(hào)的方法

    Java去掉字符串最后一個(gè)逗號(hào)的方法

    Java中去掉字符串的最后一個(gè)逗號(hào)有多種實(shí)現(xiàn)方法,不同的方法適用于不同的場(chǎng)景,本文通過(guò)實(shí)例代碼介紹Java去掉字符串最后一個(gè)逗號(hào)的相關(guān)知識(shí),感興趣的朋友一起看看吧
    2023-12-12

最新評(píng)論