我有一个大小约为1500万条目的非常大的文件。 文件中的每一行都包含一个单独的字符串(称之为键)。
我需要使用Java查找文件中的重复条目。 我试图使用哈希表来检测重复条目。 显然,这种方法会抛出"java.lang.OutOfMemoryError: Java heap space"错误。
如何解决这个问题?
我认为我可以增加堆空间并尝试它,但我想知道是否有更好、更有效的解决方案,而无需调整堆空间。
我有一个大小约为1500万条目的非常大的文件。 文件中的每一行都包含一个单独的字符串(称之为键)。
我需要使用Java查找文件中的重复条目。 我试图使用哈希表来检测重复条目。 显然,这种方法会抛出"java.lang.OutOfMemoryError: Java heap space"错误。
如何解决这个问题?
我认为我可以增加堆空间并尝试它,但我想知道是否有更好、更有效的解决方案,而无需调整堆空间。
关键在于你的数据无法适应内存。您可以使用外部排序算法:
将文件划分为多个适合内存的小块。对每个块进行排序,消除重复项(现在相邻元素)。
合并块并再次消除重复项。由于这里有n路合并,因此您可以在内存中保留来自每个块的下一个k个元素,一旦某个块的项目用尽(它们已经合并),则从磁盘抓取更多项目。
我不确定你是否会考虑在Java之外实现这个功能,但如果可以,使用shell非常简单:
cat file | sort | uniq
sort file | uniq --repeated
(也称为 uniq -d
)。Michael:永远不要写 cat file | something
,那只是愚蠢的行为,与 something < file
(或 something file
,如果结果相同)相比,浪费 CPU 时间和内存带宽。 - Peter Cordes您可能无法一次加载整个文件,但是可以将哈希值和行号存储在HashSet中,没有问题。
伪代码...
for line in file
entries.put(line.hashCode, line-number)
for entry in entries
if entry.lineNumbers > 1
fetch each line by line number and compare
RandomAccessFile.seek()
的偏移量。 - greybeard请注意,k可以等于1。
pivot[i-1] < line < pivot[i]
。如果您的字符串具有相当均匀的第一个或两个字符分布,则使用前一个或两个字符作为基数将输入散布到桶中要容易得多,而不是搜索枢轴列表。 - Peter Cordesexternal sort java
可以得到很多带有代码的结果)。然后,您可以逐行迭代文件,重复项现在显然会直接跟在彼此后面,所以您只需要在迭代时记住前一行即可。#!/bin/bash
# This script will sort a file and remove duplicates
# It will use external merge sort to do this
# It will use a temporary directory to store the sorted chunks
# It will use a temporary directory to store the merged chunks
# It will use a temporary directory to store the final sorted file
# The script will take the following parameters
# $1 - The file to sort
# $2 - The number of lines to sort in each chunk
# $3 - The number of chunks to merge at a time
# $4 - The temporary directory to use
# The script will output the sorted file to stdout
# The script will return 0 on success
# The script will return 1 if the file does not exist
# The script will return 2 if the temporary directory does not exist
# Check that the file exists
if [ ! -f "$1" ]; then
echo "The file $1 does not exist"
exit 1
fi
# Check that the temporary directory exists
if [ ! -d "$4" ]; then
echo "The temporary directory $4 does not exist"
exit 2
fi
# Create a temporary directory to store the sorted chunks
chunk_dir="$4/chunks"
mkdir -p "$chunk_dir"
# Create a temporary directory to store the merged chunks
merge_dir="$4/merge"
mkdir -p "$merge_dir"
# Create a temporary directory to store the final sorted file
sort_dir="$4/sort"
mkdir -p "$sort_dir"
# Split the file into chunks
split -l "$2" "$1" "$chunk_dir/chunk"
# Sort each chunk
for chunk in "$chunk_dir"/chunk*; do
sort "$chunk" > "$chunk.sorted"
done
# Merge the chunks
while [ $(ls "$chunk_dir" | wc -l) -gt 0 ]; do
# Merge the first $3 chunks
merge_chunks=""
for i in $(seq 1 "$3"); do
chunk=$(ls "$chunk_dir" | head -n 1)
merge_chunks="$merge_chunks $chunk_dir/$chunk"
rm "$chunk_dir/$chunk"
done
merge_file="$merge_dir/merge$(date +%s%N)"
sort -m "$merge_chunks" > "$merge_file"
# Remove duplicates from the merged file
uniq "$merge_file" > "$merge_file.uniq"
mv "$merge_file.uniq" "$merge_file"
# Move the merged file to the chunk directory
mv "$merge_file" "$chunk_dir"
done
# Move the final sorted file to the sort directory
mv "$chunk_dir"/* "$sort_dir"
# Output the sorted file to stdout
cat "$sort_dir"/*
# Remove the temporary directories
rm -rf "$chunk_dir"
rm -rf "$merge_dir"
rm -rf "$sort_dir"
关键是你的数据无法放入内存。(BrokenGlass)
如果有足够的内存来存储key哈希值到某个东西的Map,比如RandomAccessFile.seek()
的偏移量或像Andrew White建议的行号,您可以在识别出非唯一键时处理它们。
否则,在第一遍中建立哈希值到“可能之前见过”的映射(例如,使用key.hashCode() % (3<<23)
索引的3MB位图),在第二遍中仅处理至少命中两次的桶中的键。