2

25k 反復のループ内で変更される一連の文字列があります。最初は空ですが、各サイクルで 0 ~ 200 個の文字列がランダムに追加または削除されます。最終的に、セットには約 80k 文字列が含まれます。
再開できるようにしたい。セットは、各サイクルの後にディスクに保存し、再開時にロードする必要があります。
どのライブラリを使用できますか? 生データの量は約 16M ですが、通常、変更はわずかです。反復ごとにストア全体を書き換えたくありません。

文字列はパスなので、次のようなログ ファイルに保存することを考えています。

+a
+b
commit
-b
+d
commit

最初に、ファイルはハッシュにロードされてから圧縮されます。最後にコミット行がない場合、最後のブロックは考慮されません。

4

2 に答える 2

0

重砲を片付けて、簡単なものを書くことにしました。

package LoL::IMadeADb;

sub new {
  my $self;
  ( my $class, $self->{dbname} )  = @_;
  # open for read, then write. create if not exist
  #msg "open $self->{dbname}";
  open(my $fd, "+>>", $self->{dbname}) or die "cannot open < $self->{dbname}: $!";
  seek($fd, 0, 0);
  $self->{fd} = $fd;
  #msg "opened";
  $self->{paths} = {};
  my $href = $self->{paths};

  $self->{nlines} = 0;
  my $lastcommit = 0;
  my ( $c, $rest );
  while(defined($c = getc($fd)) && substr(($rest = <$fd>), -1) eq "\n") {
    $self->{nlines}++;
    chomp($rest);
    if ($c eq "c") {
      $lastcommit = tell($fd);
      #msg "lastcommit: " . $lastcommit;
    } elsif ($c eq "+") {
      $href->{$rest} = undef;
    } elsif ($c eq "-") {
      delete $href->{$rest};
    }
    #msg "line: '" . $c . $rest . "'";
  }
  if ($lastcommit < tell($fd)) {
    print STDERR "rolling back incomplete file: " . $self->{dbname} . "\n";
    seek($fd, $lastcommit, 0);
    while(defined($c = getc($fd)) && substr(($rest = <$fd>), -1) eq "\n") {
      $self->{nlines}--;
      chomp($rest);
      if ($c eq "+") {
        delete $href->{$rest};
      } else {
        $href->{$rest} = undef;
      }
    }
    truncate($fd, $lastcommit) or die "cannot truncate $self->{dbname}: $!";
    print STDERR "rolling back incomplete file; done\n";
  }
  #msg "entries = " . (keys( %{ $href })+0) . ", nlines = " . $self->{nlines} . "\n";
  bless $self, $class
}

sub add {
  my ( $self , $path ) = @_;
  if (!exists $self->{paths}{$path}) {
    $self->{paths}{$path} = undef;
    print { $self->{fd} } "+" . $path . "\n";
    $self->{nlines}++;
    $self->{changed} = 1;
  }
  undef
}

sub remove {
  my ( $self , $path ) = @_;
  if (exists $self->{paths}{$path}) {
    delete $self->{paths}{$path};
    print { $self->{fd} } "-" . $path . "\n";
    $self->{nlines}++;
    $self->{changed} = 1;
  }
  undef
}

sub save {
  my ( $self ) = @_;
  return undef unless $self->{changed};
  my $fd = $self->{fd};
  my @keys = keys %{$self->{paths}};
  if ( $self->{nlines} - @keys > 5000 ) {
    #msg "compacting";
    close($fd);
    my $bkpdir = dirname($self->{dbname});
    ($fd, my $bkpname) = tempfile(DIR => $bkpdir , SUFFIX => ".tmp" ) or die "cannot create backup file in: $bkpdir: $!";
    $self->{nlines} = 1;
    for (@keys) {
      print { $fd } "+" . $_ . "\n" or die "cannot write backup file: $!";
      $self->{nlines}++;
    }
    print { $fd } "c\n";
    close($fd);
    move($bkpname, $self->{dbname})
      or die "cannot rename " . $bkpname . " => " . $self->{dbname} . ": $!";
    open($self->{fd}, ">>", $self->{dbname}) or die "cannot open < $self->{dbname}: $!";
  } else {
    print { $fd } "c\n";
    $self->{nlines}++;

    # flush:
    my $previous_default = select($fd);
    $| ++;
    $| --;
    select($previous_default);
  }
  $self->{changed} = 0;
  #print "entries = " . (@keys+0) . ", nlines = " . $self->{nlines} . "\n";
  undef
}
1;
于 2013-05-02T17:09:35.423 に答える