1.java容器都有哪些?(容器指的是集合類)
基本概念
Java容器類類庫的用途是“持有對象”涂炎,并將其劃分為兩個不同的概念:
1)Collection:一個獨(dú)立元素的序列纲菌,這些元素都服從一條或者多條規(guī)則钱慢。?List必須按照插入的順序保存元素,而set不能有重復(fù)的元素缘眶。Queue按照排隊規(guī)則來確定對象產(chǎn)生的順序(通常與它們被插入的順序相同)腻窒。?
2)Map:一組成對的“鍵值對”對象,允許你使用鍵來查找值磅崭。
|Collection?
| ├List?
| │-├LinkedList?
| │-├ArrayList?
| │-└Vector?
| │ └Stack?
| ├Set?
| │├HashSet?
| │├TreeSet?
| │└LinkedSet?
|?
|Map?
├Hashtable?
├HashMap?
└WeakHashMap
2.Collection 和 Collections 有什么區(qū)別儿子?
注:?1、java.util.Collection 是一個集合接口砸喻。它提供了對集合對象進(jìn)行基本操作的通用接口方法柔逼。Collection接口在Java 類庫中有很多具體的實現(xiàn)蒋譬。Collection接口的意義是為各種具體的集合提供了最大化的統(tǒng)一操作方式。?
2愉适、java.util.Collections 是一個包裝類犯助。它包含有各種有關(guān)集合操作的靜態(tài)多態(tài)方法。此類不能實例化维咸,就像一個工具類剂买,服務(wù)于Java的Collection框架。
3.List癌蓖、Set瞬哼、Map 之間的區(qū)別是什么?
答:
1.List:
可以允許重復(fù)對象租副、可以插入多個null元素坐慰、是一個有序容器
2.Set:
不允許重復(fù)對象、只允許一個null元素用僧、無序容器
3.Map:
?Map不是Collection的子接口或?qū)崿F(xiàn)類结胀。Map是一個接口、Map 的每個Entry都特有兩個對象责循,也就是一個鍵一個值糟港,Map可能會持有相同的值對象但鍵對象必須是唯一的、Map里可以擁有隨意個niull值但最多只能有一個null鍵
4.HashMap 和 Hashtable 有什么區(qū)別院仿?
?1.Map是一個以鍵值對存儲的接口着逐。Map下有兩個具體的實現(xiàn),分別是HashMap和HashTable.
2.HashMap是線程非安全的意蛀,HashTable是線程安全的,所以HashMap的效率高于HashTable.
3.HashMap允許鍵或值為空健芭,而HashTable不允許鍵或值為空.
4.繼承關(guān)系不同:
???HashTable?
????????????public class Hashtable<K,V> extends Dictionary<K,V>1.0
????????????implements Map<K,V>, Cloneable, java.io.Serializable {}
???HashMap
????????????public class HashMap<K,V>?extends AbstractMap<K,V>1.2
????????????implements Map<K,V>, Cloneable, Serializable{}
5.HashMap性能為什么優(yōu)于Hashtable县钥?
?????HashTable容器使用synchronized來保證線程安全,但在線程競爭激烈的情況下HashTable的效率非常低下慈迈。因為當(dāng)一個線程訪問HashTable的同步方法時若贮,其他線程訪問HashTable的同步方法時,可能會進(jìn)入阻塞或輪詢狀態(tài)痒留。如線程1使用put進(jìn)行添加元素谴麦,線程2不但不能使用put方法添加元素,并且也不能使用get方法來獲取元素伸头,所以競爭越激烈效率越低匾效。
附加問題:
我們可以使用CocurrentHashMap來代替Hashtable嗎?
我們知道Hashtable是synchronized的恤磷,但是ConcurrentHashMap同步性能更好面哼,因為它僅僅根據(jù)同步級別對map的一部分進(jìn)行上鎖野宜。ConcurrentHashMap當(dāng)然可以代替HashTable,但是HashTable提供更強(qiáng)的線程安全性魔策。它們都可以用于多線程的環(huán)境匈子,但是當(dāng)Hashtable的大小增加到一定的時候,性能會急劇下降闯袒,因為迭代時需要被鎖定很長的時間虎敦。因為ConcurrentHashMap引入了分割(segmentation),不論它變得多么大政敢,僅僅需要鎖定map的某個部分其徙,而其它的線程不需要等到迭代完成才能訪問map。簡而言之堕仔,在迭代的過程中擂橘,ConcurrentHashMap僅僅鎖定map的某個部分,而Hashtable則會鎖定整個map摩骨。CocurrentHashMap在JAVA8中存在一個bug通贞,會進(jìn)入死循環(huán),原因是遞歸創(chuàng)建ConcurrentHashMap 對象
6.如何決定使用HashMap還是 TreeMap恼五?
7.說一下 HashMap 的實現(xiàn)原理昌罩?
如圖所示,HashMap底層是基于數(shù)組和鏈表實現(xiàn)的灾馒。其中有兩個重要的參數(shù):
容量茎用、負(fù)載因子
容量的默認(rèn)大小是16,負(fù)載因子是 0.75睬罗,當(dāng)?HashMap?的?size > 16*0.75時就會發(fā)生擴(kuò)容(容量和負(fù)載因子都可以自由調(diào)整)轨功。
Put方法:
首先會將傳入的Key做?hash運(yùn)算計算出 hashcode,然后根據(jù)數(shù)組長度取模計算出在數(shù)組中的 index 下標(biāo)。
由于在計算中位運(yùn)算比取模運(yùn)算效率高的多容达,所以HashMap規(guī)定數(shù)組的長度為?2^n古涧。這樣用?2^n - 1做位運(yùn)算與取模效果一致,并且效率還要高出許多花盐。
由于數(shù)組的長度有限羡滑,所以難免會出現(xiàn)不同的Key通過運(yùn)算得到的 index 相同,這種情況可以利用鏈表來解決算芯,HashMap 會在?table[index]處形成鏈表柒昏,采用頭插法將數(shù)據(jù)插入到鏈表中。
get方法:
get和 put 類似熙揍,也是將傳入的 Key 計算出 index 职祷,如果該位置上是一個鏈表就需要遍歷整個鏈表,通過?key.equals(k)來找到對應(yīng)的元素。
遍歷方式:
?Iterator<Map.Entry<String, Integer>> entryIterator = map.entrySet().iterator();
????????while (entryIterator.hasNext()) {
????????????Map.Entry<String, Integer> next = entryIterator.next();
????????????System.out.println("key=" + next.getKey() + " value=" + next.getValue());
????????}
Iterator<String> iterator = map.keySet().iterator();
????????while (iterator.hasNext()){
????????????String key = iterator.next();
????????????System.out.println("key=" + key + " value=" + map.get(key));
????????}
map.forEach((key,value)->{
????System.out.println("key=" + key + " value=" + value);});
強(qiáng)烈建議使用第一種EntrySet進(jìn)行遍歷堪旧。
第一種可以把key value同時取出削葱,第二種還得需要通過 key 取一次 value,效率較低, 第三種需要?JDK1.8以上淳梦,通過外層遍歷 table析砸,內(nèi)層遍歷鏈表或紅黑樹。
死循環(huán)問題:
在并發(fā)環(huán)境下使用HashMap容易出現(xiàn)死循環(huán)爆袍。
并發(fā)場景發(fā)生擴(kuò)容首繁,調(diào)用resize()方法里的?rehash()時,容易出現(xiàn)環(huán)形鏈表陨囊。這樣當(dāng)獲取一個不存在的?key時弦疮,計算出的index正好是環(huán)形鏈表的下標(biāo)時就會出現(xiàn)死循環(huán)。
所以HashMap只能在單線程中使用蜘醋,并且盡量的預(yù)設(shè)容量胁塞,盡可能的減少擴(kuò)容。
在JDK1.8中對?HashMap進(jìn)行了優(yōu)化: 當(dāng)?hash碰撞之后寫入鏈表的長度超過了閾值(默認(rèn)為8)并且?table的長度不小于64(否則擴(kuò)容一次)時压语,鏈表將會轉(zhuǎn)換為紅黑樹啸罢。
假設(shè)hash沖突非常嚴(yán)重,一個數(shù)組后面接了很長的鏈表胎食,此時重新的時間復(fù)雜度就是?O(n)扰才。
如果是紅黑樹,時間復(fù)雜度就是O(logn)厕怜。
大大提高了查詢效率衩匣。
多線程場景下推薦使用CocurrentHashMap。
8.說一下 HashSet 的實現(xiàn)原理粥航?
HashSet是一個不允許存儲重復(fù)元素的集合琅捏,它的實現(xiàn)比較簡單,只要理解了?HashMap递雀,HashSet就水到渠成了柄延。
成員變量:
首先了解下HashSet的成員變量:
????private transient HashMap<E,Object> map;
????// Dummy value to associate with an Object in the backing Map
????private static final Object PRESENT = new Object();
發(fā)現(xiàn)主要就兩個變量:
map:用于存放最終數(shù)據(jù)的。
PRESENT:是所有寫入 map 的?value值映之。
構(gòu)造函數(shù):
????public HashSet() {
????????map = new HashMap<>();
????}
????public HashSet(int initialCapacity, float loadFactor) {
????????map = new HashMap<>(initialCapacity, loadFactor);
????} ???
構(gòu)造函數(shù)很簡單,利用了HashMap初始化了?map蜡坊。
add:
????public boolean add(E e) {
????????return map.put(e, PRESENT)==null;
????}
比較關(guān)鍵的就是這個add()方法杠输。 可以看出它是將存放的對象當(dāng)做了?HashMap的健,value都是相同的?PRESENT秕衙。由于?HashMap的?key是不能重復(fù)的蠢甲,所以每當(dāng)有重復(fù)的值寫入到?HashSet時,value會被覆蓋据忘,但?key不會受到影響鹦牛,這樣就保證了?HashSet中只能存放不重復(fù)的元素搞糕。
總結(jié):
HashSet的原理比較簡單,幾乎全部借助于?HashMap來實現(xiàn)的曼追。
所以HashMap會出現(xiàn)的問題?HashSet依然不能避免窍仰。
9.ArrayList 和 LinkedList 的區(qū)別是什么?
Arraylist:底層是基于動態(tài)數(shù)組礼殊,根據(jù)下表隨機(jī)訪問數(shù)組元素的效率高驹吮,向數(shù)組尾部添加元素的效率高;但是晶伦,刪除數(shù)組中的數(shù)據(jù)以及向數(shù)組中間添加數(shù)據(jù)效率低碟狞,因為需要移動數(shù)組。例如最壞的情況是刪除第一個數(shù)組元素婚陪,則需要將第2至第n個數(shù)組元素各向前移動一位族沃。而之所以稱為動態(tài)數(shù)組,是因為Arraylist在數(shù)組元素超過其容量大泌参,Arraylist可以進(jìn)行擴(kuò)容(針對JDK1.8? 數(shù)組擴(kuò)容后的容量是擴(kuò)容前的1.5倍)脆淹,Arraylist源碼中最大的數(shù)組容量是Integer.MAX_VALUE-8,對于空出的8位及舍,目前解釋是 :①存儲Headerwords未辆;②避免一些機(jī)器內(nèi)存溢出,減少出錯幾率锯玛,所以少分配③最大還是能支持到Integer.MAX_VALUE(當(dāng)Integer.MAX_VALUE-8依舊無法滿足需求時).只要ArrayList的當(dāng)前容足夠大咐柜,add()操作向數(shù)組的尾部的效率非常高的,當(dāng)向數(shù)組指定位置添加yi據(jù)時攘残,會進(jìn)行大量的數(shù)組移動復(fù)制操作拙友。而數(shù)組復(fù)制時,最終將調(diào)用System.arraycopy()方法歼郭,因此add()操作的效率還是相當(dāng)高的遗契。盡管這樣當(dāng)向指定位置添加數(shù)據(jù)時也還是比Linkedlist慢,后者添加數(shù)據(jù)只需要改變指針指向即可病曾。Arraylist刪除數(shù)組也需要移動數(shù)組牍蜂,效率較慢。
Linkedlist基于鏈表的動態(tài)數(shù)組泰涂,數(shù)據(jù)添加刪除效率高鲫竞,只需要改變指針指向即可,但是訪問數(shù)據(jù)的平均效率低逼蒙,需要對鏈表進(jìn)行遍歷从绘。
總結(jié):
1、對于隨機(jī)訪問get和set,ArrayList覺得優(yōu)于LinkedList僵井,因為LinkedList要移動指針陕截。?對于新增和刪除操作add和remove,LinedList比較占優(yōu)勢批什,因為ArrayList要移動數(shù)據(jù)农曲。
2、各自效率問題:
10.如何實現(xiàn)數(shù)組和List之間的轉(zhuǎn)換渊季?
List轉(zhuǎn)數(shù)組:
toArray(arraylist.size()方法
數(shù)組轉(zhuǎn)List:
1.Arrays的asList(a)方法(效率不高)
2.通過集合工具類Collections.addAll()方法(最高效)通過Collections.addAll(arrayList, strArray)方式轉(zhuǎn)換朋蔫,根據(jù)數(shù)組的長度創(chuàng)建一個長度相同的List,然后通過Collections.addAll()方法却汉,將數(shù)組中的元素轉(zhuǎn)為二進(jìn)制驯妄,然后添加到List中,這是最高效的方法合砂。
11.ArrayList 和 Vector 的區(qū)別是什么哼拔?
ArrayList實現(xiàn)于?List渊跋、RandomAccess?接口险领∶际可以插入空數(shù)據(jù),也支持隨機(jī)訪問缘屹。
ArrayList相當(dāng)于動態(tài)數(shù)據(jù)凛剥,其中最重要的兩個屬性分別是:?elementData?數(shù)組,以及?size?大小轻姿。 在調(diào)用?add()?方法的時候:
????public boolean add(E e) {
????????ensureCapacityInternal(size + 1); ?// Increments modCount!!
????????elementData[size++] = e;
????????return true;
????}
首先進(jìn)行擴(kuò)容校驗犁珠。
將插入的值放到尾部,并將size + 1互亮。
如果是調(diào)用add(index,e)在指定位置添加的話:
????public void add(int index, E element) {
????????rangeCheckForAdd(index);
????????ensureCapacityInternal(size + 1); ?// Increments modCount!!
? ? ? ? //復(fù)制犁享,向后移動
????????System.arraycopy(elementData, index, elementData, index + 1,
?????????????????????????size - index);
????????elementData[index] = element;
????????size++;
????}
也是首先擴(kuò)容校驗。
接著對數(shù)據(jù)進(jìn)行復(fù)制豹休,目的是把index位置空出來放本次插入的數(shù)據(jù)炊昆,并將后面的數(shù)據(jù)向后移動一個位置。
其實擴(kuò)容最終調(diào)用的代碼:
????private void grow(int minCapacity) {
????????// overflow-conscious code
????????int oldCapacity = elementData.length;
????????int newCapacity = oldCapacity + (oldCapacity >> 1);
????????if (newCapacity - minCapacity < 0)
????????????newCapacity = minCapacity;
????????if (newCapacity - MAX_ARRAY_SIZE > 0)
????????????newCapacity = hugeCapacity(minCapacity);
????????// minCapacity is usually close to size, so this is a win:
????????elementData = Arrays.copyOf(elementData, newCapacity);
????}
也是一個數(shù)組復(fù)制的過程威根。
由此可見ArrayList的主要消耗是數(shù)組擴(kuò)容以及在指定位置添加數(shù)據(jù)凤巨,在日常使用時最好是指定大小,盡量減少擴(kuò)容洛搀。更要減少在指定位置插入數(shù)據(jù)的操作敢茁。
序列化:
由于ArrayList是基于動態(tài)數(shù)組實現(xiàn)的,所以并不是所有的空間都被使用姥卢。因此使用了?transient?修飾卷要,可以防止被自動序列化。
transient Object[] elementData;
因此ArrayList自定義了序列化與反序列化:
????private void writeObject(java.io.ObjectOutputStream s)
????????throws java.io.IOException{
????????// Write out element count, and any hidden stuff
????????int expectedModCount = modCount;
????????s.defaultWriteObject();
????????// Write out size as capacity for behavioural compatibility with clone()
????????s.writeInt(size);
????????// Write out all elements in the proper order.
//只序列化了被使用的數(shù)據(jù)
????????for (int i=0; i<size; i++) {
????????????s.writeObject(elementData[i]);
????????}
????????if (modCount != expectedModCount) {
????????????throw new ConcurrentModificationException();
????????}
????}
????private void readObject(java.io.ObjectInputStream s)
????????throws java.io.IOException, ClassNotFoundException {
????????elementData = EMPTY_ELEMENTDATA;
????????// Read in size, and any hidden stuff
????????s.defaultReadObject();
????????// Read in capacity
????????s.readInt(); // ignored
????????if (size > 0) {
????????????// be like clone(), allocate array based upon size not capacity
????????????ensureCapacityInternal(size);
????????????Object[] a = elementData;
????????????// Read in all elements in the proper order.
????????????for (int i=0; i<size; i++) {
????????????????a[i] = s.readObject();
????????????}
????????}
????}
當(dāng)對象中自定義了writeObject和 readObject 方法時独榴,JVM 會調(diào)用這兩個自定義方法來實現(xiàn)序列化與反序列化僧叉。
從實現(xiàn)中可以看出ArrayList只序列化了被使用的數(shù)據(jù)。
Vector:
Vector也是實現(xiàn)于?List?接口棺榔,底層數(shù)據(jù)結(jié)構(gòu)和?ArrayList?類似,也是一個動態(tài)數(shù)組存放數(shù)據(jù)瓶堕。不過是在?add()?方法的時候使用?synchronized?進(jìn)行同步寫數(shù)據(jù),但是開銷較大症歇,所以?Vector?是一個同步容器并不是一個并發(fā)容器郎笆。
以下是add()方法:
????public synchronized boolean add(E e) {
????????modCount++;
????????ensureCapacityHelper(elementCount + 1);
????????elementData[elementCount++] = e;
????????return true;
????}
以及指定位置插入數(shù)據(jù):
????public void add(int index, E element) {
????????insertElementAt(element, index);
????}
????public synchronized void insertElementAt(E obj, int index) {
????????modCount++;
????????if (index > elementCount) {
????????????throw new ArrayIndexOutOfBoundsException(index
?????????????????????????????????????????????????????+ " > " + elementCount);
????????}
????????ensureCapacityHelper(elementCount + 1);
????????System.arraycopy(elementData, index, elementData, index + 1, elementCount - index);
????????elementData[index] = obj;
????????elementCount++;
????}
12.Array 和 ArrayList 有何區(qū)別?
Array可以包含基本類型和對象類型忘晤,ArrayList只能包含對象類型
Array大小固定宛蚓,ArrayList的大小是動態(tài)變化的。
ArrayList提供了更多的方法和特性:比如 :addAll(),removeAll(),iterator()等等设塔。
對于基本數(shù)據(jù)類型凄吏,集合使用自動裝箱來減少編碼工作量。但是闰蛔,當(dāng)處理固定大小基本數(shù)據(jù)類型的時候痕钢,這種方式相對較慢。
13.在Queue中poll()和remove()有什么區(qū)別序六?
poll()和 remove() 都是從隊列中取出一個元素任连,但是 poll() 在獲取元素失敗的時候會返回空,但是 remove() 失敗的時候會拋出異常例诀。
14.哪些集合類是線程安全的随抠?
Vector:就比ArrayList多了一個同步化機(jī)制(線程安全)
LinkedList因為成員方法大多是synchronized的,因此LinkedList是線程安全的余佃。而ArrayList不是線程安全的暮刃。在擴(kuò)容機(jī)制上,當(dāng)Vector的元素數(shù)量超過它的初始化大小的時候會將容量翻倍爆土,而ArrayList只會增長50%椭懊。
ArrayList的數(shù)據(jù)結(jié)構(gòu)是基于數(shù)組的(Object[]),而LinkList內(nèi)部結(jié)構(gòu)是基于一組鏈接的記錄,形式上屬于鏈表的步势。所以在增加元素方面linkList的效率更高氧猬,因為在ArrayList中增加元素,會牽扯一次重新排序坏瘩。刪除也是類似盅抚,所以ArrayList的查詢性能要好些。反之LinkList增加倔矾,刪除性能更好妄均。如果是迭代讀取的話柱锹,就沒有什么差別了。
HashTable:比hashMap多了一個線程安全丰包。hashTable的方法都提供了同步機(jī)制禁熏。hashTable不允許插入空值,hashMap是允許的邑彪。
ConcurrentHashMap:是一種高效但是也線程安全的集合瞧毙。它比Hashmap來講,是線程安全的寄症,但是效率也比較高宙彪,因為它引入了一個分段鎖的概念,可以理解為把一個大的Map拆分成了N個小的hashTable有巧。根據(jù)key.hashCode()決定把key放到哪個hashtable中释漆。HashMap的數(shù)據(jù)結(jié)構(gòu)是數(shù)據(jù)和鏈表。通過hash算法計算該key所在的數(shù)組下標(biāo)篮迎,根據(jù)equals取比較值灵汪。通俗的說救贖ConcurrenthashMap是對每個數(shù)組進(jìn)行加鎖,當(dāng)通過hash算法得出的結(jié)果相同時才需要去同步數(shù)據(jù)柑潦。
15.迭代器Iterator 是什么享言?
對Collection 進(jìn)行迭代的類,稱其為迭代器渗鬼。還是面向?qū)ο蟮乃枷肜缆叮瑢I(yè)對象做專業(yè)的事情,迭代器就是專門取出集合元素的對象譬胎。但是該對象比較特殊差牛,不能直接創(chuàng)建對象(通過new),該對象是以內(nèi)部類的形式存在于每個集合類的內(nèi)部堰乔。
16.Iterator 怎么使用偏化?有什么特點(diǎn)?
Collection接口中定義了獲取集合類迭代器的方法(iterator())镐侯,所以所有的Collection體系集合都可以獲取自身的迭代器侦讨。
17.Iterator 和 ListIterator 有什么區(qū)別?
1. ListIterator有add()方法苟翻,可以向List中添加對象韵卤,而Iterator不能
2. ListIterator和Iterator都有hasNext()和next()方法,可以實現(xiàn)順序向后遍歷崇猫,但是ListIterator有hasPrevious()和previous()方法沈条,可以實現(xiàn)逆向(順序向前)遍歷。Iterator就不可以诅炉。
3. ListIterator可以定位當(dāng)前的索引位置蜡歹,nextIndex()和previousIndex()可以實現(xiàn)屋厘。Iterator沒有此功能。
4. 都可實現(xiàn)刪除對象月而,但是ListIterator可以實現(xiàn)對象的修改擅这,set()方法可以實現(xiàn)。Iierator僅能遍歷景鼠,不能修改。
18.怎么確保一個集合不能被修改痹扇?
Collections.unmodifiableList(List)
Collections.unmodifiableMap(Map)
Collections.unmodifiableSet(Set)
可以返回一個只讀視圖不能修改
19.List铛漓、Map、Set三個接口鲫构,存取元素時浓恶,各有什么特點(diǎn)?
List?以特定次序來持有元素结笨,可有重復(fù)元素包晰。即,有序可重復(fù)炕吸。
訪問時可以使用for循環(huán)伐憾,foreach循環(huán),iterator迭代器 迭代赫模。
Set?無法擁有重復(fù)元素,內(nèi)部排序树肃。即,無序不可重復(fù)瀑罗。
訪問時可以使用foreach循環(huán)胸嘴,iterator迭代器 迭代。
Map?保存?key-value?值斩祭,一一映射劣像。key值 是無序,不重復(fù)的摧玫。value值可重復(fù)耳奕。
訪問時可以map中key值轉(zhuǎn)為為set存儲,然后迭代這個set诬像,用map.get(key)獲取value
20.對比Hashtable吮铭、HashMap、TreeMap有什么不同颅停?
HashTable HashMap TreeMap都是最常見的一些Map實現(xiàn),是以鍵值對的形式存儲和操作數(shù)據(jù)的容器類型谓晌。
HashTable是早期Java類庫提供的一個哈希表實現(xiàn),本身是同步的,不支持null鍵和值,由于同步導(dǎo)致的性能開銷,所以已經(jīng)很少被推薦使用。
HashMap是應(yīng)用更加廣泛的哈希表實現(xiàn),行為大致上與HashTable一致,主要區(qū)別在于HashMap不是同步的,支持null鍵和值等癞揉。通常情況下,HashMap進(jìn)行put或者get操作,可以達(dá)到常數(shù)時間的性能,所以它是絕大部分利用鍵值對存儲場景的首選,比如,實現(xiàn)一個用戶ID和用戶信息對應(yīng)的運(yùn)行時存儲結(jié)構(gòu)纸肉。
TreeMap則是基于紅黑樹的一種提供順序訪問的Map,和HashMap不同,它的get remove之類操作都是O(long(n)的時間復(fù)雜度,具體順序可以由指定的Comparator來決定或者根據(jù)鍵的自然順序來判斷溺欧。