blog.itcode.devblog.itcode.dev

[JAVA] split vs StringTokenizer

When solving algorithm problems, you inevitably end up handling input values. To cover various cases, you take user input directly and process it. When you do, nine times out of ten, the following situation comes up. To send a dataset, a collection of data is joined into a single string using a delimiter (space or comma).

[JAVA] split vs StringTokenizer

When solving algorithm problems, you inevitably end up handling input values. To cover various cases, you take user input directly and process it. When you do, nine times out of ten, the following situation comes up. To send a dataset, a collection of data is joined into a single string using a delimiter (space or comma).
RWB0104
@RWBwritten at 2021-06-13 16:56:01

When solving algorithm problems, you inevitably end up handling input values. To cover various cases, you take user input directly and process it. When you do, nine times out of ten, the following situation comes up. To send a dataset, a collection of data is joined into a single string using a delimiter (space or comma).

["A","B","C","D"]>"ABCD"[ "A", "B", "C", "D" ] -> "A B C D"

For example, to pass an array like ["A","B","C","D"][ "A", "B", "C", "D" ] as shown above, each element is separated by a space and delivered as "ABCD""A B C D". Personally, I usually use the split method, but while looking through algorithm solutions, I came across quite a few pieces of code using a class called StringTokenizer. Since it was a class I'd never seen before, and there must be some reason to deliberately use it in place of the far more accessible split, I decided to directly compare their performance. Execution speed is also an important metric in algorithms, so it's worth shaving off even a little bit of time. Unfortunately, my code optimization skills are terrible, so I need to trim these small details wherever I can. Not bothering to optimize the core logic and instead trying to save time on things like this does feel a bit like drinking diet soda while eating pizza to lose weight, but if StringTokenizer really does perform better, it would be worth applying to algorithm problems going forward.

ItemDetails
Language

🖼️ JAVA

OSWindows 10 64bit
CPUIntel i7-10700K
RAM32GB

The split method is the traditional method for splitting a string by a specific delimiter. It's a keyword that exists in many languages besides JAVA — C(++, #), JavaScript, Python, and so on — so it's the first thing most people try when they need to split a string in any language.

JAVA's split is a method included in the String class, the data type for string data. If you have string data, you can call split to split the string. It returns a String[] object.

Usage is as follows.

JAVA

import java.util.Arrays;

/**
 * 메인 클래스
 *
 * @author RWB
 * @since 2021.06.13 Sun 22:50:57
 */
public class Main
{
	/**
	 * 메인 함수
	 *
	 * @param args: [String[]] 매개변수
	 */
	public static void main(String[] args)
	{
		String text = "A B C D";
		
		String[] splited = text.split(" ");
		
		System.out.println(Arrays.toString(splited));
	}
}

The output is as follows.

TC

[A, B, C, D]

You can see that the string A B C D is split into [A, B, C, D] based on spaces. There's one other interesting point: JAVA's split method accepts a regular expression as the delimiter. If used well, this lets you use compound delimiters.

The direct reason I ended up writing this post. StringTokenizer is also a class specialized for splitting strings. Unlike split, which returns a String[], the difference is that it's its own standalone class.

You initialize and use it in a form like StringTokenizer tokenizer = new StringTokenizer("string");. Here are some useful methods to know when working with a StringTokenizer instance.

MethodReturn ValueDescription
countTokenintNumber of tokens
nextTokenStringNext token
hasMoreTokensbooleanWhether a next token exists

You can also add a delimiter as an argument to the constructor, like StringTokenizer tokenizer = new StringTokenizer("string", "delimiter");, to split on a delimiter of your choosing. If not specified separately, the default delimiter is \t\n\r\t, which splits on line breaks, spaces, and tabs. There's one thing to watch out for here: the default delimiter \t\n\r\t includes line breaks, spaces, and tabs all at once. In other words, if spaces and line breaks are mixed together, as in A B C D\nA B C D, both spaces and line breaks are split on, producing output like [A, B, C, D, A, B, C, D]. If you explicitly specify a delimiter in the constructor, you can prevent this. When you specify it directly, you can also use multiple characters, not just spaces or line breaks.

JAVA

import java.util.Arrays;
import java.util.StringTokenizer;

/**
 * 메인 클래스
 *
 * @author RWB
 * @since 2021.06.13 Sun 23:48:14
 */
public class Test
{
	/**
	 * 메인 함수
	 *
	 * @param args: [String[]] 매개변수
	 */
	public static void main(String[] args)
	{
		String text = "A B C D";
		
		StringTokenizer tokenizer = new StringTokenizer(text);
		
		String[] splited = new String[tokenizer.countTokens()];
		
		for (int i = 0; i < splited.length; i++)
		{
			splited[i] = tokenizer.nextToken();
		}
		
		System.out.println(Arrays.toString(splited));
	}
}

The output is the same.

TC

[A, B, C, D]

So how do split and StringTokenizer compare in terms of performance? I wrote a simple test program to compare them.

  1. Assign a repeat count t.
  2. For each case, generate a random string of 5 to 20 characters. Each character is separated by a space.
  3. Split the string using a space as the delimiter
    1. Using split
    2. Using StringTokenizer
  4. Calculate the total elapsed time and average elapsed time
  5. Display the results

The source is as follows.

JAVA

import java.text.DecimalFormat;
import java.util.Arrays;
import java.util.Random;
import java.util.StringTokenizer;

/**
 * 메인 클래스
 *
 * @author RWB
 * @since 2021.06.14 Mon 00:06:32
 */
public class Main
{
	/**
	 * 메인 함수
	 *
	 * @param args: [String[]] 매개변수
	 */
	public static void main(String[] args)
	{
		int t = 10000;
		
		long[] timer = { 0, 0 };
		
		int[] sum = { 0, 0 };
		
		for (int i = 0; i < t; i++)
		{
			int random = (int) ((Math.random() * (20 - 5)) + 5);
			
			String text = getTestString(random);
			
			// split 로직 ----------------------------------------
			long timeStart = System.nanoTime();
			
			String[] a1 = useSplit(text);
			
			long timeEnd = System.nanoTime() - timeStart;
			
			sum[0] += a1.length;
			
			timer[0] += timeEnd;
			
			System.out.println(Arrays.toString(a1) + ": " + addComma(timeEnd) + "ns");
			// split 로직 ----------------------------------------
			
			// StringTokenizer 로직 ----------------------------------------
			timeStart = System.nanoTime();
			
			String[] a2 = useStringTokenizer(text);
			
			timeEnd = System.nanoTime() - timeStart;
			
			sum[1] += a2.length;
			
			timer[1] += timeEnd;
			
			System.out.println(Arrays.toString(a2) + ": " + addComma(timeEnd) + "ns");
			// StringTokenizer 로직 ----------------------------------------
		}
		
		System.out.println(addComma(t) + "개 데이터 그룹 수행");
		
		System.out.println();
		
		System.out.println("split 결과");
		System.out.println(" * 총 소요: " + addComma(timer[0]) + "ns");
		System.out.println(" * 평균 소요: " + addComma((timer[0] / t)) + "ns");
		System.out.println(" * 분해한 요소: " + addComma(sum[0]) + "개");
		
		System.out.println();
		
		System.out.println("StringTokenizer 결과");
		System.out.println(" * 총 소요: " + addComma(timer[1]) + "ns");
		System.out.println(" * 평균 소요: " + addComma((timer[1] / t)) + "ns");
		System.out.println(" * 분해한 요소: " + addComma(sum[1]) + "개");
		
		System.out.println();
		
		System.out.println("split " + (timer[0] == timer[1] ? "==" : (timer[0] > timer[1]) ? "<" : ">") + " StringTokenizer");
	}
	
	/**
	 * 구분된 문자열 반환 함수 (split)
	 *
	 * @param text: [String] 대상 문자열
	 *
	 * @return [String[]] 구분된 문자열
	 */
	private static String[] useSplit(String text)
	{
		return text.split(" ");
	}
	
	/**
	 * 구분된 문자열 반환 함수 (StringTokenizer)
	 *
	 * @param text: [String] 대상 문자열
	 *
	 * @return [String[]] 구분된 문자열
	 */
	private static String[] useStringTokenizer(String text)
	{
		StringTokenizer tokenizer = new StringTokenizer(text, " ");
		
		int count = tokenizer.countTokens();
		
		String[] result = new String[count];
		
		for (int i = 0; i < count; i++)
		{
			result[i] = tokenizer.nextToken();
		}
		
		return result;
	}
	
	/**
	 * 무작위 문자열 반환 함수
	 *
	 * @param n: [int] 문자 갯수
	 *
	 * @return [String] 무작위 문자
	 */
	private static String getTestString(int n)
	{
		Random random = new Random();
		
		StringBuilder builder = new StringBuilder();
		
		for (int i = 0; i < n; i++)
		{
			builder.append((char) ((random.nextInt(26)) + 97)).append(" ");
		}
		
		return builder.toString().trim();
	}
	
	/**
	 * 1000 단위 구분 숫자 반환 함수
	 *
	 * @param num: [long] 대상 숫자
	 *
	 * @return [String] 1000 단위 구분 숫자
	 */
	private static String addComma(long num)
	{
		DecimalFormat format = new DecimalFormat(",###");
		
		return format.format(num);
	}
}

The results of running it 10 times per count are summarized in the tables below.

  • t=1t = 1
Test #split totalStringTokenizer totalSpeed
180.3us44.8ussplit < StringTokenizer
283.7us46.2ussplit < StringTokenizer
3136.6us31.8ussplit < StringTokenizer
4111.3us40.4ussplit < StringTokenizer
593.4us32.2ussplit < StringTokenizer
6104.5us28.7ussplit < StringTokenizer
740.1us42.7ussplit > StringTokenizer
840.1us42.7ussplit > StringTokenizer
9104.7us28.3ussplit < StringTokenizer
1038.3us29.2ussplit < StringTokenizer

With a single iteration, StringTokenizer wins decisively, 8 to 2.

  • t=100t = 100
Test #split totalStringTokenizer totalSpeed
11.12ms0.602mssplit < StringTokenizer
21.11ms0.612mssplit < StringTokenizer
31.06ms0.562mssplit < StringTokenizer
41.02ms0.595mssplit < StringTokenizer
51.ms0.550mssplit < StringTokenizer
61.16ms0.651mssplit < StringTokenizer
798ms0.558mssplit < StringTokenizer
81.11ms0.627mssplit < StringTokenizer
90.981ms0.555mssplit < StringTokenizer
101.23ms0.666mssplit < StringTokenizer

At 100 iterations, StringTokenizer again wins decisively, 10 to 0.

  • t=1,000t = 1,000
Test #split totalStringTokenizer totalSpeed
13.00ms3.17mssplit > StringTokenizer
22.53ms2.71mssplit > StringTokenizer
32.79ms2.84mssplit > StringTokenizer
42.53ms2.67mssplit > StringTokenizer
52.67ms2.97mssplit > StringTokenizer
62.58ms2.87mssplit > StringTokenizer
72.48ms2.65mssplit > StringTokenizer
82.69ms3.01mssplit > StringTokenizer
92.50ms2.90mssplit > StringTokenizer
102.62ms2.94mssplit > StringTokenizer

The reason I jumped from clean powers like 212^1, 232^3 straight to 1000 out of nowhere is that, oddly enough, split wins decisively at t=1,000t = 1,000.

  • t=10,000t = 10,000
Test #split totalStringTokenizer totalSpeed
19.91ms9.27mssplit < StringTokenizer
29.49ms9.19mssplit < StringTokenizer
39.02ms8.61mssplit < StringTokenizer
49.95ms9.25mssplit < StringTokenizer
59.03ms8.87mssplit < StringTokenizer
68.83ms9.08mssplit > StringTokenizer
79.14ms8.68mssplit < StringTokenizer
89.28ms9.07mssplit < StringTokenizer
99.49ms9.66mssplit > StringTokenizer
1011.79ms11.20mssplit < StringTokenizer

StringTokenizer wins decisively again, 8 to 2.

  • t=1,000,000t = 1,000,000
Test #split totalStringTokenizer totalSpeed
1306.86ms373.06mssplit > StringTokenizer
2287.26ms262.05mssplit < StringTokenizer
3289.92ms255.51mssplit < StringTokenizer
4272.43ms267.96mssplit < StringTokenizer
5278.35ms322.28mssplit > StringTokenizer
6285.23ms264.57mssplit < StringTokenizer
7273.37ms268.18mssplit < StringTokenizer
8278.65ms264.34mssplit < StringTokenizer
9278.56ms266.62mssplit < StringTokenizer
10306.00ms256.56mssplit < StringTokenizer

StringTokenizer wins decisively, 8 to 2.

Except for the peculiar case of t=1,000t = 1,000, StringTokenizer generally performs better. I don't fully understand why that particular case behaves that way. Of course, since statistics become more meaningful as the sample size grows, it's hard to draw firm conclusions from just 10 runs.

On my work computer (AMD Ryzen 2700X), StringTokenizer was faster in every single case. There may be some variation in the computation results or behavior depending on the CPU.

According to the JAVA API, StringTokenizer is a legacy class kept around for backward compatibility. The JAVA API recommends using split or the regex package instead of StringTokenizer wherever possible.

Original text
StringTokenizer is a legacy class that is retained for compatibility reasons although its use is discouraged in new code. It is recommended that anyone seeking this functionality use the split method of String or the java.util.regex package instead.

Based on the numbers in the tables, StringTokenizer is up to about 20% faster than split. However, the JAVA API recommends using alternatives wherever possible, and even after a million operations, the difference stays in the millisecond range. So while there's a relative difference, in objective terms there's not much difference at all. Rather than reaching for a whole new class just to split strings, I think it's more efficient to just use split, which operates directly on the string itself.

# JAVA# String# split# StringTokenizer
ship
blog.itcode.dev

Notes from the π-th Alpaca

7.0.1
Developed by RWB since 2021.057th upgraded at 2026.08