ColdFusion String Comparison: Compare() vs. Equals() vs. CompareTo()
As part of my on-going study of the Java that lives under ColdFusion, I thought I would test out some string comparison methods so see if going directly to the Java methods would be any faster than using the ColdFusion methods. For this, I compared the following:
- Compare() - Built into ColdFusion
- String::CompareTo() - Built into Java string
- String::Equals() - Built into Java string
- String::EqualsIgnoreCase() - Built into Java string
Compare( strOne, strTwo )
Compare takes two strings and does what I assume is some sort of Bit manipulation to figure out if two strings are equal. If the two strings are equal, it returns zero. If the first string is "less than" the second string, it returns -1. If the first string is "greater than" the first, it returns 1.
String::CompareTo( strOne | objOne )
The Java string method CompareTo takes either a string or an object and tests for equality. I am not 100% sure, but I believe the return values to be that of the ColdFusion Compare method.
String::Equals( objOne )
The Java string method Equals takes an object and compares it to the string. This returns a boolean true/false.
String::EqualsIgnoreCase( strOne )
The Java string method EqualsIgnoreCase takes a string object and compares it to the string ignoring case. This returns a boolean true/false.
Now, for the test:
<!---
Start off with a test statement. This is the string to
which we will be comparing dynamic strings.
--->
<cfset strStatement = "I like gorillas that weigh 165 lbs." />
<!--- Set the number of iterations for testing. --->
<cfset intIterations = 5000 />
<!--- Test the ColdFusion Compare() method. --->
<cftimer label="NOT Compare" type="outline">
<!--- Loop for speed testing. --->
<cfloop index="intI" from="1" to="#intIterations#" step="1">
<!--- Create random test string. --->
<cfset strTest = (
"I like gorillas that weigh " &
NumberFormat( intI, "000" ) &
" lbs."
) />
<!--- Check to see if the statements match up. --->
#YesNoFormat(
NOT Compare( strStatement, strTest )
)#
</cfloop>
</cftimer>
<!--- Test the Java String::CompareTo() method. --->
<cftimer label="CompareTo" type="outline">
<!--- Loop for speed testing. --->
<cfloop index="intI" from="1" to="#intIterations#" step="1">
<!--- Create random test string. --->
<cfset strTest = (
"I like gorillas that weigh " &
NumberFormat( intI, "000" ) &
" lbs."
) />
<!--- Check to see if the statements match up. --->
#YesNoFormat(
strStatement.CompareTo( strTest )
)#
</cfloop>
</cftimer>
<!--- Test the Java String::Equals() method. --->
<cftimer label="Equals" type="outline">
<!--- Loop for speed testing. --->
<cfloop index="intI" from="1" to="#intIterations#" step="1">
<!--- Create random test string. --->
<cfset strTest = (
"I like gorillas that weigh " &
NumberFormat( intI, "000" ) &
" lbs."
) />
<!--- Check to see if the statements match up. --->
#YesNoFormat(
strStatement.Equals( strTest )
)#
</cfloop>
</cftimer>
<!--- Test the Java String::EqualsIgnoreCase() method. --->
<cftimer label="EqualsIgnoreCase" type="outline">
<!--- Loop for speed testing. --->
<cfloop index="intI" from="1" to="#intIterations#" step="1">
<!--- Create random test string. --->
<cfset strTest = (
"I like gorillas that weigh " &
NumberFormat( intI, "000" ) &
" lbs."
) />
<!--- Check to see if the statements match up. --->
#YesNoFormat(
strStatement.EqualsIgnoreCase( strTest )
)#
</cfloop>
</cftimer>
I was surprised by the results. The ColdFusion Compare() method outperformed all the Java methods. Remember though, we are talking 5000 iterations here. At low iterations, they all perform lightening fast. Here is trending order of speed:
- Compare()
- String::Equals()
- String::EqualsIgnoreCase()
- String::CompareTo()
The fact that CompareTo() was the slowest and Compare() was the fastest is a bit surprising. I assume one would be built on the other. The only reason I can hazard a guess at this is because CompareTo() can take either an object or a string, and the type conversion is throwing it off. But, then again, on the other hand, Equals() takes only and object and performs well. I wanted to test EqualsNoCase() because it ONLY takes a string and I wanted to see what that did for performance. I think though, any performance boost it got from the string parameter is counter-acted by the fact that it is not case sensitive.
One reason why I wanted to test this AT ALL is the fact that seeing "NOT Compare()" for equality is not that user friendly. I was hoping that Equals() would be faster since it is nicer to look at. However, now that I think about it, I should be using JavaCast() for all of these, which adds some readability issues. I guess it was just a fun test to do.
Want to use code from this post? Check out the license.
Reader Comments
Cool man thanks for taking the time to do this - I am in a project where I'm going to be manipulating strings from a db based on replace rules and I need to do a comparison once the manipulation/rules have been run on the database strings - then if the values are not the same as the origional, then I update the updated string to the db -
I was debating on what function to use and was leaning towards compare() as it was - this solidified it!
I just stumbled across this though google and thought I'd shed some light on it. The reason the CF method is faster is because when you call a Java method in CF it uses reflection to call the method... which is quite expensive.
In fact, what CF really does is call java.lang.Class#getMethods() [1], do a search through the result to find a matching name and then try and match up the types of the arguments. It does cache the match result so subsequent calls are faster, but there's definitely overhead from doing a method call like this.
On the other hand CF's Compare function might look like this in Java:
public int Compare( String a, String b ) {
int r = a.compareTo( b );
if( r > 0 ) return 1;
return r == 0 ? 0 : -1;
}
So it should definitely be faster than doing a lookup for the method.
Things have gotten a lot faster over the years though in the Java world... [2]. :) Shame there's such a horrible class loader issue in Java 1.6 :/
[1] http://java.sun.com/j2se/1.5.0/docs/api/java/lang/Class.html#getMethods()
[2] http://www.jguru.com/faq/view.jsp?EID=246569
@Elliott,
Thanks for the insight. Yeah, I have read a few really bad things about the Class Loader in Java 1.6. It seems like with CF8, a lot of people are going with Java 1.5. At least, that's what I think I've read.
Ben,
Is there a way in Coldfusion to do a SQL like comparison?
For example, I have a webform that has a ID drop down - the iD looks something like this "AC1200" or "PR1982" etc.
Now what i wanna do is look at the first 2 characters of the ID, and based on that, enter a certain value in a column in the DB. SO that every time a "AC" appears in the ID, I should enter a specific value. Similarly, for each "PR..." entry, I want another value.
I know we can do a "WHERE Var LIKE AC%" comparison in SQL, but not totally sure if and/or how that can be done in CF!
@UMTerp,
You can use REFind() for this:
<cfif REFind( "^AC", variable )> ... </cfif>
The RE = "regular expression". The "^" signifies the beginning of the string. So ^AC matches "AC" as the first part of the string.
You can also use the Left() function to get the first 2 characters of the term - even trim it first then use Left(string,count).
@Ben Nadel,
Great, it worked!! You got unbelievable reply speed! - within 6 mins of my query!!
Thanks!
@UMTerp,
Ha ha, glad to help.
@Kevin,
Using left is good also.
This is why I hate coldfusion. It doesn't hold to any conventions about how objects should be compared. For example, strings of different lengths get evaluated completely differently. I know of no other language that does this.
<cfscript>
a = '12345';
b = '12346';
// Should be false, and is false.
writeoutput('a eq b == #a eq b#<br />');
a = '2707200116272368271111';
b = '2707200116272368279465';
// Should be false, but isn't.
writeoutput('a eq b == #a eq b#<br />');
</cfscript>
@Chris, Ben and others,
Re. Chris's comment about the difference in comparison results, could anyone explain why that is happening?
@UMTerp,
My guess is that the strings that @Chris is comparing in the latter example are being converted to numbers for the comparison and the numbers that large cannot be compared well. As such, they get truncated and their truncations happen to be equal.
If you change the strings to have a preceeding, non-numeric value:
a = '_2707200116272368271111';
b = '_2707200116272368279465';
... then they are NOT equal. So, something is happen when it tries to convert them to numbers.
I've personally never had to deal with numbers this large, so it's never caused a problem. But, if you don't know why its happening, you can pull your hair out trying to debug this!
@UMTerp,
Incidentally, if you use the Compare() function, this works with strings explicitly:
#NOT Compare( a, b )# == NO
.. which is what we expect.
The most recent part of this discussion was also carried on over in the Railo group. Figure I would post a link for those interested.
http://groups.google.com/group/railo/browse_thread/thread/2b326e81c8e5e399?hl=en
Besides speed there is something to consider when choosing a comparison method:
Today I spend hours to find a "bug" in the simple statement
// if (left (tf, j) is curr_dp) { ....
if (compare (left (tf, j), curr_dp) is 0) { ....
the simple test "is" fooled me for hours ... given these values:
curr_dp is "+43", left (tf, j) is "043"
Both are strings, curr_dp comes from a query
curr_dp = Prefix_Query["DIAL_PREFIX"][i];
and
tf is a phone number like "0431234567"
When using the if statemet with "is", it always returned true. It took me hours because I never thought of such a behavior, because both are strings (at least in my mind). Obviously one CAN see them as numerics, which makes them match.
I will never use "is" again ... :-)
Hi Ben !
Firstly, thank you for your site and all the help it already provided me.
Now, I've a strange problem with a Compare function (same problem with a cfif eq statement):
when I pass two VARIABLES as arguments in the Compare function, it doesn't works, it returns a non equal resulat BUT the content of the variables (two Hash strings) are the same !
I get the content of a text file (which just contains one string: an encrypted password) as the variable "hashPassword"; and I also get a password encoded in a form, as FORM.password. Then, I hash this one like this: Hash(FORM.password) and the Compare is like this, in a cfif statement:
<cfif #Compare(Hash(FORM.password), hashPassword)# eq 0>
but it never returns zero :(
Do you have any idea of what's happening there ?
Actually ... no real problem with the Compare function.
It's just about the file reading. As I was reading ALL the text (even if the entire texte was only 1 line long) by a <cffile>, it was not correct.
I had to read the first line and only her with a fileReadLine...and the it works fine !
The function compare() is failing for me. I am getting a 1 when it should be a 0. Has anybody come accross this.
example ends up as 1 instead of 0.
Came across another strange one today:
I have a statement that compares two strings derived from filenames:
This has been working for years. Today it failed with the filename "582SustainingEngineeringPlanTemplate 20120417" - although both names are apparently identical, the comparison produced a false.
When I tried a Compare instead, that produced a true.
I can't imagine that these names are being converted into numbers, so it's a bit of a mystery.
Ok - I'm officially an idiot. After methodically eliminating every other possible cause (and possible fix) it turned out that the the two filenames I was comparing were NOT, in fact, identical. One of the names contained a typo that I still couldn't see after looking at it a million times (maybe BECAUSE I looked at it a million times).
So, as usual, I have reached wisdom through humiliation :o)