All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.arcadedb.index.CompressedAny2RIDIndex Maven / Gradle / Ivy

There is a newer version: 24.11.1
Show newest version
/*
 * Copyright © 2021-present Arcade Data Ltd ([email protected])
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * SPDX-FileCopyrightText: 2021-present Arcade Data Ltd ([email protected])
 * SPDX-License-Identifier: Apache-2.0
 */
package com.arcadedb.index;

import com.arcadedb.database.Binary;
import com.arcadedb.database.Database;
import com.arcadedb.database.RID;
import com.arcadedb.schema.Type;
import com.arcadedb.serializer.BinaryComparator;
import com.arcadedb.serializer.BinarySerializer;
import com.arcadedb.serializer.BinaryTypes;

import java.util.*;

/**
 * Map like optimized to avoid stressing the GC by using mechanical sympathy technique + compression of key and values.
 * This class is synchronized. Values are RIDs, key can be anything. This Map implementation doesn't support to overwrite a value.
 * Values cannot be null.
 * 
* A Binary object is used to store the hash table (the first part of it) and then keys and values. The key is serialized in the position * pointed by the hash table, then a fixed-size integer containing the next entry (with the same hash) and after that the compressed RID. *

* TODO support up to 4GB by using unsigned int */ public class CompressedAny2RIDIndex { private final Database database; private final BinarySerializer serializer; private final byte keyBinaryType; private final Type keyType; private final Binary chunk; private final int keys; private int totalEntries = 0; private int totalUsedSlots = 0; public class EntryIterator implements Iterator { private int posInHashTable = 0; private int nextKeyPos; private RID nextVertexRID; public boolean hasNext() { if (nextVertexRID != null) return true; if (totalUsedSlots == 0) return false; if (nextKeyPos > 0) { // IGNORE THE KEY AND TAKE THE VERTEX RID chunk.position(nextKeyPos); serializer.deserializeValue(database, chunk, keyBinaryType, null); // NEXT KEY ON SAME POSITION IN HASHTABLE nextKeyPos = chunk.getInt(); nextVertexRID = (RID) serializer.deserializeValue(database, chunk, BinaryTypes.TYPE_COMPRESSED_RID, null); return true; } // NEXT POSITION IN HASHTABLE for (; posInHashTable < keys; ++posInHashTable) { int posInChunk = chunk.getInt(posInHashTable * Binary.INT_SERIALIZED_SIZE); if (posInChunk > 0) { chunk.position(posInChunk); // IGNORE THE KEY AND TAKE THE VERTEX RID serializer.deserializeValue(database, chunk, keyBinaryType, null); nextKeyPos = chunk.getInt(); nextVertexRID = (RID) serializer.deserializeValue(database, chunk, BinaryTypes.TYPE_COMPRESSED_RID, null); ++posInHashTable; return true; } } return false; } public RID next() { if (!hasNext()) throw new NoSuchElementException(); try { return nextVertexRID; } finally { nextVertexRID = null; } } } public CompressedAny2RIDIndex(final Database database, final Type keyType, final int expectedSize) throws ClassNotFoundException { this.database = database; this.keys = expectedSize; this.chunk = new Binary(expectedSize * 16); // 14 as an average size per entry this.chunk.setAllocationChunkSize(expectedSize); this.chunk.fill((byte) 0, keys * Binary.INT_SERIALIZED_SIZE); this.serializer = new BinarySerializer(database.getConfiguration()); this.keyType = keyType; this.keyBinaryType = keyType.getBinaryType(); } public Type getKeyBinaryType() { return keyType; } public EntryIterator vertexIterator() { return new EntryIterator(); } public int size() { return totalEntries; } public boolean isEmpty() { return totalEntries == 0; } public boolean containsKey(final Object key) { if (key == null) throw new IllegalArgumentException("Key is null"); return get(key) != null; } public RID get(final Object key) { synchronized (this) { return get(chunk, key); } } public RID get(final Binary threadBuffer, final Object key) { if (key == null) throw new IllegalArgumentException("Key is null"); final int hash = (key.hashCode() & 0x7fffffff) % keys; final int pos = threadBuffer.getInt(hash * Binary.INT_SERIALIZED_SIZE); if (pos == 0) return null; // SLOT OCCUPIED, CHECK FOR THE KEY threadBuffer.position(pos); while (true) { final Object slotKey = serializer.deserializeValue(database, threadBuffer, keyBinaryType, null); if (BinaryComparator.equals(slotKey, key)) { threadBuffer.position(threadBuffer.position() + Binary.INT_SERIALIZED_SIZE); return (RID) serializer.deserializeValue(database, threadBuffer, BinaryTypes.TYPE_COMPRESSED_RID, null); } final int nextPos = threadBuffer.getInt(); if (nextPos <= 0) break; threadBuffer.position(nextPos); } return null; } public void put(final K key, final RID value) { if (key == null) throw new IllegalArgumentException("Key is null"); if (value == null) throw new IllegalArgumentException("Value is null"); final int hash = (key.hashCode() & 0x7fffffff) % keys; synchronized (this) { final int pos = chunk.getInt(hash * Binary.INT_SERIALIZED_SIZE); if (pos == 0) { // NEW KEY chunk.position(chunk.size()); chunk.putInt(hash * Binary.INT_SERIALIZED_SIZE, chunk.position()); // WRITE THE KEY FIRST serializer.serializeValue(database, chunk, keyBinaryType, key); // LEAVE AN INT AS EMPTY SLOT FOR THE NEXT KEY chunk.putInt(0); // WRITE THE VALUE serializer.serializeValue(database, chunk, BinaryTypes.TYPE_COMPRESSED_RID, value); ++totalUsedSlots; } else { // SLOT OCCUPIED, CHECK FOR THE KEY chunk.position(pos); int lastNextPos; while (true) { final Object slotKey = serializer.deserializeValue(database, chunk, keyBinaryType, null); if (BinaryComparator.equals(slotKey, key)) throw new IllegalArgumentException("Key '" + key + "' is already present in the map"); lastNextPos = chunk.position(); final int nextPos = chunk.getInt(); if (nextPos <= 0) break; chunk.position(nextPos); } // APPEND TO THE END chunk.position(chunk.size()); final int entryPosition = chunk.position(); // WRITE THE KEY FIRST serializer.serializeValue(database, chunk, keyBinaryType, key); // LEAVE AN INT AS EMPTY SLOT FOR THE NEXT KEY chunk.putInt(0); // WRITE THE VALUE serializer.serializeValue(database, chunk, BinaryTypes.TYPE_COMPRESSED_RID, value); // WRITE THIS ENTRY POSITION TO THE PREVIOUS NEXT POSITION FIELD chunk.putInt(lastNextPos, entryPosition); } ++totalEntries; } } public int getChunkSize() { return chunk.size(); } public int getChunkAllocated() { return chunk.capacity(); } public int getTotalUsedSlots() { return totalUsedSlots; } public Binary getInternalBuffer() { return chunk; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy