77import java .text .MessageFormat ;
88import java .util .AbstractList ;
99import java .util .List ;
10- import java .util .Objects ;
1110import java .util .stream .Collectors ;
12- import java .util .stream .Stream ;
1311
1412import org .openstreetmap .gui .jmapviewer .Coordinate ;
1513import org .openstreetmap .josm .data .coor .LatLon ;
@@ -53,8 +51,7 @@ public Shape() {
5351 */
5452 public String encodeAsString (String separator ) {
5553 return getPoints ().stream ()
56- .flatMap (c -> Stream .of (c .getLat (), c .getLon ()))
57- .map (String ::valueOf )
54+ .map (c -> c .getLat () + separator + c .getLon ())
5855 .collect (Collectors .joining (separator ));
5956 }
6057
@@ -70,19 +67,7 @@ public static String encodeAsString(List<Shape> shapes) {
7067 }
7168
7269 public List <Coordinate > getPoints () {
73- return new AbstractList <Coordinate >() {
74- @ Override
75- public Coordinate get (int index ) {
76- double lat = coords .ypoints [index ] / LatLon .MAX_SERVER_INV_PRECISION ;
77- double lon = coords .xpoints [index ] / LatLon .MAX_SERVER_INV_PRECISION ;
78- return new Coordinate (lat , lon );
79- }
80-
81- @ Override
82- public int size () {
83- return coords .npoints ;
84- }
85- };
70+ return new CoordinateList (this .coords );
8671 }
8772
8873 /**
@@ -126,19 +111,101 @@ public void addPoint(String sLat, String sLon) {
126111
127112 @ Override
128113 public int hashCode () {
129- return Objects .hash (getPoints ());
114+ // This was Objects.hash(getPoints()), but that made 8-24MB of allocations on application startup
115+ return 31 + hashPolygon (this .coords ); // Arrays#hash, new array instantiation
130116 }
131117
132118 @ Override
133119 public boolean equals (Object obj ) {
134120 if (this == obj ) return true ;
135121 if (obj == null || getClass () != obj .getClass ()) return false ;
136122 Shape shape = (Shape ) obj ;
137- return Objects . equals ( getPoints () , shape .getPoints () );
123+ return equalsPolygon ( this . coords , shape .coords );
138124 }
139125
140126 @ Override
141127 public String toString () {
142128 return "Shape{coords=" + getPoints () + '}' ;
143129 }
130+
131+ /**
132+ * Hash a polygon
133+ * @param coords The polygon to hash
134+ * @return The hashcode to use; equivalent to {@link Polygon#hashCode()}, but zero allocations.
135+ */
136+ private static int hashPolygon (Polygon coords ) {
137+ // This is faster than coords.hashCode() by ~90% and performs effectively no memory allocations.
138+ // This was originally written to replace Objects.hash(getPoints()). The only difference is +31 on the return.
139+ // Objects.hash(getPoints()) -> Arrays.hash(getPoints()) -> sum of hashes of Coordinates
140+ // First, AbstractList#hashCode equivalent
141+ int hashCode = 1 ;
142+ for (int index = 0 ; index < coords .npoints ; index ++) {
143+ final double lat = coords .ypoints [index ] / LatLon .MAX_SERVER_INV_PRECISION ;
144+ final double lon = coords .xpoints [index ] / LatLon .MAX_SERVER_INV_PRECISION ;
145+ // Coordinate uses Object.hash(x, y) - new array instantiation *and* two Double instantiations
146+ // Double conversion is 3.22MB
147+ // The array creation for Object.hash(x, y) is 2.11 MB
148+ final int coordinateHash = 31 * (31 + Double .hashCode (lon )) + Double .hashCode (lat );
149+ hashCode = 31 * hashCode + coordinateHash ; // hashCode * 31 + coordinate.hashCode()
150+ }
151+ return hashCode ;
152+ }
153+
154+ /**
155+ * Check that two {@link Polygon}s are equal
156+ * @param first The first polygon to check
157+ * @param second The second polygon to check
158+ * @return {@code true} if the polygons are equal
159+ */
160+ private static boolean equalsPolygon (Polygon first , Polygon second ) {
161+ // If the coordinate lists aren't the same size, short circuit.
162+ // We aren't doing fuzzy comparisons here.
163+ if (first .npoints != second .npoints ) {
164+ return false ;
165+ }
166+ for (int i = 0 ; i < first .npoints ; i ++) {
167+ if (first .xpoints [i ] != second .xpoints [i ] ||
168+ first .ypoints [i ] != second .ypoints [i ]) {
169+ return false ;
170+ }
171+ }
172+ return true ;
173+ }
174+
175+ /**
176+ * A list of {@link Coordinate}s that attempts to be very efficient in terms of CPU time and memory allocations.
177+ */
178+ private static final class CoordinateList extends AbstractList <Coordinate > {
179+ private final Polygon coords ;
180+
181+ CoordinateList (Polygon coords ) {
182+ this .coords = coords ;
183+ }
184+
185+ @ Override
186+ public Coordinate get (int index ) {
187+ double lat = coords .ypoints [index ] / LatLon .MAX_SERVER_INV_PRECISION ;
188+ double lon = coords .xpoints [index ] / LatLon .MAX_SERVER_INV_PRECISION ;
189+ return new Coordinate (lat , lon );
190+ }
191+
192+ @ Override
193+ public int size () {
194+ return coords .npoints ;
195+ }
196+
197+ @ Override
198+ public boolean equals (Object o ) {
199+ if (o instanceof CoordinateList ) {
200+ CoordinateList other = (CoordinateList ) o ;
201+ return equalsPolygon (this .coords , other .coords );
202+ }
203+ return super .equals (o );
204+ }
205+
206+ @ Override
207+ public int hashCode () {
208+ return hashPolygon (this .coords );
209+ }
210+ }
144211}
0 commit comments