Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Efficiency of equalsIgnoreCase() versus toUpperCase().equals and toLowerCase().equals

The title says it all. My question regards the efficiency of different String equivalency methods. I frequently use .equalsIgnoreCase(String str) because I just have a thing for it. But I'm beginning to wonder if it may not be the most efficient method for finding equivalency between Strings. It seems to me that .equalsIgnoreCase(String str) is calling one of the case shifting methods toUpperCase or toLowerCase then calling equals in its definition, but I am likely wrong. So, which of these methods is more efficient in the following situation, or any situation for that matter?

int count = 0;//checks for face cards at indexes listed in selectedCards
              // Selected cards is Integer ArrayList

    for(; (count < selectedCards.size() && count < 3); count++)
    {
        if(cardAt(selectedCards.get(count)).rank().equalsIgnoreCase("Queen"))
            count++;
        else if(cardAt(selectedCards.get(count)).rank().equalsIgnoreCase("King"))
            count++;
        if(cardAt(selectedCards.get(count)).rank().equalsIgnoreCase("Jack"))
            count++;
    }

    if(count == 3)
        return true;
    return false;
like image 221
Ungeheuer Avatar asked Apr 22 '15 00:04

Ungeheuer


People also ask

Which is faster equals or equalsIgnoreCase?

equalsIgnoreCase() is 20–50% faster than the equals(param. toLowerCase()) pattern. And 25 times faster when the parameter doesn't match the constant string.

What is difference between equals and equalsIgnoreCase in Java?

Difference between equals() vs equalsIgnoreCase() in JavaUse equals() in Java to check for equality between two strings. Use equalsIgnoreCase() in Java to check for equality between two strings ignoring the case.

What is equalsIgnoreCase () in Java?

The equalsIgnoreCase() method compares two strings, ignoring lower case and upper case differences. This method returns true if the strings are equal, and false if not.


3 Answers

JMH makes microbenchmarking easy:

Update: Set up the input string as a parameter to address the comment from JMH God Alexey Shipilev. I left the target string constant because OP's use case is to compare input strings to a constant.

@State(Benchmark)
public class StrComp {

    @Param({"Queen", "queen", "King"})
    public String input;

    @Benchmark
    public boolean eqIgnoreCase() {
        return input.equalsIgnoreCase("queen");
    }

    @Benchmark
    public boolean eqToLower() {
        return input.toLowerCase().equals("queen");
    }

    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(".*StrComp.*")
                .mode(Mode.AverageTime)
                .timeUnit(TimeUnit.NANOSECONDS)
                .forks(5)
                .warmupIterations(10)
                .measurementIterations(10)
                .build();

        new Runner(opt).run();
    }
}

And the output:

Benchmark             Mode  Cnt   Score   Error  Units
StrComp.eqIgnoreCase  avgt   50  18.581 ± 0.051  ns/op
StrComp.eqToLower     avgt   50  54.796 ± 0.173  ns/op

Updated output with parameter:

Benchmark             (input)  Mode  Cnt   Score   Error  Units
StrComp.eqIgnoreCase    Queen  avgt   50  17.947 ± 0.205  ns/op
StrComp.eqIgnoreCase    queen  avgt   50  15.553 ± 0.159  ns/op
StrComp.eqIgnoreCase     King  avgt   50   2.968 ± 0.037  ns/op
StrComp.eqToLower       Queen  avgt   50  56.499 ± 0.180  ns/op
StrComp.eqToLower       queen  avgt   50  22.023 ± 0.040  ns/op
StrComp.eqToLower        King  avgt   50  49.174 ± 0.145  ns/op

So, eqIgnoreCase is faster but unless you are doing a million comparisons per second, you won't notice any difference.

You can play around and see how the difference would be affected if the first string is already lower case or if the strings are of different lengths, etc.

Anyway, If you want to make your code more "efficient" and also more clear, type-safe and less prone to bugs, don't use strings for things like this. Use enums.

Deck of cards is so well-suited to implementation by enums, it's frequently used to illustrate the enum concept: http://docs.oracle.com/javase/8/docs/technotes/guides/language/enums.html#Card

like image 74
Misha Avatar answered Oct 31 '22 11:10

Misha


Surprisingly, these methods are not equivalent for some strange Unicode reasons:

  • toUpperCase("ß") returns "SS", i.e., two letters, while equalsIgnoreCase works character-wise
  • There are characters for which neither toUpperCase nor toLowerCase is sufficient, you must do both

Concerning efficiency, I'd bet that equalsIgnoreCase is way faster, as it doesn't copy any data. It also starts with the length comparison.

Note also that toUpperCase and toLowerCase are Locale-sensitive, while equalsIgnoreCase is not. IIRC the performance diminishes if an exotic locale gets used.

Your use case

The best and simplest optimization would be to normalize the casing beforehand. There's no reason to have a "Queen", a "queen", and a "QueEN" in your data - clean up the input ASAP.

You can also use an enum for representing the rank.

I'm afraid, your loop is broken. After any of JQK you skip one card, is that intended???

Simplified code

Do the following

  • use foreach loop to keep it cleaner
  • use local variables to avoid repeating complicated expression
  • replace if (x) return true; else return false; by return x;

The code without using enums and early normalization would look like this:

int count = 0;
// Whatever Position is
for (Position p : selectedCards) {
    String rank = cardAt(p).rank();
    if (rank.equalsIgnoreCase("Jack")
            || rank.equalsIgnoreCase("Queen")
            || rank.equalsIgnoreCase("King")) {
        ++count;
    if (count > 3) { // Tiny and probably useless optimization.
        return false;
    }
}
return count == 3;
like image 24
maaartinus Avatar answered Oct 31 '22 10:10

maaartinus


You can check the code simply yourself (it is included in SDK).

The .equalsIgnoreCase is faster than toUperCase().equals() In java 8, the code is:

   while (len-- > 0) {
        char c1 = ta[to++];
        char c2 = pa[po++];
        if (c1 == c2) {
            continue;
        }
        if (ignoreCase) {
            // If characters don't match but case may be ignored,
            // try converting both characters to uppercase.
            // If the results match, then the comparison scan should
            // continue.
            char u1 = Character.toUpperCase(c1);
            char u2 = Character.toUpperCase(c2);
            if (u1 == u2) {
                continue;
            }
            // Unfortunately, conversion to uppercase does not work properly
            // for the Georgian alphabet, which has strange rules about case
            // conversion.  So we need to make one last check before
            // exiting.
            if (Character.toLowerCase(u1) == Character.toLowerCase(u2)) {
                continue;
            }
        }
        return false;
    }
    return true;

so if the compared string are actually different it will be faster than first calling toUpderCase then equals as the toUperCase will modifiy all the characters. Also the logic involved in toUperCase seems to be more complicated than in the comparison loop and it creates a new String object at the end.

But you would need to have A LOT of comparison operations to actually see any difference.

like image 7
Zielu Avatar answered Oct 31 '22 12:10

Zielu