Многие микробенчмарки, приведенные здесь, нашли числа в несколько наносекунд для таких вещей, как чтение массива / ArrayList. Это вполне разумно, если все находится в вашем кеше L1.
Доступ к кэшу более высокого уровня или доступу к основной памяти может иметь порядок времен порядка примерно от 10 нс до 100 нс, по сравнению с 1 нс для кеша L1. Доступ к ArrayList имеет дополнительное косвенное обращение к памяти, и в реальном приложении вы можете заплатить эту стоимость почти всегда, в зависимости от того, что ваш код делает между доступами. И, конечно, если у вас много маленьких списков ArrayList, это может увеличить использование памяти и повысить вероятность пропадания кэша.
Оригинальный постер, кажется, использует только один и получает доступ к большому количеству контента за короткое время, так что это не должно быть большими трудностями. Но это может отличаться для других людей, и вам следует остерегаться при интерпретации микробенчмарков.
Строки Java, однако, ужасно расточительны, особенно если вы храните много маленьких (просто посмотрите на них с помощью анализатора памяти, кажется, что> 60 байт для строки из нескольких символов). Массив строк имеет косвенную ссылку на объект String, а другой - от объекта String на char [], который содержит саму строку. Если что-то и взорвет ваш кэш L1, то это в сочетании с тысячами или десятками тысяч строк. Так что, если вы серьезно - действительно серьезно - о том, чтобы снизить как можно большую производительность, то вы можете посмотреть на это иначе. Вы могли бы, скажем, содержать два массива, char [] со всеми строками в нем, одну за другой, и int [] со смещениями в начале. Это будет PITA, чтобы делать что-нибудь, и вам почти наверняка это не нужно. И если вы это сделаете, вы выбрали не тот язык.