Perl中哈希结构的深度拷贝

来源:互联网 发布:新东方烹饪学校 知乎 编辑:程序博客网 时间:2024/05/11 21:42

下午修改一段Perl代码的时候,遇到了一个深度拷贝的问题。废话少说,上代码:

    my ($rc, $refdata) = db_query($sql_text, "hashref");    unless ($rc) {        return ($rc, $refdata);    }    my @arrayhash = ();    foreach my $ref (@$refdata) {        # 这里$ref->{host_ip}的值为"10.136.142.205,127.0.0.1"        if (defined($ref->{"is_logserver_log"})                 && $ref->{"is_logserver_log"} eq "y"                 && defined($ref->{"host_ip"}))         {            foreach my $host (split(/,/, $ref->{"host_ip"})) {                $ref->{"host_ip"} = $host;                if ($ref->{"host_ip"} eq "127.0.0.1") {                    write_log("1  " . Dumper($ref) . "\n")                 }                push @arrayhash, $ref;            }        }        else {            push @arrayhash, $ref;        }    }    foreach my $tmp (@arrayhash) {        if ($tmp->{"host_ip"} eq "127.0.0.1") {            write_log("3  " . Dumper($tmp) . "\n")         }    }


上面这段代码用于将数据库中的记录集存入一个Hash数组,这段代码的执行结果在1处打印两次,但在3处却打印4次。显然不符合预期。
多出来的两条记录哪儿来的呢,唯一可能的就是内层的foreach以及push过程:

于是修改内层foreach代码为深度拷贝如下,测试OK:

            foreach my $host (split(/,/, $ref->{"host_ip"})) {                my $elem;                $elem->{$_} = $ref->{$_} foreach keys %$ref;                $elem->{"host_ip"} = $host;                push @arrayhash, $elem;            }


原来在内层foreach中$ref被修改时,并没有执行CopyOnWrite(写时复制)的过程,而是直接修改了$ref指向内容的值,即这里一直是浅拷贝。从而导致$ref指向的变量被修改,然后又被覆盖,改为深度拷贝后问题解决。

上面的深度拷贝只能拷贝一层Hash结构,如果数据结构更复杂一点便无法支持,这里提供一个支持任意复杂程序基于递归的深度拷贝函数:

use Scalar::Util qw(reftype refaddr);sub real_copy{    my ($value, $cloned) = @_;    return $value unless (ref($value));    my $addr = refaddr($value);    return $cloned->{$addr} if (exists($cloned->{$addr}));    my ($ref_type, $copy) = reftype($value);    if ($ref_type eq 'ARRAY') {        $copy = [];        $cloned->{$addr} = $copy;        push(@$copy, map {  real_copy($_, $cloned) } @$value);    }    elsif ($ref_type eq 'HASH') {        $copy = {};        $cloned->{$addr} = $copy;        foreach my $key (keys(%{$value})) {            $copy->{$key} =  real_copy($value->{$key}, $cloned);        }    }    elsif ($ref_type eq 'SCALAR') {        $copy = \do{ my $scalar = $$value; };        $cloned->{$addr} = $copy;    }    elsif ($ref_type eq 'REF') {        if ($addr == refaddr($$value)) {            $copy = \$copy;            $cloned->{$addr} = $copy;        }        else {            my $tmp;            $copy = \$tmp;            $cloned->{$addr} = $copy;            $tmp = real_copy($$value, $cloned);        }    }    return $copy;}


 

原创粉丝点击