JAVA的基本学习第二部分——数组、字符串类、集合类

这里我们承接上一部分的内容

来学习一下Java语言的数组,字符串类,集合类

 

 

 


数组♥

Java的数组概念和C++类似,要注意的就是数组是一个对象

一维数组

  • 直接指定初值来创建数组对象:
int [] a1={23,-9,38,8,65};
  • 用关键字new创建数组对象
int a[];
a=new int[9];

一维数组实现冒泡排序

package 测试;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
class SortClass                //类定义开始
{
	void sort(int arr[])          //开始定义冒泡排序法
	{
		int i,k,temp;
		int len=arr.length;
		for(i=0;i<len-1;i++)
		{
			for(k=len-1;k>i;k--)       //从后往前检测(排好的会堆积到前面)
			{
				if(arr[k]<arr[k-1])
				{
					temp=arr[k];
					arr[k]=arr[k-1];
					arr[k-1]=temp;
				}
			}
		}
	}
}
public class test
{
	public static void main(String []args) throws IOException
	{
		BufferedReader keyin=new BufferedReader(new InputStreamReader(System.in));
		int i;
		String C1;
		int arr[]=new int [8];
		int len=arr.length;
		System.out.println("请从键盘输入8个整数,一行只能输入一个数");
		for(i=0;i<len;i++)
		{
			C1=keyin.readLine();         //用于读取一个字符串
			arr[i]=Integer.parseInt(C1);          //将字符串类型c1转换成整数类型			
		}
		//打印原始数据
		System.out.println("原始数据:");
		for(i=0;i<len;i++)
		{
			System.out.print(" "+arr[i]);
		}			
		System.out.println("\n");
		SortClass p1=new SortClass(); 
		p1.sort(arr);
		System.out.println("冒泡法排序的结果:");
		for(i=0;i<len;i++)
		{
			System.out.print(" "+arr[i]);
		}
		System.out.println("\n");
		keyin.close();                   //关闭这个流
	}
}

原始数据:
2 354 5 -7 34 1 96 465

冒泡法排序的结果:
-7 1 2 5 34 96 354 465

补充:print和println的区别:

print:将它的参数显示在命令窗口,并将输出光标定位在所显示的最后一个字符之后。

println:将它的参数显示在命令窗口,并在结尾加上换行符,将输出光标定位在下一行的开始。

二维数组


字符串类♠

String

package 测试;
import java.io.UnsupportedEncodingException;
public class test
{
	public static void main(String []args) throws UnsupportedEncodingException
	{
		//字符数组类型的字符串
		char charArray[]= {'b','i','r','t','h',' ','d','a','y'};
		//字节数组类型的字符串,其中每个字节的值代表汉字的国际机内码
		//汉字的国际机内码(GB 2312码),两个字节的编码构成一个汉字
		//数组构成“面向对象”4个汉字。-61与-26组成的汉字是“面”,以此类推
		byte byteArray[]= {-61,-26,-49,-14,-74,-44,-49,-13};
		StringBuffer buffer;   //声明字符串对象的引用变量
		String s,s1,s2,s3,s4,s5,s6,s7,ss;       //声明字符串对象的引用变量
		s=new String("hello"); //创建一个字符串对象“hello”,s指向该对象
		ss="ABC";            //创建一个字符串对象“ABC”,ss指向该对象
		//用StringBuffer创建一个字符串对象
		buffer=new StringBuffer("欢迎来到Java程序!");
		s1=new String();          //创建一个空字符串对象
		s2=new String(s);        //创建一个新的String对象“hello”,s2指向该对象
		s3=new String(charArray);  //用字符数组创建字符串对象“birth day”,s3指向该对象
		//用字符串数组中下标为6开始的3个字符创建字符串对象“day”
		s4=new String(charArray,6,3);
		//用字符串数组byteArray,按GB2313编码方案创建串对象“面向对象”
		s5=new String(byteArray,"GB2312");
		//从前面创建的字节型数组byteArray下标为2的字节开始,取连续的4个字节,
		//即取{-49,-14,-74,-44},创建字符串对象
		s6=new String(byteArray,2,4,"GB2312");
		//创建一个新的String对象"欢迎来到Java程序!",s7指向该对象
		s7=new String(buffer);
		System.out.println("s1="+s1);
		System.out.println("s2="+s2);
		System.out.println("s3="+s3);
		System.out.println("s4="+s4);
		System.out.println("s5="+s5);
		System.out.println("s6="+s6);
		System.out.println("s7="+s7);
		System.out.println("ss="+ss);
		System.out.println("buffer="+buffer);
	}
}

s1=
s2=hello
s3=birth day
s4=day
s5=面向对象
s6=向对
s7=欢迎来到Java程序!
ss=ABC
buffer=欢迎来到Java程序!

String类常用方法

int length()       返回当前串对象的长度

char charAt(int index)  返回当前字符串下标index处的字符

int indexOf(int ch)            返回指定字符在此字符串中第一次出现的索引

String subString(int beginIndex)      返回当前串中从beginIndex开始到串尾的子串

boolean equals(Object obj)       将此字符串与指定对象比较

String replace(char oldCh,char newCh)       将字符串的字符 oldCh 替换成 newCh

static String valueOf(type variable)          返回变量variable的字符串形式,type是基本类型

String toString()                   返回当前字符串 

StringBuffer类

stringbuffer是字符串缓冲器类,与String类不同,StringBuffer类是一个在操作中可以更改其内容的字符串类,即一旦创建了StringBuffer类的对象,那么在操作中便可以更改和变动字符串的内容。

package 测试;
import java.io.UnsupportedEncodingException;
public class test
{
	public static void main(String []args) throws UnsupportedEncodingException
	{
		StringBuffer buf1=new StringBuffer(); //创建一个buf1指向的初始长度为16的空字符串缓冲区
		StringBuffer buf2=new StringBuffer(10); //创建一个buf2指向的初始长度为10的空字符串缓冲区
		StringBuffer buf3=new StringBuffer("hello"); //用指定的“hello”串创建一个字符串缓冲区
		//返回当前字符串长度
		int len1=buf1.length();
		int len2=buf2.length();
		int len3=buf3.length();
		//返回当前缓冲区长度
		int le1=buf1.capacity();
		int le2=buf2.capacity();
		int le3=buf3.capacity();
		//从buf3字符串中取下标为3的字符
		char ch=buf3.charAt(3);
		//使用StringBuffer的toString方法将三个StringBuffer对象转换成String对象输出
		System.out.println("buf1的内容:"+buf1.toString());
		System.out.println("buf2的内容:"+buf2.toString());
		System.out.println("buf3的内容:"+buf3.toString());
		System.out.println("buf1的字符串长度:"+len1);
		System.out.println("buf2的字符串长度:"+len2);
		System.out.println("buf3的字符串长度:"+len3);
		System.out.println("buf1的缓冲区长度:"+le1);
		System.out.println("buf2的缓冲区长度:"+le2);
		System.out.println("buf3的缓冲区长度:"+le3);
		System.out.println("buf3的第三个字符:"+ch);
	}
}

buf1的内容:
buf2的内容:
buf3的内容:hello
buf1的字符串长度:0
buf2的字符串长度:0
buf3的字符串长度:5
buf1的缓冲区长度:16
buf2的缓冲区长度:10
buf3的缓冲区长度:21
buf3的第三个字符:l

补充

String的两种声明方式

String a=”123″; String b=”123;
a 和 b 都指向常量“123“的地址

String c=new String(“123”); d=new String(“123”);
c 和 d 开辟了各自的内存空间

所以,如果你 if(a==b) ,会返回true,因为是同一个地址;而 if(c==d) ,会返回false,因为是两个不同的地址

(比较值使用equals,直接用双等是比较地址)

 

String,StringBuffer与StringBuilder的区别

转载自这里

一、Java String 类——String字符串常量

字符串广泛应用 在Java 编程中,在 Java 中字符串属于,Java 提供了 String 类来创建操作字符串

需要注意的是,String的值是不可变的(final关键字定义),这就导致每次对String的操作都会生成新的String对象,这样不仅效率低下,而且大量浪费有限的内存空间。我们来看一下这张对String操作时内存变化的图:

我们可以看到,初始String值为“hello”,然后在这个字符串后面加上新的字符串“world”,这个过程是需要重新在栈堆内存中开辟内存空间的,最终得到了“hello world”字符串也相应的需要开辟内存空间,这样短短的两个字符串,却需要开辟三次内存空间,不得不说这是对内存空间的极大浪费。为了应对经常性的字符串相关的操作,谷歌引入了两个新的类——StringBuffer类和StringBuild类来对此种变化字符串进行处理。

二、Java StringBuffer 和 StringBuilder 类——StringBuffer字符串变量、StringBuilder字符串变量

对字符串进行修改的时候,需要使用 StringBuffer 和 StringBuilder 类。

和 String 类不同的是,StringBuffer 和 StringBuilder 类的对象能够被多次的修改,并且不产生新的未使用对象

StringBuilder 类在 Java 5 中被提出,它和 StringBuffer 之间的最大不同在于 StringBuilder 的方法不是线程安全的(不能同步访问)。

由于 StringBuilder 相较于 StringBuffer 有速度优势,所以多数情况下建议使用 StringBuilder 类。然而在应用程序要求线程安全的情况下,则必须使用 StringBuffer 类。

三者的继承结构

三者的区别

(1)字符修改上的区别(主要,见上面分析)

(2)初始化上的区别,String可以空赋值,后者不行,报错

①String

StringBuffer s = null;

StringBuffer s = “abc”;

②StringBuffer

StringBuffer s = null; //结果警告:Null pointer access: The variable result can only be null at this location

StringBuffer s = new StringBuffer();//StringBuffer对象是一个空的对象

StringBuffer s = new StringBuffer(“abc”);//创建带有内容的StringBuffer对象,对象的内容就是字符串”

 

 

 

 

 


集合类♦

Collection接口:

Collection作为集合层次结构的根,它存储元素的方式可以是Set型或List型。JDK不提供Collection接口的任何直接实现,而是通过它的子接口(例如Set和List来实现)

常见基本操作如下:

boolean add(Object obj)         //添加一个元素
boolean addAll(Collection c)      //添加一个集合中的所有元素
boolean remove(Object obj)         //删除一个元素
boolean removeAll(Collection c)         //删除一个集合中的所有元素
void clear()    //清空,删除所有元素
boolean retainAll(Collection c)        //取两个集合中的相同元素
int size()       	//获取集合中的元素个数
boolean isEmpty()     	//判断该集合是否为空
boolean contains(Object o)        //判断是否包含指定元素
boolean containsAll(Collection c)          //判断是否包含参数中指定集合的所有元素
Object[]  toArray()                //返回包含此Collection中所有元素的数组
/*返回包含此Collection中所有元素的数组;返回数组的运行时类型与指定数组的运行时类型
相同。注意,不能直接把集合转换成基本数据类型的数组(例如int,char),因为集合必须持
有对象。其中<T>中的T表示泛型类型的变量,它的值可以是传递过来的任何类型(例如Integer
、Character、自己定义的对象等),但它不能是任何基本数据类型(例如int、char)*/
<T>T[]      toArray(T[] a)         
terator iterator()               //遍历Collection中的每一个元素

Iterator迭代器:

for-each 结构

for-each 结构的格式:

for(Object o:collection)System.out.println(o);          //表示每一行输出一个元素

Iterator(迭代器)

Collection接口扩展了Iterator接口。Collection接口中的iteration()方法返回一个Iterator对象,通过这个对象可以逐一访问Collection集合中的每一个元素。典型的用法如下:

Iterator it=collection.iterator();           //创建一个迭代器对象
while(it.hasNext())                    //其中的hasNext()用于判断迭代器中是否存在下一个元素
{
        Object obj=it.next();          //取下一个元素
}

代码示例:

package 测试;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
public class test 
{
	public static void main(String []args)
	{
		Character[] c= {'A','B','C','D','E'};     //创建元素为字符对象的c数组
		System.out.print("输出元素为字符对象的c数组");
		for(int i=0;i<c.length;i++)
		{
			System.out.print(c[i]+",");
		}
		System.out.println();                 //换个行
		//创建ArrayList,并转型为Collection
		//Arrays.asList(c)表示c数组的元素是ArrayList元素
		//这里是泛型构造
		Collection<Character> c1=new ArrayList<Character> (Arrays.asList(c));
		c1.add('f');                //添加一个元素  
		System.out.println("输出集合中添加f后的元素"+c1);
		c1.remove('B');              //删除一个元素
		System.out.println("利用迭代器输出集合删除B后的所有元素");
		Iterator<Character> it=c1.iterator();     //创建一个迭代器对象
		while(it.hasNext())              //判断是否存在下一个元素
		{ 
			Character i=it.next();     //取下一个元素
			System.out.print(i+",");
		}
		System.out.println();
		Object[] cc=c1.toArray();      
		System.out.print("输出集合转数组后数组的所有元素:");
		for(Object ac:cc)         //foreach结构的循环
			System.out.print(ac+",");
		System.out.println();
	}
}

输出元素为字符对象的c数组A,B,C,D,E,
输出集合中添加f后的元素[A, B, C, D, E, f]
利用迭代器输出集合删除B后的所有元素
A,C,D,E,f,
输出集合转数组后数组的所有元素:A,C,D,E,f,

List接口与实现类:

List接口继承了Collection接口。

实现List的通用类是ArrayList类和LinkedList类(这两个类没有同步的方法,如果多个线程同时访问一个List,则必须自己实现访问的同步)。

  • ArrayList:采用数组来存储线性表的元素,是线性表的顺序存储结构。它与JAVA数组的主要区别是:数组作为容器的大小是不可变的,而ArrayList作为容器大小是可变的。

List接口中增加的特有操作

void add(int index,E elemtype)        //插入一个元素到指定位置
boolean addAll(int index,Collection<? extends E> c)    //插入集合中所有元素到指定位置
//上面中, <? extends E>表示未知类型是E的子类型或者E类型
E remove(int index)              //删除列表中指定位置的元素
E get(int index)                  //返回列表中指定位置的元素
int indexOf(Object o)            //获取列表中第一次出现的指定元素的下标,不存在则返回-1
int lastIndexOf(Object o)      //获取列表中最后出现的指定元素的下标,不存在则返回-1
E set(int index,E elemtype)        //用指定元素替换列表中指定位置的元素
ListIterator<E> listIterator()         //列表迭代器
ListIterator<E> listIterator(int index)         //从列表指定位置开始迭代
  • LinkedList采用链接存储结构来存储线性表中的元素。由链接存储结构的特点可知,它适用于元素的插入、删除操作。

LinkedList特有的操作方法

void addFirst(E e)      //将指定元素插入此列表的开头
void addLast(E e)      //将指定元素添加到此列表的结尾
boolean offerFirst(E e)     //在此列表的开头插入指定的元素
boolean offerLast(E e)      //在此列表的结尾插入指定的元素
E removeFirst()          //移除并返回此列表的第一个元素
E removeLast()        //移除并返回此列表的最后一个元素
E pollFirst()            //获取并移除此列表的第一个元素,如果为空,则返回null
E pollLast()            //获取并移除此列表的最后一个元素,如果为空,则返回null
E getFirst()              //返回此列表的第一个元素
E getLast()                //返回此列表的最后一个元素
void push(E e)           //将元素推入此列表所表示的堆栈
E pop()                  //从此列表所表示的堆栈处弹出一个元素
E peek()                   //获取但不移除此列表的头

Set接口:

Set接口简介:

Set是不保存重复元素的Collection。实现Set接口的通用类是HashSet、LinkedHashSet、TreeSet类(使用时注意这三个类没有同步机制)

  • HashSet类。HashSet采用hashCode算法(散列函数)存放函数,元素的存放顺序与插入顺序无关。HashSet是为快速查找而实现的Set。
  • TreeSet类。TreeSet类采用红黑树数据结构对元素排序,是保证元素字母排列顺序的Set。它的查找速度比HashSet慢。
  • LinkedHashSet。LinkedHashSet是HashSet的子类,它的内部使用散列以加快查找速度,同时使用链表维护元素的排序。

代码实现HashSet、LinkedHashSet及TreeSet输出结果的比较

package 测试;
import java.util.*;
public class test 
{
	public static void main(String []args)
	{
		LinkedHashSet<String> s1=new LinkedHashSet<String> ();
		HashSet<String> s2=new HashSet<String> ();
		TreeSet<String> s3=new TreeSet<String> ();
		String[] str= {"B","A","C","D"};
		//创建ArrayList,并转型为Collection 
		//Arrays.asList(c)表示c数组的元素是ArrayList元素
		Collection<String> list=new ArrayList<String> (Arrays.asList(str)); 
		//将list的元素全部添加到三个Set结合中
		s1.addAll(list);
		s2.addAll(list);
		s3.addAll(list);
		System.out.println("s1="+s1);
		System.out.println("s2="+s2);
		System.out.println("s3="+s3);
	}
}

s1=[B, A, C, D]
s2=[A, B, C, D]
s3=[A, B, C, D]

有趣的是,HashSet竟然进行了排序,这里面的道理比较复杂,我找了一个大神的CSDN博客,想深入了解的同学可以看看https://blog.csdn.net/he37176427/article/details/98054668

SortedSet接口:

SortSet接口是Set接口的子接口,是一种按升序维护其元素的Set。它是根据元素的自然顺序进行排序(自然排序)的,或者根据在创建SortedSet时提供的Comparator进行排序(定制排序)的。实现SortedSet接口的类是TreeSet类。SortedSet接口除了继承Set接口的操作外,还提供了如下一些操作:

//返回对此Set中的元素进行排序的比较器,如果此Set使用其元素的自然排序,则返回null。
//其中,<? super E>表示未知类型是E的超类型或E类型
Comparator<? super E> comparator()  
E first()        //返回集合中的第一个元素
E last()          //返回集合中最后一个元素
//返回此Set中元素值小于toElemment的元素子集
SortedSet<E> headset(E toElement)   
//返回此Set的子集合,范围从fromElement(包括)到toElement(不包括)
SortedSet<E> subset(E fromElement,E toElement)   

用SortedSet进行简单操作

package 测试;
import java.util.*;
public class test 
{
	public static void main(String []args)
	{
		//实现SortedSet接口
		SortedSet<String> set1=new TreeSet<String> (); 
		String[] str= {"B","A","C","D"};
		Collection<String> list=new ArrayList<String> (Arrays.asList(str));
		set1.addAll(list);        //将s1元素全部添加到s2中
		System.out.println("set1="+set1);  //直接输出集合
		System.out.println("headSet('C')="+set1.headSet("C"));  //返回小于C的子集
		System.out.println("subSet('B','D')="+set1.subSet("B","D"));//返回在B、D之间的子集
		System.out.println("first="+set1.first()); //返回第一个元素
		System.out.println("last="+set1.last());  //返回最后一个元素
	}
}

set1=[A, B, C, D]
headSet(‘C’)=[A, B]
subSet(‘B’,’D’)=[B, C]
first=A
last=D

Map接口:

Map接口与Collection接口无继承关系。Map作为一个映射集合,每一个元素包含key-value(键-值对)。

Map中的value对象可以重复,但是key不能重复。实现Map接口常用的通用类是HashMap、TreeMap、LinkedHashMap类。这三个类没有同步机制。

Map接口常用操作:

V put(K key,V value)       //将指定的值value与此映射中的指定键key关联
V remove(Object key)      //如果存在一个键的映射关系,则将其从此映射中移除
void putAll(Map<? extends K,? extends V> m)     //从指定映射中将所有的映射关系复制到此映射中
void clear()       //从此映射中移除所有映射关系
V get(Object key)       //返回指定键key所映射的值,如果没有,则返回null
boolean containsKey(Object key)    //判断映射关系中是否存在关键字key
boolean containsValue(Object value) //判断映射中是否存在值value
int size()               //返回此映射中的键-值映射关系数
boolean isEmpty()               //判断此映射是否存在映射关系
Set<K> keySet()    //返回此映射中包含的键的Set视图
Collection<V> values()           //返回此映射中包含的值的Collection视图
Set<Map.Entry<K,V>> entrySet()  返回此映射中包含的映射关系的Set视图

Map.Entry接口常用操作:

Map.Entry是Map内部定义的一个接口,专门用来存储键-值对。常用方法如下:

K getKey()   //返回与此项对应的键
V getValue()   //返回与此项对应的值
V setValue(Object value)      //用指定的值替换与此项对应的值
boolean equals(Object ob)      //比较指定对象ob与此项的相等性
Map.entrySet()         //返回映射的Set视图

Map的添加元素和读取元素操作:

package 测试;
import java.util.*;
public class test 
{
	public static void main(String []args)
	{
		Map<String,String> map=new HashMap<String,String>();
		map.put("书", "Java");
		map.put("学生", "张学友");
		map.put("班级", "333");
		System.out.println("map="+map.toString());    //输出map
		if(map.containsKey("书")) //如果存在键为“书”
		{
			System.out.print("查找key=书,存在     ");
			String val=map.get("书");
			System.out.println("键“书”对应的值是:"+val);
		}
		else
			System.out.println("查找的键“书”,不存在");
		//全部输出key
		Set<String> keys=map.keySet();          //得到全部的key
		Iterator<String> iter1=keys.iterator(); 
		System.out.print("全部的key:");
		while(iter1.hasNext())
		{
			String str=iter1.next();
			System.out.print(str+"、");
		}
		Collection<String> values=map.values();      //得到全部的value
		Iterator<String> iter2=values.iterator(); 
		System.out.print("\n全部的value:");
		while(iter2.hasNext())
		{
			String str=iter2.next();
			System.out.print(str+"、");
		}
		System.out.println();
	}
}

map={学生=张学友, 班级=333, 书=Java}
查找key=书,存在 键“书”对应的值是:Java
全部的key:学生、班级、书、
全部的value:张学友、333、Java、

SortedMap接口:

SortedMap接口是Map接口的子接口,是排序的Map。实现此接口的子类都属于排序的子类。实现该接口的常用的类是TreeMap类。SortedMap除了继承Map接口的操作外,还提供了一些扩展方法:

Comparator<? super K> comparator()       //返回比较器对象
K firstKey()          //返回第一个元素的key
SortedMap<K,V>  headMap(K toKey)   //返回小于等于指定K的部分集合
K lastKey()            //返回最后一个元素的key
SortedMap<K,V> subMap(K fromKey,K toKey)  //返回指定Key范围的集合
SortedMap<K,V> tailMap(K fromKey)      //返回大于指定Key的部分集合

用TreeMap进行简单操作

package 测试;
import java.util.*;
public class test 
{
	public static void main(String []args)
	{
		SortedMap<String,String> smap=new TreeMap<String,String>();
		smap.put("D", "04");
		smap.put("A", "01");
		smap.put("C", "03");
		smap.put("B", "02");
		System.out.println("sortMap="+smap);
		System.out.println("headMap('C')="+smap.headMap("C"));
		System.out.println("subMap('B','D')="+smap.subMap("B","D"));
		System.out.println("first()="+smap.firstKey());
		System.out.println("last()="+smap.lastKey());
	}
}

Collections算法类:

Collections类不同于Collection接口,是一个算法类,进一步提供一系列静态方法

  • 为集合增加元素的addAll()方法
addAll(Collection<? super T> c,T ...elements )

将所有指定元素添加到指定的Collection中,可以接受可变参数

实例:

Collections.addAll(Collection集合,"B","A","E");
Collections.addAll(Collection集合,"C","F");
  • sort()和reverse()方法
sort(List<T> list)   //根据元素的自然顺序按升序排列
reverse(List<?> list)  //反转指定列表中元素的排序
sort(List<T> list,Comparator<? super T> c)  //以比较器为参数进行排序
  • 混排的shuffle()方法(洗牌操作)
void shuffle(List<?> list)  //对指定列表使用默认随机源进行排列
void shuffle(List<?> list,Random md)   //对指定列表使用md指定的随机源进行排列
  • 替换集合元素的replaceAll()方法
replaceAll(List<T> list,T oldVal,T newVal)  //使用newVal替换列表中所有oldVal
  • 二分查找的binarySearch
//二分查找搜索列表,获得key指定的对象
binarySearch(List<? extends Comparable<? super T>> list,T key)
//根据c指定的比较器进行排序后,二分查找元素
binarySearch(List<? extends  T> list,T key,Comparator<? super T> c)

二分查找补充:如果list中不包含搜索键,则返回一个负整数值,这个值是“-insertpoint-1”,其中insertpoint是指搜索键可能会被插入到List的位,如果List中的所有元素都小于搜索键,则insertpoint为list.size()

  • 交换指定元素位置的swap方法
swap(List<?> list,int i,int j)   //在List中交换i和j的位置

 

 

 


这一篇就先写到这里了,消化几天再写后面的内容

 

商业转载 请联系作者获得授权,非商业转载 请标明出处

发表评论