Java中有哪个函数能够像sscanf一样,使用已知的模式从字符串中解析值?

40

我原本是C语言背景,虽然已经五年没有使用这种语言了。现在我尝试在Java中从字符串中解析一些值。在C语言中,我会使用sscanf, 但在Java中人们告诉我“使用Scanner或StringTokenizer”,但我不知道如何使用它们来达到我的目的。

我的输入字符串看起来像是 "17-MAR-11 15.52.25.000000000"。在C语言中,我会做这样的事情:

sscanf(thestring, "%d-%s-%d %d.%d.%d.%d", day, month, year, hour, min, sec, fracpart);

但在Java中,我只能做像这样的事情:

scanner.nextInt();

这样做并不允许我检查该模式,而对于“MAR”,我最终不得不执行诸如以下操作:

str.substring(3,6);

很糟糕!肯定有更好的方法吧?


1
如果您的问题实际上是解析日期时间字符串?那么可能会有更好的选择,但您应该寻找与strptime等效的选项,而不是scanf - riffraff
你尝试过使用SimpleDateFormat吗?它有一个解析方法,如果出错会返回null。 - rineez
9个回答

41

问题在于Java没有像C或C#那样的输出参数(或按引用传递)。

但有一种更好的方法(也更可靠)。使用正则表达式:

Pattern p = Pattern.compile("(\\d+)-(\\p{Alpha}+)-(\\d+) (\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)")
Matcher m = p.matcher("17-MAR-11 15.52.25.000000000");
day = m.group(1);
month= m.group(2);
....

当然,C代码更加简洁,但这种技术有一个好处:模式比“%s”和“%d”更精确地指定格式。因此,您可以使用\d{2}来指定日期必须由正好两个数字组成。


1
太棒了...这需要我自己进行字符串到整数的转换,但这似乎是最好的解决方案,而且是我没有想到的一个。 - Adam Burley
6
请注意,您可以将除了小数秒部分以外的所有内容捕获为一个组,并使用SimpleDateFormat("dd-MMM-yy hh.mm.ss")将其解析为日期。 - ewan.chalmers
9
C语言确实有宽度说明符,你可以使用"%2d"来指定需要两个数字。这只是我的个人意见。:) - Kounavi
18
在调用 m.group() 之前,我认为你需要先调用 m.find() - xuhdev
1
确实,你应该首先调用 m.find()(如上面 xuhdev 所提到的),或者直接使用 if (m.matches()) {m.group(1); ...} - Mitrakov Artem
显示剩余2条评论

29

以下是使用扫描器(Scanner)的解决方案:

Scanner scanner = new Scanner("17-MAR-11 15.52.25.000000000");

Scanner dayScanner = new Scanner(scanner.next());
Scanner timeScanner = new Scanner(scanner.next());

dayScanner.useDelimiter("-");
System.out.println("day=" + dayScanner.nextInt());
System.out.println("month=" + dayScanner.next());
System.out.println("year=" + dayScanner.nextInt());

timeScanner.useDelimiter("\\.");
System.out.println("hour=" + timeScanner.nextInt());
System.out.println("min=" + timeScanner.nextInt());
System.out.println("sec=" + timeScanner.nextInt());
System.out.println("fracpart=" + timeScanner.nextInt());

13

这些例子都不令我满意,因此我自己编写了一个Java sscanf实用程序:

https://github.com/driedler/java-sscanf/tree/master/src/util/sscanf

这是解析十六进制字符串的示例:

String buffer = "my hex string: DEADBEEF\n"
Object output[] = Sscanf.scan(buffer, "my hex string: %X\n", 1);

System.out.println("parse count: " + output.length);
System.out.println("hex str1: " + (Long)output[0]);

// Output:
// parse count: 1
// hex str1: 3735928559

我在提取城市、州和邮编时遇到了异常:无效的数字格式:“s”不是“diuoxX”中的一个。例如:`String buffer = "["WALTER PAYTON HIGH SCHOOL - CHICAGO, IL","60622"]"; Object output[] = Sscanf.scan(buffer, "["%s - %s, %s","%d"]", 1,2,3,4);System.out.println("parse count: " + output.length); System.out.println("data : " + output[0]+output[1]+output[2]+output[3]);` - MD. Mohiuddin Ahmed

3
对于"17-MAR-11 15.52.25.000000000":
SimpleDateFormat format = new SimpleDateFormat("dd-MMM-yy HH.mm.ss.SSS");

try 
{
    Date parsed = format.parse(dateString);
    System.out.println(parsed.toString());
}
catch (ParseException pe)
{
    System.out.println("ERROR: Cannot parse \"" + dateString + "\"");
}

我认为这个答案过于特定于日期,而原帖要求使用通用已知模式解析值的方法。 - magnum87

2
这并不是使用正则表达式那样优雅的解决方案,但应该能够工作。
public static void stringStuffThing(){
String x = "17-MAR-11 15.52.25.000000000";
String y[] = x.split(" ");

for(String s : y){
    System.out.println(s);
}
String date[] = y[0].split("-");
String values[] = y[1].split("\\.");

for(String s : date){
    System.out.println(s);
}
for(String s : values){
    System.out.println(s);
}

1

2019年的答案:Java的Scanner可以灵活地读取各种格式。但是,如果您的格式只包含简单的{%d, %f, %s}字段,则可以使用这个小类(约90行)轻松扫描。

import java.util.ArrayList;

/**
 * Basic C-style string formatting and scanning.
 * The format strings can contain %d, %f and %s codes.
 * @author Adam Gawne-Cain
 */
public class CFormat {
    private static boolean accept(char t, char c, int i) {
        if (t == 'd')
            return "0123456789".indexOf(c) >= 0 || i == 0 && c == '-';
        else if (t == 'f')
            return "-0123456789.+Ee".indexOf(c) >= 0;
        else if (t == 's')
            return Character.isLetterOrDigit(c);
        throw new RuntimeException("Unknown format code: " + t);
    }

    /**
     * Returns string formatted like C, or throws exception if anything wrong.
     * @param fmt format specification
     * @param args values to format
     * @return string formatted like C.
     */
    public static String printf(String fmt, Object... args) {
        int a = 0;
        StringBuilder sb = new StringBuilder();
        int n = fmt.length();
        for (int i = 0; i < n; i++) {
            char c = fmt.charAt(i);
            if (c == '%') {
                char t = fmt.charAt(++i);
                if (t == 'd')
                    sb.append(((Number) args[a++]).intValue());
                else if (t == 'f')
                    sb.append(((Number) args[a++]).doubleValue());
                else if (t == 's')
                    sb.append(args[a++]);
                else if (t == '%')
                    sb.append(t);
                else
                    throw new RuntimeException("Unknown format code: " + t);
            } else
                sb.append(c);
        }
        return sb.toString();
    }

    /**
     * Returns scanned values, or throws exception if anything wrong.
     * @param fmt format specification
     * @param str string to scan
     * @return scanned values
     */
    public static Object[] scanf(String fmt, String str) {
        ArrayList ans = new ArrayList();
        int s = 0;
        int ns = str.length();
        int n = fmt.length();
        for (int i = 0; i < n; i++) {
            char c = fmt.charAt(i);
            if (c == '%') {
                char t = fmt.charAt(++i);
                if (t=='%')
                    c=t;
                else {
                    int s0 = s;
                    while ((s == s0 || s < ns) && accept(t, str.charAt(s), s - s0))
                        s++;
                    String sub = str.substring(s0, s);
                    if (t == 'd')
                        ans.add(Integer.parseInt(sub));
                    else if (t == 'f')
                        ans.add(Double.parseDouble(sub));
                    else
                        ans.add(sub);
                    continue;
                }
            }
            if (str.charAt(s++) != c)
                throw new RuntimeException();
        }
        if (s < ns)
            throw new RuntimeException("Unmatched characters at end of string");
        return ans.toArray();
    }
}

例如,可以像这样处理OP的情况:
    // Example of "CFormat.scanf"
    String str = "17-MAR-11 15.52.25.000000000";
    Object[] a = CFormat.scanf("%d-%s-%d %d.%d.%f", str);

    // Pick out scanned fields
    int day = (Integer) a[0];
    String month = (String) a[1];
    int year = (Integer) a[2];
    int hour = (Integer) a[3];
    int min = (Integer) a[4];
    double sec = (Double) a[5];

    // Example of "CFormat.printf"  
    System.out.println(CFormat.printf("Got day=%d month=%s hour=%d min=%d sec=%f\n", day, month, year, hour, min, sec));

0
你是否熟悉正则表达式的概念?Java 提供了使用 Pattern 类来使用正则表达式的能力。 看看这个链接: http://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html 你可以像这样测试你的字符串:
Matcher matcher = Pattern.match(yourString);
matcher.find();

然后使用Matcher提供的方法来操作你找到的字符串或者不操作。


0
这里是使用Scanner实现sscanf的简单示例:
public static ArrayList<Object> scan(String s, String fmt)
{ ArrayList<Object> result = new ArrayList<Object>();
  Scanner scanner = new Scanner(s);

  int ind = 0; // s upto ind has been consumed

  for (int i = 0; i < fmt.length(); i++) 
  { char c = fmt.charAt(i); 
    if (c == '%' && i < fmt.length() - 1)
    { char d = fmt.charAt(i+1); 
      if (d == 's') 
      { scanner = new Scanner(s.substring(ind)); 
        try { 
          String v = scanner.next(); 
          ind = ind + v.length(); 
          result.add(v); 
        } 
        catch (Exception _ex) { 
          _ex.printStackTrace(); 
        }  
        i++; 
      }
      else if (d == 'f')
      { String fchars = ""; 
        for (int j = ind; j < s.length(); j++) 
        { char x = s.charAt(j); 
          if (x == '.' || Character.isDigit(x))
          { fchars = fchars + x; } 
          else 
          { break; } 
        } 

        try { 
          double v = Double.parseDouble(fchars); 
          ind = ind + (v + "").length(); 
          result.add(v); 
        } 
        catch (Exception _ex) { 
          _ex.printStackTrace(); 
        }  
        i++;  
      }
      else if (d == 'd') 
      { String inchars = ""; 
        for (int j = ind; j < s.length(); j++) 
        { char x = s.charAt(j); 
          if (Character.isDigit(x))
          { inchars = inchars + x; } 
          else 
          { break; } 
        } 
      
        try { 
          int v = Integer.parseInt(inchars); 
          ind = ind + (v + "").length(); 
          result.add(v); 
        } 
        catch (Exception _ex) { 
          _ex.printStackTrace(); 
        }  
        i++;  
      }
    } 
    else if (s.charAt(ind) == c) 
    { ind++; } 
    else 
    { return result; }

  } 
  return result; 
} 

public static void main(String[] args)
{ ArrayList res = StringLib.scan("100##3.3::20\n", "%d##%f::%d\n"); 
  System.out.println(res); 
}  

-3

System.in.read() 是另一个选项。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接