This repository has been archived by the owner on Mar 3, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 77
Ершов Вадим, Магистратура ИТМО "Распределенные веб-сервисы", hw 5 #291
Closed
Closed
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
64d9afa
HW 1 | Implemented InMemoryDao
vadim01er 79cfcb3
HW 1 | Fix CodeClimate
vadim01er 47858c8
Merge branch 'main' into hw1
incubos 967d46b
Merge pull request #1 from vadim01er/hw1
vadim01er d039828
Merge remote-tracking branch 'upstream/main'
vadim01er 18c5058
Merge remote-tracking branch 'upstream/main'
vadim01er 47de052
Merge branch 'polis-vk:main' into main
vadim01er f6aa84b
Merge remote-tracking branch 'origin/main'
vadim01er fc1318f
Merge branch 'polis-vk:main' into main
vadim01er fef40e2
Merge branch 'polis-vk:main' into main
vadim01er 9f92230
HW 5
vadim01er b730690
Merge branch 'polis-vk:main' into main
vadim01er 15909c1
Merge branch 'main' into hw5
vadim01er 1a57697
Merge branch 'main' into hw5
incubos cb0421e
Merge branch 'main' into hw5
incubos File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
287 changes: 287 additions & 0 deletions
287
src/main/java/ru/vk/itmo/ershovvadim/hw5/DiskStorage.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,287 @@ | ||
package ru.vk.itmo.ershovvadim.hw5; | ||
|
||
import ru.vk.itmo.BaseEntry; | ||
import ru.vk.itmo.Entry; | ||
|
||
import java.io.IOException; | ||
import java.lang.foreign.Arena; | ||
import java.lang.foreign.MemorySegment; | ||
import java.lang.foreign.ValueLayout; | ||
import java.nio.channels.FileChannel; | ||
import java.nio.charset.StandardCharsets; | ||
import java.nio.file.*; | ||
import java.util.*; | ||
|
||
public class DiskStorage { | ||
|
||
private final List<MemorySegment> segmentList; | ||
|
||
public DiskStorage(List<MemorySegment> segmentList) { | ||
this.segmentList = segmentList; | ||
} | ||
|
||
public Iterator<Entry<MemorySegment>> range( | ||
Iterator<Entry<MemorySegment>> firstIterator, | ||
MemorySegment from, | ||
MemorySegment to) { | ||
List<Iterator<Entry<MemorySegment>>> iterators = new ArrayList<>(segmentList.size() + 1); | ||
for (MemorySegment memorySegment : segmentList) { | ||
iterators.add(iterator(memorySegment, from, to)); | ||
} | ||
iterators.add(firstIterator); | ||
|
||
return new MergeIterator<>(iterators, Comparator.comparing(Entry::key, ThreadSafeDaoImpl::compare)); | ||
} | ||
|
||
public static void save(Path storagePath, Iterable<Entry<MemorySegment>> iterable) | ||
throws IOException { | ||
if (!iterable.iterator().hasNext()) { | ||
return; | ||
} | ||
final Path indexTmp = storagePath.resolve("index.tmp"); | ||
final Path indexFile = storagePath.resolve("index.idx"); | ||
|
||
try { | ||
Files.createFile(indexFile); | ||
} catch (FileAlreadyExistsException ignored) { | ||
// it is ok, actually it is normal state | ||
} | ||
List<String> existedFiles = Files.readAllLines(indexFile, StandardCharsets.UTF_8); | ||
|
||
String newFileName = String.valueOf(existedFiles.size()); | ||
|
||
long dataSize = 0; | ||
long count = 0; | ||
for (Entry<MemorySegment> entry : iterable) { | ||
dataSize += entry.key().byteSize(); | ||
MemorySegment value = entry.value(); | ||
if (value != null) { | ||
dataSize += value.byteSize(); | ||
} | ||
count++; | ||
} | ||
long indexSize = count * 2 * Long.BYTES; | ||
|
||
try ( | ||
FileChannel fileChannel = FileChannel.open( | ||
storagePath.resolve(newFileName), | ||
StandardOpenOption.WRITE, | ||
StandardOpenOption.READ, | ||
StandardOpenOption.CREATE | ||
); | ||
Arena writeArena = Arena.ofConfined() | ||
) { | ||
MemorySegment fileSegment = fileChannel.map( | ||
FileChannel.MapMode.READ_WRITE, | ||
0, | ||
indexSize + dataSize, | ||
writeArena | ||
); | ||
|
||
// index: | ||
// |key0_Start|value0_Start|key1_Start|value1_Start|key2_Start|value2_Start|... | ||
// key0_Start = data start = end of index | ||
long dataOffset = indexSize; | ||
int indexOffset = 0; | ||
for (Entry<MemorySegment> entry : iterable) { | ||
fileSegment.set(ValueLayout.JAVA_LONG_UNALIGNED, indexOffset, dataOffset); | ||
dataOffset += entry.key().byteSize(); | ||
indexOffset += Long.BYTES; | ||
|
||
MemorySegment value = entry.value(); | ||
if (value == null) { | ||
fileSegment.set(ValueLayout.JAVA_LONG_UNALIGNED, indexOffset, tombstone(dataOffset)); | ||
} else { | ||
fileSegment.set(ValueLayout.JAVA_LONG_UNALIGNED, indexOffset, dataOffset); | ||
dataOffset += value.byteSize(); | ||
} | ||
indexOffset += Long.BYTES; | ||
} | ||
|
||
// data: | ||
// |key0|value0|key1|value1|... | ||
dataOffset = indexSize; | ||
for (Entry<MemorySegment> entry : iterable) { | ||
MemorySegment key = entry.key(); | ||
MemorySegment.copy(key, 0, fileSegment, dataOffset, key.byteSize()); | ||
dataOffset += key.byteSize(); | ||
|
||
MemorySegment value = entry.value(); | ||
if (value != null) { | ||
MemorySegment.copy(value, 0, fileSegment, dataOffset, value.byteSize()); | ||
dataOffset += value.byteSize(); | ||
} | ||
} | ||
} | ||
|
||
Files.move(indexFile, indexTmp, StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING); | ||
|
||
List<String> list = new ArrayList<>(existedFiles.size() + 1); | ||
list.addAll(existedFiles); | ||
list.add(newFileName); | ||
Files.write( | ||
indexFile, | ||
list, | ||
StandardOpenOption.WRITE, | ||
StandardOpenOption.CREATE, | ||
StandardOpenOption.TRUNCATE_EXISTING | ||
); | ||
|
||
Files.delete(indexTmp); | ||
} | ||
|
||
public static void deleteFiles(Path path) throws IOException { | ||
try (DirectoryStream<Path> pathStream = Files.newDirectoryStream(path)) { | ||
for (Path currentPath: pathStream) { | ||
if (Files.isDirectory(currentPath)) { | ||
deleteFiles(currentPath); | ||
} | ||
Files.delete(currentPath); | ||
} | ||
} | ||
} | ||
|
||
public static List<MemorySegment> loadOrRecover(Path storagePath, Arena arena) throws IOException { | ||
Path indexTmp = storagePath.resolve("index.tmp"); | ||
Path indexFile = storagePath.resolve("index.idx"); | ||
|
||
if (Files.exists(indexTmp)) { | ||
Files.move(indexTmp, indexFile, StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING); | ||
} else { | ||
try { | ||
Files.createFile(indexFile); | ||
} catch (FileAlreadyExistsException ignored) { | ||
// it is ok, actually it is normal state | ||
} | ||
} | ||
|
||
List<String> existedFiles = Files.readAllLines(indexFile, StandardCharsets.UTF_8); | ||
List<MemorySegment> result = new ArrayList<>(existedFiles.size()); | ||
for (String fileName : existedFiles) { | ||
Path file = storagePath.resolve(fileName); | ||
try (FileChannel fileChannel = FileChannel.open(file, StandardOpenOption.READ, StandardOpenOption.WRITE)) { | ||
MemorySegment fileSegment = fileChannel.map( | ||
FileChannel.MapMode.READ_WRITE, | ||
0, | ||
Files.size(file), | ||
arena | ||
); | ||
result.add(fileSegment); | ||
} | ||
} | ||
|
||
return result; | ||
} | ||
|
||
private static long indexOf(MemorySegment segment, MemorySegment key) { | ||
long recordsCount = recordsCount(segment); | ||
|
||
long left = 0; | ||
long right = recordsCount - 1; | ||
while (left <= right) { | ||
long mid = (left + right) >>> 1; | ||
|
||
long startOfKey = startOfKey(segment, mid); | ||
long endOfKey = endOfKey(segment, mid); | ||
long mismatch = MemorySegment.mismatch(segment, startOfKey, endOfKey, key, 0, key.byteSize()); | ||
if (mismatch == -1) { | ||
return mid; | ||
} | ||
|
||
if (mismatch == key.byteSize()) { | ||
right = mid - 1; | ||
continue; | ||
} | ||
|
||
if (mismatch == endOfKey - startOfKey) { | ||
left = mid + 1; | ||
continue; | ||
} | ||
|
||
int b1 = Byte.toUnsignedInt(segment.get(ValueLayout.JAVA_BYTE, startOfKey + mismatch)); | ||
int b2 = Byte.toUnsignedInt(key.get(ValueLayout.JAVA_BYTE, mismatch)); | ||
if (b1 > b2) { | ||
right = mid - 1; | ||
} else { | ||
left = mid + 1; | ||
} | ||
} | ||
|
||
return tombstone(left); | ||
} | ||
|
||
private static long recordsCount(MemorySegment segment) { | ||
long indexSize = indexSize(segment); | ||
return indexSize / Long.BYTES / 2; | ||
} | ||
|
||
private static long indexSize(MemorySegment segment) { | ||
return segment.get(ValueLayout.JAVA_LONG_UNALIGNED, 0); | ||
} | ||
|
||
private static Iterator<Entry<MemorySegment>> iterator(MemorySegment page, MemorySegment from, MemorySegment to) { | ||
long recordIndexFrom = from == null ? 0 : normalize(indexOf(page, from)); | ||
long recordIndexTo = to == null ? recordsCount(page) : normalize(indexOf(page, to)); | ||
long recordsCount = recordsCount(page); | ||
|
||
return new Iterator<>() { | ||
long index = recordIndexFrom; | ||
|
||
@Override | ||
public boolean hasNext() { | ||
return index < recordIndexTo; | ||
} | ||
|
||
@Override | ||
public Entry<MemorySegment> next() { | ||
if (!hasNext()) { | ||
throw new NoSuchElementException(); | ||
} | ||
MemorySegment key = slice(page, startOfKey(page, index), endOfKey(page, index)); | ||
long startOfValue = startOfValue(page, index); | ||
MemorySegment value = | ||
startOfValue < 0 | ||
? null | ||
: slice(page, startOfValue, endOfValue(page, index, recordsCount)); | ||
index++; | ||
return new BaseEntry<>(key, value); | ||
} | ||
}; | ||
} | ||
|
||
private static MemorySegment slice(MemorySegment page, long start, long end) { | ||
return page.asSlice(start, end - start); | ||
} | ||
|
||
private static long startOfKey(MemorySegment segment, long recordIndex) { | ||
return segment.get(ValueLayout.JAVA_LONG_UNALIGNED, recordIndex * 2 * Long.BYTES); | ||
} | ||
|
||
private static long endOfKey(MemorySegment segment, long recordIndex) { | ||
return normalizedStartOfValue(segment, recordIndex); | ||
} | ||
|
||
private static long normalizedStartOfValue(MemorySegment segment, long recordIndex) { | ||
return normalize(startOfValue(segment, recordIndex)); | ||
} | ||
|
||
private static long startOfValue(MemorySegment segment, long recordIndex) { | ||
return segment.get(ValueLayout.JAVA_LONG_UNALIGNED, recordIndex * 2 * Long.BYTES + Long.BYTES); | ||
} | ||
|
||
private static long endOfValue(MemorySegment segment, long recordIndex, long recordsCount) { | ||
if (recordIndex < recordsCount - 1) { | ||
return startOfKey(segment, recordIndex + 1); | ||
} | ||
return segment.byteSize(); | ||
} | ||
|
||
private static long tombstone(long offset) { | ||
return 1L << 63 | offset; | ||
} | ||
|
||
private static long normalize(long value) { | ||
return value & ~(1L << 63); | ||
} | ||
|
||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Мы обращаемся к полю
ThreadSafeDaoImpl
? Текут абстракции...