StringBuilder vs. String considering replace - java

When doing concatenating lots of strings, I have been recommended to do it using a StringBuilder as such:
StringBuilder someString = new StringBuilder("abc");
someString.append("def");
someString.append("123");
someString.append("moreStuff");
as opposed to
String someString = "abc";
someString = someString + "def";
someString = someString + "123";
someString = someString + "moreStuff";
which would result in the creation of quite a few Strings, as opposed to one.
Now, I need to do a similar thing, but instead of using concatenation I use the replace method of String as such:
String someString = SOME_LARGE_STRING_CONSTANT;
someString = someString.replace("$VARIABLE1", "abc");
someString = someString.replace("$VARIABLE2", "def");
someString = someString.replace("$VARIABLE3", "123");
someString = someString.replace("$VARIABLE4", "moreStuff");
To accomplish the same thing using StringBuilder, I have to do this, just for one replace:
someString.replace(someString.indexOf("$VARIABLE1"), someString.indexOf("$VARIABLE1")+10, "abc");
So my question is: "Is it better to use String.replace and have lots of extra Strings created, or to use StringBuilder still, and have lots of long winded lines such as the one above?"

It is true that StringBuilder tends to be better than concatenating or modifying Strings manually, since StringBuilder is mutable, while String is immutable and you need to create a new String for each modification.
Just to note, though, the Java compiler will automatically convert an example like this:
String result = someString + someOtherString + anotherString;
into something like:
String result = new StringBuilder().append(someString).append(someOtherString).append(anotherString).toString();
That said, unless you're replacing a whole lot of Strings, go for whichever is more readable and more maintainable. So if you can keep it cleaner by having a sequence of 'replace' calls, go ahead and do that over the StringBuilder method. The difference will be negligible compared to the stress you save from dealing with the sad tragedy of micro-optimizations.
PS
For your code sample (which, as OscarRyz pointed out, won't work if you have more than one "$VARIABLE1" in someString, in which case you'll need to use a loop), you could cache the result of the indexOf call in:
someString.replace(someString.indexOf("$VARIABLE1"), someString.indexOf("$VARIABLE1")+10, "abc");
With
int index = someString.indexOf("$VARIABLE1");
someString.replace(index, index+10, "abc");
No need to search the String twice :-)

Guess what? If you are running with Java 1.5+ the concatenation works the same with string literals
String h = "hello" + "world";
and
String i = new StringBuilder().append("hello").append("world").toString();
Are the same.
So, the compiler did the work for you already.
Of course better would be:
String j = "hellworld"; // ;)
As for the second, yeap, that's preferred, but should't be that hard, with the power of "search and replace" and a bit of regex foo
For instance you can define a method like the one in this sample:
public static void replace( String target, String replacement,
StringBuilder builder ) {
int indexOfTarget = -1;
while( ( indexOfTarget = builder.indexOf( target ) ) >= 0 ) {
builder.replace( indexOfTarget, indexOfTarget + target.length() , replacement );
}
}
And your code currently looks like this:
someString = someString.replace("VARIABLE1", "abc");
someString = someString.replace("VARIABLE2", "xyz");
All you have to do is grab text editor an trigger something like this vi search and replace:
%s/^.*("\(.*\)".\s"\(.*\)");/replace("\1","\2",builder);
That read: "take anything in parenthesis and that looks like a string literal, and put it in this other string".
And your code will look from this:
someString = someString.replace("VARIABLE1", "abc");
someString = someString.replace("VARIABLE2", "xyz");
to this:
replace( "VARIABLE1", "abc", builder );
replace( "VARIABLE2", "xyz", builder );
In no time.
Here's a working demo:
class DoReplace {
public static void main( String ... args ) {
StringBuilder builder = new StringBuilder(
"LONG CONSTANT WITH VARIABLE1 and VARIABLE2 and VARIABLE1 and VARIABLE2");
replace( "VARIABLE1", "abc", builder );
replace( "VARIABLE2", "xyz", builder );
System.out.println( builder.toString() );
}
public static void replace( String target, String replacement,
StringBuilder builder ) {
int indexOfTarget = -1;
while( ( indexOfTarget = builder.indexOf( target ) ) > 0 ) {
builder.replace( indexOfTarget, indexOfTarget + target.length() ,
replacement );
}
}
}

I would say go for using StringBuilder but simply write a wrapper that facilitates making the code more readable and thus more maintainable, while still maintaining efficiency. =D
import java.lang.StringBuilder;
public class MyStringBuilder
{
StringBuilder sb;
public MyStringBuilder()
{
sb = new StringBuilder();
}
public void replace(String oldStr, String newStr)
{
int start = -1;
while ((start = sb.indexOf(oldStr)) > -1)
{
int end = start + oldStr.length();
sb.replace(start, end, newStr);
}
}
public void append(String str)
{
sb.append(str);
}
public String toString()
{
return sb.toString();
}
//.... other exposed methods
public static void main(String[] args)
{
MyStringBuilder sb = new MyStringBuilder();
sb.append("old old olD dudely dowrite == pwn");
sb.replace("old", "new");
System.out.println(sb);
}
}
OUTPUT:
new new olD dudely dowrite == pwn
Now you can just use the new version that is one easy liner
MyStringBuilder mySB = new MyStringBuilder();
mySB.append("old dudley dowrite == pwn");
mySB.replace("old", "new"):

Instead of having long lines like that, you could just write a method for replacing parts of StringBuilder strings, something along the lines of this:
public StringBuilder replace(StringBuilder someString, String replaceWhat, String replaceWith) {
return someString.replace(someString.indexOf(replaceWhat), someString.indexOf(replaceWhat)+replaceWhat.length(), replaceWith);
}

May be the String Class internally uses
indexOf
method to find index of old string and replace it with new string.
And also StringBuilder is not threadsafe so it executes much faster.

If your string really is large and you're worried about performance I would recommend writing a class which takes your template text and a list of variables, then reads over the source string character by character and builds the result using StringBuilder. That should be the most efficient both in terms of CPU and memory usage. Also, if you are reading this template text from a file I wouldn't load it all into memory up front. Process it in chunks as you read it from the file.
If you're just looking for a nice way to build a string that's not quite as efficient as StringBuilder but more efficient than appending strings over and over you can use String.format(). It works like sprintf() in C. MessageFormat.format() is an option too but it uses StringBuffer.
There is another related question here: Inserting a Java string in another string without concatenation?

All guys' codes have a bug .try yourReplace("x","xy").It will loop infinitely

Jam Hong is correct - the above solutions all contain the potential to loop infinitely. I guess the lesson to take away here is that micro optimisations can often cause all sorts of horrible issues and don't really save you much. Still, be that as it may - here is a solution that will not infinite loop.
private static void replaceAll(StringBuilder builder, String replaceWhat, String replaceWith){
int occuranceIndex = builder.indexOf(replaceWhat);
int lastReplace = -1;
while(occuranceIndex >= 0){
if(occuranceIndex >= lastReplace){
builder.replace(occuranceIndex, occuranceIndex+replaceWhat.length(), replaceWith);
lastReplace = occuranceIndex + replaceWith.length();
occuranceIndex = builder.indexOf(replaceWhat);
}else{
break;
}
}
}

while it's true that micro optimizations can be problematic, it sometimes depends on the context, for instance, if your replace happens to run inside of a loop with 10000 iterations, your will see a significant performance difference from the "useless" optimizations.
in most cases however, it's best to err on the side of readability

Related

In the build folder, why is a concat-file smaller then a "+"file? [Java]

As the Title states. Why is the file smaller? I know that "+" invokes a StringBuilder, but I still don't get why this whould lead to more memory usage when the end result is the same.
public class ConcatWithPlus {
public static void main(String[] s) {
String www = "www.";
String company = "nyhetsbolaget.";
String country = "se";
System.out.println(www+company+country);
vs
public class ConcatWithConcat {
public static void main(String[] s) {
String www = "www.";
String company = "nyhetsbolaget.";
String country = "se";
System.out.println(www.concat(company).concat(country));
}
}
When I try it with javac 1.8.0_152, ConcatWithConcat.class is smaller (568 bytes) than ConcatWithPlus.class (640 bytes). This is to be expected, since the expression www+company+country causes the compiler to generate code equivalent to the following:
StringBuilder __compilerGeneratedName = new StringBuilder();
__compilerGeneratedName
.append(www)
.append(company)
.append(country)
.toString();
... which is slightly more verbose than using concat, even though the end result is the same.
So why doesn't the compiler generate code equivalent to calling concat() if that's smaller? The Java API creators have determined that using StringBuilder is more efficient (perhaps because calling concat results in creation of intermediate strings).

Substituting multiple Strings in String

I know there are a lot of questions and answers related to similar questions but I couldn't find an answer to my question. This a small snippet of my code:
private String substitute(String text) {
List<Macro> macros = getMacros();
for (Macro macro : macros) {
text = StringUtils.replace(text, macro.getKey(), macro.getValue());
}
return text;
}
Would this be a good way to substitute multiple macros variables in a text String? This creates a new String object on every loop so I am wondering if there's a better way to do this. Ideally I would have used Apache Commons StrSubstitutor class but I can't because of the format of the tokens/macros (different formats and not between a fixed prefix/suffix). I also don't want to use Regex because of performance issues.
According to some coding rules at work I need to mark the argument as final. I wonder if that's indeed good practice here. I know that Strings are immutable and I know that whenever I call StringUtils.replace() it will return me a new String object. But I am wondering if the String argument here should be marked as final as suggested and in the method do something like this:
String result = text;
for (Macro macro : macros) {
result = StringUtils.replace(result, macro.getKey(), macro.getValue());
}
I just don't like this.
Any help would be appreciated. Thanks.
You can use apache velocity to replace a string with keys with the equivalent string with values.
Your concern seems to be valid. String is immutable so it creates multiple objects. You should you either use StringBuilder or StringBuffer.
I wrote a sample for you. Build from here
private static String substitute(String text) {
List<Macro> macros = getMacros();
StringBuffer st = new StringBuffer(text);
for (Macro macro : macros) {
int start = st.indexOf(macro.getKey());
if (start != -1) {
st.replace(start, start + macro.getKey().length(), macro.getValue());
}
}
return st.toString();
}
Cheers!!
If you have some concerns about performance you could use a StringBuilder, which allows you to declare the text param as final:
private String substitute(final String text) {
List<Macro> macros = getMacros();
StringBuilder stringBuilder=new StringBuilder(text);
for(Macro macro: macros) {
int index=stringBuilder.indexOf(macro.getKey());
if (index!=-1) {
stringBuilder.replace(index, index+macro.getKey().length(), macro.getValue());
}
}
return stringBuilder.toString();
}

alternate method for using substring on a String

I have a string which contains an underscore as shown below:
123445_Lisick
I want to remove all the characters from the String after the underscore. I have tried the code below, it's working, but is there any other way to do this, as I need to put this logic inside a for loop to extract elements from an ArrayList.
public class Test {
public static void main(String args[]) throws Exception {
String str = "123445_Lisick";
int a = str.indexOf("_");
String modfiedstr = str.substring(0, a);
System.out.println(modfiedstr);
}
}
Another way is to use the split method.
String str = "123445_Lisick";
String[] parts = string.split("_");
String modfiedstr = parts[0];
I don't think that really buys you anything though. There's really nothing wrong with the method you're using.
Your method is fine. Though not explicitly stated in the API documentation, I feel it's safe to assume that indexOf(char) will run in O(n) time. Since your string is unordered and you don't know the location of the underscore apriori, you cannot avoid this linear search time. Once you have completed the search, extraction of the substring will be needed for future processing. It's generally safe to assume the for simple operations like this in a language which is reasonably well refined the library functions will have been optimized.
Note however, that you are making an implicit assumption that
an underscore will exist within the String
if there are more than one underscore in the string, all but the first should be included in the output
If either of these assumptions will not always hold, you will need to make adjustments to handle those situations. In either case, you should at least defensively check for a -1 returned from indexAt(char) indicating that '_' is not in the string. Assuming in this situation the entire String is desired, you could use something like this:
public static String stringAfter(String source, char delim) {
if(source == null) return null;
int index = source.indexOf(delim);
return (index >= 0)?source.substring(index):source;
}
You could also use something like that:
public class Main {
public static void main(String[] args) {
String str = "123445_Lisick";
Pattern pattern = Pattern.compile("^([^_]*).*");
Matcher matcher = pattern.matcher(str);
String modfiedstr = null;
if (matcher.find()) {
modfiedstr = matcher.group(1);
}
System.out.println(modfiedstr);
}
}
The regex groups a pattern from the start of the input string until a character that is not _ is found.
However as #Bill the lizard wrote, i don't think that there is anything wrong with the method you do it now. I would do it the same way you did it.

How to convert a string[] into string?

I know how to separate the string to string[]. In my project, I use t = time.split("-") to split a time into a string array t, and t[0]=DD, t[1]=MM. Now I need to convert the string array t into string time with format DD-MM. Did java have functions for that?
Guava does:
String joined = Joiner.on('-').join(parts);
On the other hand, I'd actually suggest not splitting and joining your string to start with. Instead, parse it into an appropriate date/time type (ideally Joda Time), perform any manipulation you need, and then reformat it using a different format pattern.
This will improve your error detection, and basically make your code really reflect the nature of the data you're working with - instead of just talking about splitting and joining text.
You can use String Utils from Apache Commons like this:
String res = StringUtils.join(myStrings, "-");
If you are not looking to use external frameworks, you can roll your own, like this:
StringBuilder res = new StringBuilder();
boolean isFirst = true;
for (String s : myStrings) {
if (!isFirst) {
res.append('-');
} else {
isFirst = false;
}
res.append(s);
}
public static String join(String[] arr, String separator)
{
StringBuilder b = new StringBuilder();
for(int i = 0; i < arr.length; i++)
{
if(i != 0) b.append(separator);
b.append(arr[i]);
}
return b.toString();
}
For this simple example, what's wrong with this?
String time = t[0]+"-"+t[1];
Yes, use StringBuilder when performance matters, but this is much more concise.

Insert string in beginning of another string

How to insert a string enclosed with double quotes in the beginning of the StringBuilder and String?
Eg:
StringBuilder _sb = new StringBuilder("Sam");
I need to insert the string "Hello" to the beginning of "Sam" and O/p is "Hello Sam".
String _s = "Jam";
I need to insert the string "Hello" to the beginning of "Jam" and O/p is "Hello Jam".
How to achieve this?
The first case is done using the insert() method:
_sb.insert(0, "Hello ");
The latter case can be done using the overloaded + operator on Strings. This uses a StringBuilder behind the scenes:
String s2 = "Hello " + _s;
Other answers explain how to insert a string at the beginning of another String or StringBuilder (or StringBuffer).
However, strictly speaking, you cannot insert a string into the beginning of another one. Strings in Java are immutable1.
When you write:
String s = "Jam";
s = "Hello " + s;
you are actually causing a new String object to be created that is the concatenation of "Hello " and "Jam". You are not actually inserting characters into an existing String object at all.
1 - It is technically possible to use reflection to break abstraction on String objects and mutate them ... even though they are immutable by design. But it is a really bad idea to do this. Unless you know that a String object was created explicitly via new String(...) it could be shared, or it could share internal state with other String objects. Finally, the JVM spec clearly states that the behavior of code that uses reflection to change a final is undefined. Mutation of String objects is dangerous.
Sure, use StringBuilder.insert():
_sb.insert(0, _s);
You can add a string at the front of an already existing one. for example, if I have a name string name, I can add another string name2 by using:
name = name2 + name;
Don't know if this is helpful or not, but it works. No need to use a string builder.
private static void appendZeroAtStart() {
String strObj = "11";
int maxLegth = 5;
StringBuilder sb = new StringBuilder(strObj);
if (sb.length() <= maxLegth) {
while (sb.length() < maxLegth) {
sb.insert(0, '0');
}
} else {
System.out.println("error");
}
System.out.println("result: " + sb);
}
import java.lang.StringBuilder;
public class Program {
public static void main(String[] args) {
// Create a new StringBuilder.
StringBuilder builder = new StringBuilder();
// Loop and append values.
for (int i = 0; i < 5; i++) {
builder.append("abc ");
}
// Convert to string.
String result = builder.toString();
// Print result.
System.out.println(result);
}
}
It is better if you find quotation marks by using the indexof() method and then add a string behind that index.
string s="hai";
int s=s.indexof(""");

Categories