在 Java 中比较版本字符串的有效方法



可能的重复项:
你如何比较 Java 中的两个版本的字符串?

我有 2 个字符串,其中包含版本信息,如下所示:

str1 = "1.2"
str2 = "1.1.2"

现在,谁能告诉我在Java中比较字符串中的这些版本并返回0的有效方法,如果它们相等,则为-1,如果str1<str2和1,如果str1>str2。

需要 commons-lang3-3.8.1.jar 进行字符串操作。

/**
 * Compares two version strings. 
 * 
 * Use this instead of String.compareTo() for a non-lexicographical 
 * comparison that works for version strings. e.g. "1.10".compareTo("1.6").
 * 
 * @param v1 a string of alpha numerals separated by decimal points. 
 * @param v2 a string of alpha numerals separated by decimal points.
 * @return The result is 1 if v1 is greater than v2. 
 *         The result is 2 if v2 is greater than v1. 
 *         The result is -1 if the version format is unrecognized. 
 *         The result is zero if the strings are equal.
 */
public int VersionCompare(String v1,String v2)
{
    int v1Len=StringUtils.countMatches(v1,".");
    int v2Len=StringUtils.countMatches(v2,".");
    if(v1Len!=v2Len)
    {
        int count=Math.abs(v1Len-v2Len);
        if(v1Len>v2Len)
            for(int i=1;i<=count;i++)
                v2+=".0";
        else
            for(int i=1;i<=count;i++)
                v1+=".0";
    }
    if(v1.equals(v2))
        return 0;
    String[] v1Str=StringUtils.split(v1, ".");
    String[] v2Str=StringUtils.split(v2, ".");
    for(int i=0;i<v1Str.length;i++)
    {
        String str1="",str2="";
        for (char c : v1Str[i].toCharArray()) {
            if(Character.isLetter(c))
            {
                int u=c-'a'+1;
                if(u<10)
                    str1+=String.valueOf("0"+u);
                else
                    str1+=String.valueOf(u);
            }
            else
                str1+=String.valueOf(c);
        }            
        for (char c : v2Str[i].toCharArray()) {
            if(Character.isLetter(c))
            {
                int u=c-'a'+1;
                if(u<10)
                    str2+=String.valueOf("0"+u);
                else
                    str2+=String.valueOf(u);
            }
            else
                str2+=String.valueOf(c);
        }
        v1Str[i]="1"+str1;
        v2Str[i]="1"+str2;
            int num1=Integer.parseInt(v1Str[i]);
            int num2=Integer.parseInt(v2Str[i]);
            if(num1!=num2)
            {
                if(num1>num2)
                    return 1;
                else
                    return 2;
            }
    }
    return -1;
}    

正如其他人所指出的,String.split(( 是一种非常简单的方法来进行比较,Mike Deck 提出了一个很好的观点,即对于如此(可能的(短字符串,它可能无关紧要,但是嘿! 如果您想在不手动解析字符串的情况下进行比较,并且可以选择提前退出,则可以尝试 java.util.Scanner 类。

public static int versionCompare(String str1, String str2) {
    try ( Scanner s1 = new Scanner(str1);
          Scanner s2 = new Scanner(str2);) {
        s1.useDelimiter("\.");
        s2.useDelimiter("\.");
        while (s1.hasNextInt() && s2.hasNextInt()) {
            int v1 = s1.nextInt();
            int v2 = s2.nextInt();
            if (v1 < v2) {
                return -1;
            } else if (v1 > v2) {
                return 1;
            }
        }
        if (s1.hasNextInt() && s1.nextInt() != 0)
            return 1; //str1 has an additional lower-level version number
        if (s2.hasNextInt() && s2.nextInt() != 0)
            return -1; //str2 has an additional lower-level version 
        return 0;
    } // end of try-with-resources
}

这几乎肯定不是最有效的方法,但考虑到版本号字符串几乎总是只有几个字符长,我认为不值得进一步优化:

public static int compareVersions(String v1, String v2) {
    String[] components1 = v1.split("\.");
    String[] components2 = v2.split("\.");
    int length = Math.min(components1.length, components2.length);
    for(int i = 0; i < length; i++) {
        int result = new Integer(components1[i]).compareTo(Integer.parseInt(components2[i]));
        if(result != 0) {
            return result;
        }
    }
    return Integer.compare(components1.length, components2.length);
}

我自己想这样做,我看到了三种不同的方法来做到这一点,到目前为止,几乎每个人都在拆分版本字符串。我不认为这样做是有效的,尽管代码大小明智,它读起来很好,看起来不错。

方法:

  1. 假设版本字符串中的节数(序号(有上限,以及其中表示的值有上限。通常最多 4 个点,任何序数最多 999 个点。你可以看到这是怎么回事,它将把版本转换为适合这样的字符串:"1.0" => "001000000000",带有字符串格式或其他方式填充每个序数。然后做一个字符串比较。
  2. 拆分序号分隔符 ('.'( 上的字符串并迭代它们并比较解析后的版本。这是亚历克斯·吉特尔曼(Alex Gitelman(很好地证明的方法。
  3. 在从相关版本字符串中解析序号时比较序号。如果所有字符串实际上都只是指向字符数组的指针,如 C 中的字符数组,那么这将是明确的方法(在找到时将 '." 替换为空终止符并移动大约 2 或 4 个指针。

对三种方法的思考:

  1. 有一篇博客文章链接了如何使用 1。限制在于版本字符串长度、节数和节的最大值。我不认为有这样一个字符串一次突破 10,000 是疯狂的。此外,大多数实现最终仍然会拆分字符串。
  2. 提前拆分字符串很容易阅读和思考,但是我们将对每个字符串进行大约两次检查才能做到这一点。我想将它与下一个方法的时间进行比较。
  3. 在拆分字符串时比较
  4. 字符串的优势是,在比较"2.1001.100101.9999998"到"1.0.0.0.0.0.0.1.0.0.0.1"时,可以很早就停止拆分。如果这是C而不是Java,那么优势可以继续限制为每个版本的每个部分的新字符串分配的内存量,但事实并非如此。

我没有看到有人举出第三种方法的例子,所以我想在这里添加它作为提高效率的答案。

public class VersionHelper {
    /**
     * Compares one version string to another version string by dotted ordinals.
     * eg. "1.0" > "0.09" ; "0.9.5" < "0.10",
     * also "1.0" < "1.0.0" but "1.0" == "01.00"
     *
     * @param left  the left hand version string
     * @param right the right hand version string
     * @return 0 if equal, -1 if thisVersion &lt; comparedVersion and 1 otherwise.
     */
    public static int compare(@NotNull String left, @NotNull String right) {
        if (left.equals(right)) {
            return 0;
        }
        int leftStart = 0, rightStart = 0, result;
        do {
            int leftEnd = left.indexOf('.', leftStart);
            int rightEnd = right.indexOf('.', rightStart);
            Integer leftValue = Integer.parseInt(leftEnd < 0
                    ? left.substring(leftStart)
                    : left.substring(leftStart, leftEnd));
            Integer rightValue = Integer.parseInt(rightEnd < 0
                    ? right.substring(rightStart)
                    : right.substring(rightStart, rightEnd));
            result = leftValue.compareTo(rightValue);
            leftStart = leftEnd + 1;
            rightStart = rightEnd + 1;
        } while (result == 0 && leftStart > 0 && rightStart > 0);
        if (result == 0) {
            if (leftStart > rightStart) {
                return containsNonZeroValue(left, leftStart) ? 1 : 0;
            }
            if (leftStart < rightStart) {
                return containsNonZeroValue(right, rightStart) ? -1 : 0;
            }
        }
        return result;
    }
    private static boolean containsNonZeroValue(String str, int beginIndex) {
        for (int i = beginIndex; i < str.length(); i++) {
            char c = str.charAt(i);
            if (c != '0' && c != '.') {
                return true;
            }
        }
        return false;
    }
}

演示预期输出的单元测试。

public class VersionHelperTest {
    @Test
    public void testCompare() throws Exception {
        assertEquals(1, VersionHelper.compare("1", "0.9"));
        assertEquals(1, VersionHelper.compare("0.0.0.2", "0.0.0.1"));
        assertEquals(1, VersionHelper.compare("1.0", "0.9"));
        assertEquals(1, VersionHelper.compare("2.0.1", "2.0.0"));
        assertEquals(1, VersionHelper.compare("2.0.1", "2.0"));
        assertEquals(1, VersionHelper.compare("2.0.1", "2"));
        assertEquals(1, VersionHelper.compare("0.9.1", "0.9.0"));
        assertEquals(1, VersionHelper.compare("0.9.2", "0.9.1"));
        assertEquals(1, VersionHelper.compare("0.9.11", "0.9.2"));
        assertEquals(1, VersionHelper.compare("0.9.12", "0.9.11"));
        assertEquals(1, VersionHelper.compare("0.10", "0.9"));
        assertEquals(0, VersionHelper.compare("0.10", "0.10"));
        assertEquals(-1, VersionHelper.compare("2.10", "2.10.1"));
        assertEquals(-1, VersionHelper.compare("0.0.0.2", "0.1"));
        assertEquals(1, VersionHelper.compare("1.0", "0.9.2"));
        assertEquals(1, VersionHelper.compare("1.10", "1.6"));
        assertEquals(0, VersionHelper.compare("1.10", "1.10.0.0.0.0"));
        assertEquals(1, VersionHelper.compare("1.10.0.0.0.1", "1.10"));
        assertEquals(0, VersionHelper.compare("1.10.0.0.0.0", "1.10"));
        assertEquals(1, VersionHelper.compare("1.10.0.0.0.1", "1.10"));
    }
}

将字符串拆分为 "." 或任何您的分量仪,然后将每个标记解析为 Integer 值并进行比较。

int compareStringIntegerValue(String s1, String s2, String delimeter)  
{  
   String[] s1Tokens = s1.split(delimeter);  
   String[] s2Tokens = s2.split(delimeter);  
   int returnValue = 0;
   if(s1Tokens.length > s2Tokens.length)  
   {  
       for(int i = 0; i<s1Tokens.length; i++)  
       {  
          int s1Value = Integer.parseString(s1Tokens[i]);  
          int s2Value = Integer.parseString(s2Tokens[i]);  
          Integer s1Integer = new Integer(s1Value);  
          Integer s2Integer = new Integer(s2Value);  
          returnValue = s1Integer.compareTo(s2Value);
          if( 0 == isEqual)  
           {  
              continue; 
           }  
           return returnValue;  //end execution
        }
           return returnValue;  //values are equal
 } 

我将把另一个如果陈述作为练习。

比较版本字符串可能会一团糟;你得到的答案是无用的,因为完成这项工作的唯一方法是非常具体地说明你的排序约定是什么。我在一篇博客文章中看到了一个相对较短且完整的版本比较功能,代码放在公共领域 - 它不在 Java 中,但应该很容易看到如何适应它。

编自Alex Gitelman的回答。

int compareVersions( String str1, String str2 ){
    if( str1.equals(str2) ) return 0; // Short circuit when you shoot for efficiency
    String[] vals1 = str1.split("\.");
    String[] vals2 = str2.split("\.");
    int i=0;
    // Most efficient way to skip past equal version subparts
    while( i<vals1.length && i<val2.length && vals[i].equals(vals[i]) ) i++;
    // If we didn't reach the end,
    if( i<vals1.length && i<val2.length )
        // have to use integer comparison to avoid the "10"<"1" problem
        return Integer.valueOf(vals1[i]).compareTo( Integer.valueOf(vals2[i]) );
    if( i<vals1.length ){ // end of str2, check if str1 is all 0's
        boolean allZeros = true;
        for( int j = i; allZeros & (j < vals1.length); j++ )
            allZeros &= ( Integer.parseInt( vals1[j] ) == 0 );
        return allZeros ? 0 : -1;
    }
    if( i<vals2.length ){ // end of str1, check if str2 is all 0's
        boolean allZeros = true;
        for( int j = i; allZeros & (j < vals2.length); j++ )
            allZeros &= ( Integer.parseInt( vals2[j] ) == 0 );
        return allZeros ? 0 : 1;
    }
    return 0; // Should never happen (identical strings.)
}

所以正如你所看到的,不是那么微不足道。当您允许前导 0 时,这也失败了,但我从未见过版本"1.04.5"或 w/e。您需要在 while 循环中使用整数比较来解决此问题。当您在版本字符串中混合字母和数字时,这会变得更加复杂。

将它们拆分为数组,然后进行比较。

// check if two strings are equal. If they are return 0;
String[] a1;
String[] a2;
int i = 0;
while (true) {
    if (i == a1.length && i < a2.length) return -1;
    else if (i < a1.length && i == a2.length) return 1;
    if (a1[i].equals(a2[i]) {
       i++;
       continue;
    }
     return a1[i].compareTo(a2[i];
}
return 0;

我会将问题一分为二,格式化和比较。如果你可以假设格式是正确的,那么只比较数字版本非常简单:

final int versionA = Integer.parseInt( "01.02.00".replaceAll( "\.", "" ) );
final int versionB = Integer.parseInt( "01.12.00".replaceAll( "\.", "" ) );

然后两个版本都可以作为整数进行比较。所以"大问题"是格式,但这可能有很多规则。就我而言,我只完成至少两对数字,因此格式始终为"99.99.99",然后我进行上述转换;因此,就我而言,程序逻辑在格式中,而不是在版本比较中。现在,如果您正在做一些非常具体的事情,也许您可以信任版本字符串的来源,也许您可以检查版本字符串的长度,然后进行 int 转换......但我认为确保格式符合预期是最佳做法。

Step1 : 在java中使用StringTokenizer,点作为分隔符

StringTokenizer(String str, String delimiters)

您可以使用String.split()Pattern.split(),在点上拆分,然后使用Integer.parseInt(String str)将每个字符串转换为整数

第 2 步:从左到右比较整数。

最新更新