Use StringBuilder.deleteCharAt(int) and StringBuilder.delete(int, int) to handle sanitization more effectively

Comment From: stsypanov

@rstoyanchev done.

I've also measured performance impact with simple benchmark

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Fork(jvmArgsAppend = {"-Xms2g", "-Xmx2g"})
public class DropDoubleSlashBenchmark {

  @Benchmark
  public String ineffective(Data data) {
    String path = data.path;
    while (true) {
      int index = path.indexOf("//");
      if (index == -1) {
        break;
      }
      path = path.substring(0, index) + path.substring(index + 1);
    }
    return path;
  }

  @Benchmark
  public String effective(Data data) {
    StringBuilder path = new StringBuilder(data.path);
    while (true) {
      int index = path.indexOf("//");
      if (index == -1) {
        break;
      }
      path.deleteCharAt(index);
    }
    return path.toString();
  }

  @State(Scope.Thread)
  public static class Data {
    private final String path = "/home/" + "/path";
  }
}

and got the following results on my machine

JDK 8

effective                          avgt    47.882 ±   1.801   ns/op
ineffective                        avgt    51.620 ±   0.907   ns/op
effective:·gc.alloc.rate.norm      avgt   136.000 ±   0.001    B/op
ineffective:·gc.alloc.rate.norm    avgt   224.000 ±   0.001    B/op

JDK 11

effective                          avgt    33.949 ±   0.188   ns/op
ineffective                        avgt    47.735 ±   0.519   ns/op
effective:·gc.alloc.rate.norm      avgt   104.000 ±   0.001    B/op
ineffective:·gc.alloc.rate.norm    avgt   152.000 ±   0.001    B/op

On longer strings I think we'll have even better improvement.