Jump to content

Recommended Posts

Posted

So I have been looking into different ways to optimize and tune my code, when I ran into an article about strings. Now there are two types of objects in java, mutable objects, and immutable object; rather, changeable and unchangeable objects.

 

Unfortunately, a String is among those immutable objects. It cannot change once it has been created. Now you say, "Well, how is that possible? It's easy to set a String to a new value!" Of course that it is easy, but that is because the String class handles all the low level operations to 'modify' the String.

 

For example, let's say we have a simple class that takes a name input

 

import java.io.Serializable;
import java.util.Scanner;

public class RegisterUser implements Serializable {

   private static transient Scanner iReader;
   private static transient boolean nameOK = false;
   private static String username = null;

   public static void main (String args[]) {

       println("Hello, Welcome to the new user registration.");
       println("Fortunately, all we need at this point is your name.");
       
       iReader = new Scanner(System.in);

       while (!nameOK) {
           println("If you please, what is your first name?");
           username = formatCase(iReader.nextLine());
      
           println("Is " +username+ " correct? if it is, please enter Y");
           
           if (scanner.nextLine().toString().equalsIgnoreCase("y")) 
               nameOK = true;
       }

       nameOK = false;

       while (!nameOK) {
           println("Thank you. Now, what is your last name?");

           String lastName = formatCase(iReader.nextLine());
      
           println("Is " +lastName+ " spelled right? if it is, please enter Y");
           
           if (scanner.nextLine().toString().equalsIgnoreCase("y")) {
               nameOK = true;
            
               username += " " + lastName;
           }
       }

       println("Thank you very much, "
               +username+ ", your username has successfully been registered.");

   }

   static String formatCase(String s) {
       return (s.length()>0) ?
           Character.toUpperCase(s.charAt(0))+s.substring(1) : s;
   }

   static void println(String s) {

       System.out.println(s);
   }
}

 

This class, from our point of view, is simple, and gets the job done quick. However, if we were to modify this to serve a large number of users concurrently, we'd run into a problem. When we do things, such as modify a string, we are not actually modifying it. We are creating two objects to replace it. When we used the line:

 

username += " " + lastName;[/Quote] we're actually not just saying "put these together, let's be quick."

 

Since the String is immutable, the only thing we can do is replace it with another string object with the same name, right? Exactly, so what that line is actually translated to is.

 

 username = new StringBuilder().append(username).append(" ").append("lastName").toString();

 

To better explain, to modify the username, we create a new StringBuilder object, and add the username, with what we're appending. Then, it creates a new String with the contents of the StringBuilder( using the toString method).

 

With a small application like the above, using the original code would be sufficient, and we'd not see a noticeable difference. However, if this were modified, and were used on a web server serving thousands of clients simultaneously, there would be a lot of overhead to be creating all these objects. Fortunately, The if we imitate the process to modifying a String on the get go, we can avoid the extra overhead of setting a string twice.

 

Let's take a look at a slightly modified version of our RegisterUser class:

 

import java.io.Serializable;
import java.util.Scanner;

public class RegisterUser implements Serializable {

   private static transient Scanner iReader;
   private static transient boolean nameOK = false;
   private static transient StringBuilder nBuilder= null;
   private static String username = null;

   public static void main (String args[]) {

       println("Hello, Welcome to the new user registration.");
       println("Fortunately, all we need at this point is your name.");
       
       iReader = new Scanner(System.in);

       while (!nameOK) {
           println("If you please, what is your first name?");
           nBuilder = new 
               StringBuilder().append(formatCase(iReader.nextLine()));
      
           println("Is " +nBuilder.toString()+ " correct? if it is, please enter Y");
           
           if (scanner.nextLine().toString().equalsIgnoreCase("y")) 
               nameOK = true;
       }

       nameOK = false;

       while (!nameOK) {
           println("Thank you. Now, what is your last name?");

           String lastName = formatCase(iReader.nextLine());
      
           println("Is " +lastName+ " spelled right? if it is, please enter Y");
           
           if (scanner.nextLine().toString().equalsIgnoreCase("y")) {
               nameOK = true;
            
               nBuilder.append( " ").append(lastName);
           }
       }

       username = nBuilder.toString();
       nBuilder = null;

       println("Thank you very much, "
               +username+ ", your username has successfully been registered.");

   }

   static String formatCase(String s) {
       return (s.length()>0) ?
           Character.toUpperCase(s.charAt(0))+s.substring(1) : s;
   }

   static void println(String s) {

       System.out.println(s);
   }
}

 

While this simple implementation is still imperfect, there is a large improvement in the long run. Sure, only creating 3 total objects instead of 4 may not seem like much, but if we were to use this on a much larger scale where a given string may be modified hundreds of times, we save a vast amount of memory and CPU cycles by using our own StringBuilder to manipulate Strings.

~GhostSnyper

http://i142.photobucket.com/albums/r116/ghostsnyper/Ilovethat1.jpg

Posted

I always find it interesting to read your posts Dan, even though they do loose me at some points :')

 

Thanks for this! :D

image.png

"I never see what has been done; I only see what remains to be done.." - Buddha

 

| My Profile | Message Me |

image.pngimage.png

Posted

Well to give you an example, I'll modify a program I wrote to show you guys just how big this problem can be. This snippet of code comes from my conversion tool I wrote. This chunk of code's purpose is to format the cached items to a database query, so it may be inserted. I chose not to use prepared statements, as that was too much work (if you don't know what a prepared statement is, I'm makng a post about it later on in the future). We're also going to add a timer, for hilarity's sake

 

       public void sendData (ArrayList list) {

	try {
		Statement statement = con.createStatement();

		String query = "INSERT INTO core_itemdefinitions (ID, Name, Description, LowAlch, HighAlch) VALUES ";

		int lol = 0, numObjects = 1; // numObjects starts at 1, because the string is already set, as you see above.
                       long timeElapsed = System.nanoTime();

		for (String[] s: list) {

			if (lol != list.size() - 1) {
				query += "('" + s[0] +"','" + s[1] + "','" + s[2] + "','" + s[3] + "','" + s[4] + "'), ";
				lol++;
                                       numObjects += 3;  //When you append to a string, you actually have to create three objects to replace the original string.
			}

			else {
				query += "('" + s[0] +"','" + s[1] + "','" + s[2] + "','" + s[3] + "','" + s[4] + "');";
                                       numObjects += 3;  //When you append to a string, you actually have to create three objects to replace the original string.
                               }
		}
		timeElapsed = (System.nanoTime() - timeElapsed) / 1000000L;

		System.out.println ("Querty created.\nTotal number of objects created: " +numObjects +".\nTime taken to process: " +timeElapsed +"ms, or " + (double) timeElapsed /1000 +" seconds.\nAttempting to sumbit to database");

		//statement.executeUpdate(query); //Leaving this out, because I've already submited the stuff I need.
	} catch (SQLException e) {e.printStackTrace(); }
}

 

The output of the program?

 

Item.cfg found.
File put into scanner buffer
Array list has been created. Starting the reading and conversion of the file
Configuration file read. Converted a total of 6541 items.
Preparing and initiating Database connection/transfer
Querty created. 
Total number of objects created: 19624.
Time taken to process: 27745ms, or 27.745 seconds.
Attempting to sumbit to database
Press any key to continue . . .

 

Now let's modify this program. We're going to use a StringBuilder instead. The reb, bolded text are the modifications I did to achieve the output

public void sendData (ArrayList list) {

		try {
			Statement statement = con.createStatement();

			[color=red][b]StringBuilder query =  new StringBuilder("INSERT INTO core_itemdefinitions (ID, Name, Description, LowAlch, HighAlch) VALUES ");
			String formattedQuery = null; //We technically haven't created this object yet, so don't count it[/b][/color]

			int lol = 0, numObjects = 1; // numObjects starts at 1, because the string is already set, as you see above.

			long timeElapsed = System.nanoTime();
			for (String[] s: list) {

				if (lol != list.size() - 1) {
					[color=red][b]query.append("('" + s[0] +"','" + s[1] + "','" + s[2] + "','" + s[3] + "','" + s[4] + "'), ");[/b][/color]
					lol++; //We don't add any objects, because we haven't created any!
				}
				else {
					[color=red][b] query.append("('" + s[0] +"','" + s[1] + "','" + s[2] + "','" + s[3] + "','" + s[4] + "');");[/b][/color]
                     [color=red][b]formattedQuery = query.toString();[/b][/color]
                     numObjects++; // only created one object, so increment it instead of causing overhead

                }
			}
			timeElapsed = (System.nanoTime() - timeElapsed) / 1000000L;

			System.out.println ("Querty created.\nTotal number of objects created: " +numObjects +".\nTime taken to process: " +timeElapsed +"ms, or " + (double) timeElapsed /1000 +" seconds.\nAttempting to sumbit to database");

			//statement.executeUpdate(query); //Leaving this out, because I've already submited the stuff I need.
		} catch (SQLException e) {e.printStackTrace(); }
}

 

The output?

Item.cfg found.
File put into scanner buffer
Array list has been created. Starting the reading and conversion of the file
Configuration file read. Converted a total of 6541 items.
Preparing and initiating Database connection/transfer
Querty created.
Total number of objects created: 2.
Time taken to process: 17ms, or 0.017 seconds.
Attempting to sumbit to database
Press any key to continue . . .

 

 

Now do you understand a bit more? We essentially did the second version of our program more than six thousand times, to achieve the same result. Now, even if you aren't a programmer, isn't that a huge difference? Imagine a RuneScape server that does this sort of string manipulation several times per player, per cycle. No wonder most servers are shit, eh?

~GhostSnyper

http://i142.photobucket.com/albums/r116/ghostsnyper/Ilovethat1.jpg

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...