Perl引用入門

在perl中只有3種基本的數據結構:標量、數組、hash。變量能夠是數值,能夠是字符串。正則表達式

這三種基本數據結構的數據存儲方式以下:算法

可是,僅僅由這3種基本結構,就能夠構造出更復雜的數據結構,例如hash中用數組作value,數組中用hash作元素。數組

可是,perl對於底層的一些數據存儲,不少時候對這些數據是直接拷貝存儲的。而有些時候是不必去拷貝數據的,經過引用,能夠避免拷貝操做,哪裏須要數據,用數據對象的引用便可,也就是插一個"指針"的事。數據結構

如何表示數組和hash的引用

引用就像是指針,對一個引用進行輸出,在字符串上下文將返回它指向的目標數據的類型和內存地址,在數值上下文將返回地址空間。例如,數組的引用和hash的引用,以下圖。函數

上圖中,將數組@name的引用賦值給了一個名爲$arr_ref的標量,將hash@member的引用賦值給了一個名爲$hash_ref的標量。注意,引用都是標量ui

引用的方式很簡單,只需在 數組的@符號、hash的%符號以前加上反斜線\ 。例如\@name就表示這是名稱爲name的數組的引用,\%member就表示這是名稱爲member的hash的引用。操作系統

例如:scala

@name=qw(longshuai xiaofang wugui tuner);
%member=(
    longshuai => "18012345678",
    xiaofang  => "17012345678",
    wugui     => "16012345678",
    tuner     => "15012345678"
);

$arr_ref=\@name;      # 將數組的引用賦值給一個標量
$hash_ref=\%member;   # 將hash的引用賦值給一個標量

print "$arr_ref","\n";   # 輸出:ARRAY(0x18a5fa0)
print "$hash_ref","\n";  # 輸出:HASH(0x18a6060)

因爲引用被看成一個標量,因此能使用標量的地方,就能使用引用。例如,將一個數組的引用放進hash中:3d

@name=qw(longshuai xiaofang wugui tuner);
$arr_ref=\@name;

%member=(
    longshuai => "18012345678",
    xiaofang  => "17012345678",
    wugui     => "16012345678",
    tuner     => $arr_ref
);

print "$member{'tuner'}","\n";   # 輸出數組的引用:ARRAY(0x13d7fa0)

除數組、hash以外的引用

除數組、hash有引用,標量、函數也有引用,並且還不止這些,好比文件句柄引用、正則表達式引用等。指針

目前,暫時只需瞭解標量、數組、hash、函數的引用方式便可,分別爲:

\$x    # 引用 scalar
    \@y    # 引用 array
    \%z    # 引用 hash
    \&f    # 引用 function

標量引用的一個技巧

下面建立一個指向$foo1值的引用$ref_foo1,也就是說$ref_foo1和foo1是等價的,要引用這個變量的值,須要使用$$ref_foo1$foo1

my $ref_foo1 = \$foo1;

例如:

my $foo1="xyz";
my $ref_foo1 = \$foo1;
print "$$ref_foo1\n";

常常會看到下面一種代碼格式:

my $ref_foo2 = \my $foo2;

等號右邊\my $foo2表示先建立一個用my修飾的變量$foo2(由於未賦值,因此初始化爲undef),再建立一個指向它的引用$ref_foo2。和上面的示例相比,它只是多了一個臨時建立變量的功能,由於my的優先級高於\,它等價於\(my $foo2)

上面的語句還能夠賦值:

my $ref_foo2 = \(my $foo2="abcd");
print "$$ref_foo2\n";

引用計數器和引用做用域

能夠爲同一段數據對象建立多個引用,因爲每一個引用都指向同一段數據對象,因此相同數據對象的引用是徹底一致的。

因爲引用在數值上下文返回引用對象的地址空間,因此能夠用比較操做符==來比較引用,固然,使用eq來比較也徹底能夠:

@name=qw(longshuai xiaofang wugui tuner);
$arr_ref=\@name;
$arr_ref1=\@name;
$arr_ref2=$arr_ref;

print "ref equals\n" if $arr_ref == \@name;            # true
print "ref equal ref1\n" if $arr_ref == $arr_ref1;     # true
print "ref1 equal ref2\n" if $arr_ref1 == $arr_ref2;   # true

在perl中,對每段數據的引用都會維護一個引用計數器

  • 最初建立數據對象時,賦值給初始化數組、hash,引用數爲1
  • 之後每次引用、賦值引用、拷貝引用等操做都會對引用數加1
  • 將其賦值爲undef,則顯式取消引用關係,引用數減1
  • 引用有做用域,當離開做用域時,該做用域內的引用都將取消(可看做是賦值型的標量,當定義my時,超出邊界就失效)
  • 只要引用計數器還未減小爲0,用於存儲數據對象所佔用的內存就不會釋放。當引用計數爲0時,perl將回收這段內存空間,但不會交還給操做系統,perl再須要的時候會重用這段內存空間存儲新數據,而無需從新向操做系統申請開闢新內存

這和硬連接相似,每建立一個硬連接,硬連接數量就加1,每刪除一個硬連接,硬連接數量就減1。

#!/usr/bin/perl
use 5.010;

@names=qw(longshuai xiaofang wugui tuner);   # ref counter = 1

$arr_ref1=\@names;     # ref counter = 2
$arr_ref2=$arr_ref1;   # ref counter = 3
$arr_ref3=\@names;     # ref counter = 4
$arr_ref2=undef;       # ref counter = 3
@names=undef;          # ref counter = 2

say "arr: @names";       # 輸出:arr:
say "ref1: $arr_ref1";   # 輸出:ARRAY(0x55befad8f0a0)
say "ref2: $arr_ref2";   # 輸出:ref2:
say "ref3: $arr_ref3";   # 輸出:ARRAY(0x55befad8f0a0)

若是使用my或local這樣做用域關鍵詞去定義引用,當引用超出對應邊界後,就會失效,引用計數器就會對應減1。固然,若是不定義爲my或local,則超出邊界也不會失效。

#!/usr/bin/perl
use 5.010;

@names=qw(longshuai xiaofang wugui tuner);   # ref counter = 1

$arr_ref1=\@names;     # ref counter = 2
{
    my $arr_ref2=$arr_ref1;   # ref counter = 3
    my $arr_ref3=\@names;     # ref counter = 4
}      # ref counter = 2

say "ref1: $arr_ref1";
say "ref2: $arr_ref2";   # arr_ref2已經失效
say "arr: @names";
say "ref3: $arr_ref3";   # arr_ref3已經失效

有一點須要注意,就是將引用做爲子程序(函數)的參數時,它首先將引用賦值給@_特殊變量,這時引用計數會加1,當退出子程序時,@_將失效,引用計數器將減1

&mysub(\@names);    # 直接將引用賦值給@_
&mysub($arr_ref);   # 拷貝引用,@_也將引用數據對象

引用計數管理內存的優勢和缺點

perl採用引用計數的方式回收內存空間。引用計數管理內存的方式已經存在了好久,和GC(Garbage collection)垃圾回收機制是同一年(1960年)被研究出來的。不管是哪一種內存管理機制,都有優勢,也都有缺點。

對於引用計數管理方式而言,它最大的優勢在於:

  • 即刻回收:只要數據對象的引用計數器爲0了,就會馬上被回收
  • 暫停時長很短:由於回收速度時無需遍歷內存,因此負責回收的工做效率很高

使用引用計數管理內存最大的缺點在於:

  • 由於要頻繁增、減計數,負責增、減的工做壓力很是大
  • 沒法回收循環引用(引用環路問題,見下文)

相比於引用計數管理內存的方式,GC回收機制都是在內存不夠後,遍歷整個內存來回收的,因此有延遲性和低效性(有好幾種改進的GC,都各有優缺點,但原始的GC算法就是如此)。

其實不管是引用計數仍是GC,都對它們各自的缺點有各類改進,但修補缺陷的同時,也會損傷它們的優勢,因此不一樣語言針對不一樣適用場景,老是採用不一樣的折衷手段。

回到引用計數的一個缺點問題上:沒法回收循環引用問題。所謂循環引用,是指A引用B的對象,B又引用A的對象。例如:

@name1=qw(longshuai wugui);
@name2=qw(xiaofang tuner);
push @name1,\@name2;
push @name2,\@name1;

驗證下:

#!/usr/bin/perl
use 5.010;
@name1=qw(longshuai wugui);
@name2=qw(xiaofang tuner);

say "name1_1: ",\@name1;      # ARRAY(0xNAME1)
say "name2_1: ",\@name2;      # ARRAY(0xNAME2)

push @name1,\@name2;
push @name2,\@name1;

say "name1_2: @name1";      # ARRAY(0xNAME2)
say "name2_2: @name2";      # ARRAY(0xNAME1)

以下圖所示:

這樣的引用環路,使得@name1@name2對應的數據對象的引用,不管如何也沒法減小到0。

這樣會致使一個重大問題:內存泄漏(memory leak)。隨着泄漏愈來愈多,內存終有被耗盡的時刻。也許真正寫成內存泄漏的代碼比較少,但不少時候構建複雜的數據結構時,無心中可能就會出現引用環路問題。

要避免引用環路,可使用perl的另外一種引用行爲:弱引用(weak)。或者,在退出引用做用域以前,打破引用環路。例如:

{
    @name1=qw(longshuai wugui);
    @name2=qw(xiaofang tuner);
    push @name1,\@name2;
    push @name2,\@name1;
    ......
    # 退出做用域以前,在引用的內部清空引用數據
    @name1=();
    @name2=();
}

這樣,在退出做用域後,@name1=qw(longshuai wugui (這段空)),同理@name2。如此一來,@name1@name2又變回了最初狀態,都只有一個引用,且沒有了循環引用。

固然,上面是直接清空初始引用的,若是push進去的是引用變量,則清空引用變量便可。

{
    @name1=qw(longshuai wugui);
    @name2=qw(xiaofang tuner);
    $name1_ref=\@name1;
    $name2_ref=\@name2;
    
    push @name1,$name1;  # 使用引用變量,而非初始的數組名引用
    push @name2,$name2;
    ......
    # 退出做用域以前,在引用的內部清空引用數據
    $name1=undef;
    $name2=undef;
}

要檢查是否出現內存泄漏問題,可使用Test::Memory::Cycle模塊。

最後,perl目前尚未其它垃圾回收機制(garbage collection),也許在將來的版本中可能會引入新的gc管理方式來替代引用計數的管理方式。

相關文章
相關標籤/搜索