こんにちは。 エキサイト株式会社の三浦です。
コードを書いている時、たまに「一覧データの中から条件に合ったものを持ってくる」ことが必要になる場合があります。
一覧データの件数自体が大したことがなかったり、1~2件程度を持ってくるのであれば filter
を使えばいいですが、一覧データの件数や試行回数が多くなってくると、パフォーマンスが気になってきます。
今回は、高パフォーマンスで一覧データから合致データを持ってくる方法を紹介します。
一般的にどうするか
一般的に、一覧データの中から条件にあったものを持ってくる場合は以下のようにすると思います。
@Value public class SampleModel { Integer id; String data; } public class Sample { private SampleModel doSample() { List<SampleModel> sampleModelList = List.of( new SampleModel(1, "aaa"), new SampleModel(2, "bbb"), new SampleModel(3, "ccc"), new SampleModel(4, "ddd"), new SampleModel(5, "eee") ); Integer expectedId = 2; return sampleModelList .stream() .filter(sampleModel -> sampleModel.id == expectedId) .collect(Collectors.toList()) .get(0); } }
5件の SampleModel
の中から、 id
が2のものを取得してくる処理です。
この条件であれば、これで十分でしょう。
ですが、以下の場合はどうでしょうか?
@Value public class SampleModel { Integer id; String data; } public class Sample { private List<SampleModel> doSample() { List<SampleModel> sampleModelList = List.of( new SampleModel(1, "aaa"), new SampleModel(2, "bbb"), new SampleModel(3, "ccc"), new SampleModel(4, "ddd"), new SampleModel(5, "eee"), // 大量にデータが入る new SampleModel(9999, "end") ); List<Integer> expectedIdList = List.of( 2, 5, 87, // 大量にデータが入る 8799 ); return expectedIdList .stream() .map(expectedId -> doFilter(expectedId, sampleModelList)) .collect(Collectors.toList()); } private SampleModel doFilter(Integer expectedId, List<SampleModel> sampleModelList) { return sampleModelList .stream() .filter(sampleModel -> sampleModel.id == expectedId) .collect(Collectors.toList()) .get(0); } }
大量にある SampleModel
の中から、指定された大量の id
一覧に合致するものを持ってくる処理です。
動作自体はこれで問題ありませんが、各 List
が大量にある上に、 List
の map
の中で List
の filter
処理を行っているため、パフォーマンス的に良くないことは容易に想像できます。
では、このパフォーマンスを改善するにはどうすれば良いでしょうか。
HashMapを使って改善する
一覧データを扱う上で、Javaには List
形式の他に Map
形式と呼ばれるものがあります。
List
形式が、
0 -> sampleModelA 1 -> sampleModelB 2 -> sampleModelC ...
のように、自動的に割り振られた数値キーの一覧であるのに対し、 Map
形式は
ID1 -> sampleModelA ID2 -> sampleModelB ID3 -> sampleModelC ...
のように、明示的に指定したキーを元にした一覧です。
Map
の中でもその Map
の保存形式ごとに種類があり、 HashMap
はハッシュ値を使って保存しているため、ランダムアクセスに強い保存形式の Map
になります。
この「ハッシュ値を使っているためにランダムアクセスに強い」という HashMap
の特性を使うことで、先程のコードのパフォーマンスを改善できます。
具体的には、以下のような処理を行います。
@Value public class SampleModel { Integer Id; String data; } public class Sample { private List<SampleModel> doSample() { Map<Integer, SampleModel> sampleModelMap = new HashMap<Integer, SampleModel>() { { put(1, new SampleModel(1, "aaa")); put(2, new SampleModel(2, "bbb")); put(3, new SampleModel(3, "ccc")); // 大量にデータが入る put(9999, new SampleModel(9999, "end")); } }; List<Integer> expectedIdList = List.of( 2, 5, 87, // 大量にデータが入る 8799, ); return expectedIdList .stream() .map(expectedId -> sampleModelMap.get(expectedId)) .collect(Collectors.toList()); } }
HashMap
を使ってフィルタリングを行うようにした結果、ひたすら List
で filter
を行っていたときに比べてパフォーマンスが上がったはずです。
最後に
扱うデータ量が少ないのであればパフォーマンスをそこまで考慮する必要はないですが、大量のデータになってくると、段々とパフォーマンスも考慮する必要が出てきます。 Javaには同じ一覧データを扱う上でも様々な選択肢があるので、一度確認してみてもいいかもしれません。