java.util

来源:互联网 发布:网络策划经理招聘 编辑:程序博客网 时间:2024/05/17 03:03

Java.util

 

一描述

包含集合框架、遗留的 collection类、事件模型、日期时间设施、国际化和各种实用工具类(字符串标记生成器、随机数生成器和位数组、日期Date类、堆栈Stack类、向量Vector类等)。

下图给出了java.util包的基本层次结构图

  ┌java.util.BitSet

  │java.util.Calendar

  │ └java.util.GregorianCalendar

  │java.util.Date

  │java.util.Dictionary

  │ └java.util.Hashtable

  │ └java.util.Properties

  │java.util.EventObject

  │java.util.ResourceBundle

  ┌普通类┤ ├java.util.ListResourceBundle

  │ │ └java.util.PropertyResourceBundle

  │ │java.util.Local

  │ │java.util.Observable

  │ │java.util.Random

  │ │java.util.StringTokenizer

  │ │java.util.Vector

  │ │ └java.util.Stack

  Java.util┤ └java.util.TimeZone

  │ └java.util.SimpleTimeZone

  │ ┌java.util.Enumeration

  ├接 口┤java.util.EventListener

  │ └java.util.Observer

  │ ┌java.util.EmptyStackException

  └异常类┤java.util.MissingResourceException

  │java.util.NoSuchElementException

  └java.util.TooManyListenersException

1.1 java.util包层次结构图

 


二集合框架(补充)

2.1 BitSet

2.1.1继承结构与实现接口

public class BitSet extends Object
implements Cloneable, Serializable

 

2.2.2 描述

此类实现了一个按需增长的位向量。位 set 的每个组件都有一个 boolean 值。用非负的整数将 BitSet 的位编入索引。可以对每个编入索引的位进行测试、设置或者清除。通过逻辑与、逻辑或和逻辑异或操作,可以使用一个 BitSet 修改另一个 BitSet 的内容。

默认情况下,set 中所有位的初始值都是 false

每个位 set 都有一个当前大小,也就是该位 set当前所用空间的位数。注意,这个大小与位 set 的实现有关,所以它可能随实现的不同而更改。位 set 的长度与位 set 的逻辑长度有关,并且是与实现无关而定义的。

除非另行说明,否则将 null 参数传递给 BitSet 中的任何方法都将导致 NullPointerException在没有外部同步的情况下,多个线程操作一个 BitSet 是不安全的。


 事件模型

3.1 委托事件模型

用户通过键盘、鼠标等进行操纵的行为,最终都传递给了JVM,那么JVM在接收到这些事件以后该如何处理呢?我们把这种处理事件的方案,称之为事件模型。

Java中采用的是委托事件模型:jdk1.1以后,引入的一种新的事件代理模块,通过它,事件源发出的事件被委托给(注册了的)事件监听器(Listener),并由它负责执行相应的响应方法。比如:病人生病请医生。

基于这种模型,我们使用两种类型的对象来执行事件机制,这两种对象是:
1)事件源对象
2)事件的侦听对象

java语言中委托事件模型的处理步骤如下:
1.建立事件源对象。如各种GUI的组件。
2.为事件源对象选择合适的事件监听器。比如事件源对象如果是按钮,那么我们能想到的发生在按钮身上最多的,应该是单击事件了。这时我就应该选择鼠标单击事件的监听器。
3.为监听器添加适当的处理程序。比如当按钮单击事件发生后,希望完成的代码。
4.为监听器与事件源建立联系。

窗体自身实现事件监听:

包括两个对象,一个是事件源对象,即JFrame窗体,另外还创建了一个监听器对象。事实上在实际开发过程中,我们往往将这两个类写在一起,就是说一个窗体类自己监听自己的事件

3.2 相关接口

Java支持的事件监听器接口非常多,常见的主要有:

ActionListener  行为监听接口
AdjustmentListener 调整监听接口
ItemListener  选项监听接口
TextListener  文本监听接口
ComponentListener 组件监听接口
KeyListener  键盘监听接口
MouseListener  鼠标点击监听接口
MouseMotionListener 鼠标移动监听接口
FocusListener  光标聚焦监听接口
WindowListener  窗体监听接口
ContainerListener 容器监听接口

KeyListener接口与KeyAdapter
KeyListener接口:监听键盘事件。

该接口中定义了如下三个方法:
keyPressed() 键盘按下时触发
keyReleased() 键盘释放时触发
keyTyped() 键盘单击一次时触发

KeyAdpeter适配器:即使我们只想使用上述三个方法中的一种,那么我们也必须在KeyListener接口的实现类中对这三种方法进行重写,这种方式显然增加了很多无效代码,我们可以使用适配器模式解决。

匿名内部类

WindowListener接口与WindowAdapter
WindowListener接口:监听窗体的行为。

 

windowListener接口常用方法:
windowActivated(WindowEvent e)       Window 设置为活动 Window      调用。
windowClosed(WindowEvent e)    因对窗口调用 dispose而将其关闭      时调用。 

windowClosing(WindowEvent e)  用户试图从窗口的系统菜单中关闭窗     口时调用。 

windowDeactivated(WindowEvent e)    Window不再是活动        Window 时调用。 

windowDeiconified(WindowEvent e)     窗口从最小化状态变为正常状      态时调用。

 windowIconified(WindowEvent e)       窗口从正常状态变为最小化状态      时调用。

 windowOpened(WindowEvent e)       窗口首次变为可见时调用。

MouseListener接口与MouseAdapter
MouseListener接口:监听鼠标点击的行为。

MouseListener接口常用方法:
 mouseClicked(MouseEvent e)           鼠标按键在组件上单击(按下并释放)时调用。
 mouseEntered(MouseEvent e)           鼠标进入到组件上时调用。
 mouseExited(MouseEvent e)           鼠标离开组件时调用。 
mousePressed(MouseEvent e)           鼠标按键在组件上按下时调用。 
mouseReleased(MouseEvent e)           鼠标按钮在组件上释放时调用。


MouseMotionListener接口与MouseMotionAdapter
MouseMotionListener接口:监听鼠标移动的行为。

MouseMotionListener接口常用方法:
mouseDragged(MouseEvent e)           鼠标按键在组件上按下并拖动时调用。 

mouseMoved(MouseEvent e)           鼠标按键在组件上移动(无按键按下)时调用

3.3 相关接口

Java图形界面及事件模型实例:

例3.1

publicclass GUIexampleextends Frame {

    privatestaticfinallongserialVersionUID = 1L;

    private Buttonb1=new Button("登录");

    private Buttonb2=new Button("重置");

    private Buttondb1=new Button("确定");

    private Dialogd=new Dialog(this,"登录结果",false);

                  //创建对话框,父窗体为当前窗口,标题为"登录结果",模式为非模式

    private Label l3=new Label();

    TextField txt1,txt2;

    public GUIexample(String ss){

        super(ss);

        d.setLayout(new FlowLayout()); //设置对话框的布局

        d.add(l3);  //对话框中加入静态文本组件

        d.add(db1); //对话框中加入按钮

        d.setSize(200,100); //设置对话框的尺寸

        setLayout(new FlowLayout());//设置父窗体的布局

        Label l1=new Label("用户名:");

       add(l1);

       txt1=new TextField(5);

       add(txt1);

       Label l2=new Label("密码 :");

       add(l2);

       txt2=new TextField(8);

       txt2.setEchoChar('*');

       add(txt2);

       //父窗体加入两个静态文本,两个文本域

       add(b1);

       add(b2);

    }//父窗体加入两个按钮

     publicstaticvoid main(String args[]) {

        GUIexample nowFrame=new GUIexample("example menu");

        nowFrame.addWindowListener(new WindowAdapter(){

       publicvoid windowClosing(WindowEvent e){

             System.exit(0);

       }

     });

        nowFrame.pack();

        nowFrame.show();

       }

}

 

 

四日期和时间设施

Java日期和时间处理类包括java.util.Calendarjava.util.Date

4.1 Calendar

4.1.1 继承结构

public abstract class Calendar

extendsObject

implementsSerializable,Cloneable,Comparable<Calendar>

4.1.2 描述

Calendar 类是一个抽象类,它为特定瞬间与一组诸如 YEARMONTHDAY_OF_MONTHHOUR日历字段之间的转换提供了一些方法,并为操作日历字段(例如获得下星期的日期)提供了一些方法。瞬间可用毫秒值来表示,它是距历元(即格林威治标准时间 1970 1 1日的 00:00:00.000,格里高利历)的偏移量。

该类还为实现包范围外的具体日历系统提供了其他字段和方法。这些字段和方法被定义为 protected

与其他语言环境敏感类一样,Calendar提供了一个类方法 getInstance,以获得此类型的一个通用的对象。Calendar getInstance 方法返回一个 Calendar对象,其日历字段已由当前日期和时间初始化:

 

4.2 Date

4.2.1 描述
public class Date
extendsObject
implementsSerializable, Cloneable, Comparable<Date>

 

Date 表示特定的瞬间,精确到毫秒。

Date 类打算反映协调世界时 (UTC),但无法做到如此准确,这取决于 Java虚拟机的主机环境。当前几乎所有操作系统都假定 1  = 24×60×60= 86400秒。但对于 UTC,大约每一两年出现一次额外的一秒,称为“闰秒”。闰秒始终作为当天的最后一秒增加,并且始终在 12 31 日或 6 30 日增加。例如,1995年的最后一分钟是 61 秒,因为增加了闰秒。大多数计算机时钟不是特别的准确,因此不能反映闰秒的差别。

例4.1 几种获取时间的方法

首先设置时区为所在时区,否则默认为格林尼治时间。通过CalendarDateGregorianCalendar提供的方法对系统时间获取和设置。

//设置时区,默认是格林尼治时间

       TimeZone tz = TimeZone.getTimeZone("ETC/GMT-8");

       TimeZone.setDefault(tz);

      

       Calendar cal=Calendar.getInstance();  //

       SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

      

       System.out.println("今天是:"+sdf.format(cal.getTime()));

       cal.add(Calendar.MONTH,-1);

       System.out.println("昨天是:"+sdf.format(cal.getTime()));

      

       Date date=new Date();

       System.out.println("默认构造函数:"+date);

       date=new Date(System.currentTimeMillis());

       System.out.println("new Date(long date)"+date);

      

       //获取当前年、月、日

       int year=cal.get(Calendar.YEAR);

       int month=cal.get(Calendar.MONTH);

       int day=cal.get(Calendar.DATE);

       System.out.println("--日:"+year+"-"+month+"-"+day);

      

       cal.set(year, month, day);

       date=cal.getTime();

       System.out.println("Calendar date:"+date);

       /*

        * GregorianCalendarCalendar的一个具体子类,提供了世界上大多数国家/地区使用的标准日历系统。

        */

       GregorianCalendar gcal=new GregorianCalendar();;

       date=gcal.getTime();

       System.out.println("GregorianCalendar date:"+date);

      

       DateFormat df=new SimpleDateFormat("mm-dd-yyyy hh:mm:ss");

       date=df.parse("04-26-2012 00:00:00");

       System.out.println("SimpleDateFormat parsed date:"+date);

 

4.3 java.util.date和java.sql.date之间的转化与区别

java.sql.Date, java.sql.Timejava.sql.Timestamp三个都是java.util.Date的子类。

(1) java.util.date转换成java.sql.date

    java.util.Date utildate=new java.util.Date();

    java.sql.Date sqldate=new java.sql.Date(utildate.getTime());

java.sql.Date类型的值插入到数据库中Date字段中会发生数据截取,是为了配合SQL DATE而设置的数据类型。“规范化”的java.sql.Date只包含年月日信息,时分秒毫秒都会清零。格式类似:YYYY-MM-DD。当我们调用ResultSetgetDate()方法来获得返回值时,java程序会参照"规范"java.sql.Date来格式化数据库中的数值。因此,如果数据库中存在的非规范化部分的信息将会被截取。

同理。如果我们把一个java.sql.Date值通过PrepareStatementsetDate方法存入数据库时,java程序会对传入的java.sql.Date规范化,非规范化的部分将会被劫取。然而,我们java.sql.Date一般由java.util.Date转换过来,如:java.sql.Date sqlDate=new java.sql.Date(new java.util.Date().getTime()).

显然,这样转换过来的java.sql.Date往往不是一个规范的java.sql.Date,要保存java.util.Date的精确值,需要利用java.sql.Timestamp

例4.2:

java.util.Date utildate=new java.util.Date();

       java.sql.Date sqldate=new java.sql.Date(utildate.getTime());

java.sql.Timestamp ts=

new java.sql.Timestamp(utildate.getTime());

       System.out.println("sqldate="+sqldate);

    System.out.println("ts="+ts);

 

输出结果ts=2012-04-26 05:54:39.531

sqldate=2012-04-26

 

4.3

JDBC操作数据库:插入一条记录。其中JDBCDatabaseUtil是自己编写的JDBC工具类。

 

JDBCDatabaseUtil jdu=new JDBCDatabaseUtil();

       //获取数据库连接

       Connection con=jdu.getCon();

       String sql="insert into User_test values(?,?,?,?,?)";

        PreparedStatement pstmt=jdu.getPreparedStatement(sql);

java.sql.Timestamp ts=

new java.sql.Timestamp(new java.util.Date().getTime());

       try {

              pstmt.setInt(1, 1);

              pstmt.setString(2,"user");

              pstmt.setString(3,"123");

              pstmt.setString(4,"test126.com");

              pstmt.setObject(5, ts);

              pstmt.execute();

           }

       catch (SQLException e) {

           e.printStackTrace();

       }

结果查询:

 

国际化和各种实用工具类

5.1随机数

此类的实例用于生成伪随机数流。此类使用 48位的种子,使用线性同余公式对其进行修改。

如果用相同的种子创建两个 Random实例,则对每个实例进行相同的方法调用序列,它们将生成并返回相同的数字序列。为了保证属性的实现,为类 Random指定了特定的算法。为了 Java代码的完全可移植性,Java实现必须让类 Random使用此处所示的所有算法。但是允许 Random类的子类使用其他算法,只要其符合所有方法的常规协定即可。

Random 类所实现的算法使用一个 protected实用工具方法,每次调用它都可提供最多 32个伪随机生成的位。

相比之下,很多场合中java.lang.Math类中的 random方法更易于使用。该方法返回一个0.01.0之间的double型随机数,我们可以通过乘以一个常数扩大表示范围,再加上类型转化得到相应的随机数。

 

Random r1=new Random();

       double rannum1=r1.nextDouble();

       int ranInt=r1.nextInt();

       int ranSeedInt=r1.nextInt(20);

       System.out.println("rannum1="+rannum1+" ranInt="+ranInt

+" ranSeedInt="+ranSeedInt);

       double mathnum=Math.random();

       int num=(int) (Math.random()*20);

    System.out.println("mathnum="+mathnum+" num="+num);

 

输出结果:

rannum1=0.5255470618715067 ranInt=2018284433 ranSeedInt=18

mathnum=0.5492421661749425 num=7

 

5.2 正则表达式

java.util.regex

这个包中包括两个类:Pattern(模式类)和Matcher(匹配器类)。Pattern类用来表达和陈述所要搜索模式的对象,Matcher类是真正影响搜索的对象此外还有一个异常类。PatternSyntaxException,当遇到不合法的搜索模式时会抛出异常。

 

java.util.regex包的类和接口:

类分层结构:

接口分层结构:

使用方法:

5.2.1 Pattern

1.       Public static Pattern compile(String regex)

Pattern类中的compile方法将给定的正则表达式编译到模式中,参数regex是要编译的表达式。如果表达式的语法无效,则抛出PatternSyntaxException异常。

2         public Matcher matcher(CharSequence input)
创建匹配给定输入与此模式的匹配器。参数input:是要匹配的字符序列,返回值为此模式的新匹配器。
3         public static Boolean matches(String regex, CharSequence input)
编译给定正则表达式并尝试将给定输入与其匹配。

调用此便捷方法的形式: Pattern.matches(regex, input);

与表达式

Pattern.compile(regex).matcher(input).matches()

的行为完全相同。

如果要多次使用一种模式,编译一次后重用此模式比每次都调用此方法效率更高。

参数:

regex - 要编译的表达式。

input - 要匹配的字符序列。

抛出:

PatternSyntaxException -如果表达式的语法无效。

 

在使用Pattern.compile函数时,可以加入控制正则表达式的匹配行为的参数:

Pattern Pattern.compile(String regex, int flag)

flag的取值范围如下:

1Pattern.CANON_EQ    当且仅当两个字符的"正规分解(canonical decomposition)"都完全相同的情况下,才认定匹配。比如用了这个标志之后,表达式"a\?"会匹配"?"。默认情况下,不考虑"规范相等性(canonical equivalence)"

2Pattern.CASE_INSENSITIVE(?i)    默认情况下,大小写不明感的匹配只适用于US-ASCII字符集。这个标志能让表达式忽略大小写进行匹配。要想对Unicode字符进行大小不明感的匹配,只要将UNICODE_CASE与这个标志合起来就行了。

3Pattern.COMMENTS(?x)    在这种模式下,匹配时会忽略(正则表达式里的)空格字符(译者注:不是指表达式里的"\\s",而是指表达式里的空格,tab,回车之类)。注释从#开始,一直到这行结束。可以通过嵌入式的标志来启用Unix行模式。

4Pattern.DOTALL(?s)    在这种模式下,表达式'.'可以匹配任意字符,包括表示一行的结束符。默认情况下,表达式'.'不匹配行的结束符。
5Pattern.MULTILINE (?m)    在这种模式下,'^''$'分别匹配一行的开始和结束。此外,'^'仍然匹配字符串的开始,'$'也匹配字符串的结束。默认情况下,这两个表达式仅仅匹配字符串的开始和结束。
6Pattern.UNICODE_CASE (?u)    在这个模式下,如果你还启用了CASE_INSENSITIVE标志,那么它会对Unicode字符进行大小写不明感的匹配。默认情况下,大小写不敏感的匹配只适用于US-ASCII字符集。
7Pattern.UNIX_LINES(?d)    在这个模式下,只有'\n'才被认作一行的中止,并且与'.''^',以及'$'进行匹配。

 

2Matcher

通过解释 Pattern字符序列执行匹配操作的引擎。

通过调用模式的 matcher 方法从模式创建匹配器。创建匹配器后,可以使用它执行三种不同的匹配操作:

matches方法:尝试将整个输入序列与该模式匹配。

lookingAt:尝试将输入序列从头开始与该模式匹配。

find方法:扫描输入序列以查找与该模式匹配的下一个子序列。

每个方法都返回一个表示成功或失败的布尔值。通过查询匹配器的状态可以获取关于成功匹配的更多信息。

例5.2.1

匹配结果:

b1=false

b2=true

b3=true

 
5.3 java.util.zip
描述:
提供用于读写标准 ZIP  GZIP 文件格式的类。还包括使用 DEFLATE 压缩算法(用于 ZIP  GZIP 文件格式)对数据进行压缩和解压缩的类。此外,还存在用于计算任意输入流的 CRC-32  Adler-32 校验和的实用工具类。
 
类分层结构:
接口:

ZIP是一种很常见的压缩形式,在java中要实现ZIP的压缩主要用到的是java.util.zip这个包里面的类。

其中,ZipInputStreamZipEntryZipOutputStream这三个类是用来压缩文件的。Zip压缩文件的结构:一个zip文件由多个entry组成,每个entry有一个唯一的名称,entry的数据项存储压缩数据。

1)·类zipentry

  public zipentry(string name);

  name为指定的数据项名。

2)类zipoutputstream

zipoutputstream实现了zip压缩文件的写输出流,支持压缩和非压缩entry。下面是它的几个常用函数:

  public zipoutputstream(outputstream out);

  ∥利用输出流out构造一个zip输出流。

  public void setmethod(int method);

  ∥设置entry压缩方法,缺省值为deflated

  public void putnextentry(zipentry newe);

∥如果当前的entry存在且处于激活状态时,关闭它,zip文件中写入新的entry-newe,并将数据流定位于entry数据项的起始位置,压缩方法为setmethod指定的方法。

3)类zipinputstream

public zipinputstream(inputstream in); ∥利用输入流in构造一个zip输出流。

public zipentry getnextentry();

∥返回zip文件中的下一个entry,并将输出流定位在此entry数据项的起始位置。

public void closeentry();

∥关闭当前的zip entry,并将数据流定位于下一个entry的起始位置。

 

ZipInputStreamZipFile是用来解压缩文件的,在压缩和解压缩的过程中,ZipEntry都会用到。在javaZip压缩文件中,每一个子文件都是一个ZipEntry对象。


5.4 国际化Java

两个主要类:java.util.ResourceBundle和java.util.Locale。

5.4.1 java.util.ResourceBundle

这个类提供软件国际化的捷径。通过使用这个类,可以:

(1)轻松地本地化或翻译成不同的语言

(2)一次处理多个语言环境

(3)易于修改,支持更多的语言环境

 

简单地说,这个类的作用就是读取资源属性文件(properties),然后根据.properties文件的名称信息(本地化信息),匹配当前系统的国别语言信息(也可以程序指定),然后获取相应的properties文件的内容。

 

使用这个类时需要注意的是,这个properties文件的名字是有规范的:一般的命名规范是:自定义名_语言代码_国别代码.properties

如果是默认的,直接写为:自定义名.properties

比如:

myres_en_US.properties myres_zh_CN.properties

myres.properties

 

当在中文操作系统下,如果myres_zh_CN.properties、myres.properties两个文件都存在,则优先会使用myres_zh_CN.properties,当myres_zh_CN.properties不存在时候,会使用默认的myres.properties。

 

没有提供语言和地区的资源文件是系统默认的资源文件。

资源文件都必须是ISO-8859-1编码,因此,对于所有非西方语系的处理,都必须先将之转换为Java Unicode Escape格式。转换方法是通过JDK自带的工具native2ascii.

 

5.4.2 实例

定义三个资源文件,放到src的根目录下面或者配置的calsspath下面。

  例1:

(1)myres.properties

aaa=good bbb=thanks

(2)myres_en_US.properties

aaa=good bbb=thanks

(3)myres_zh_CN.properties

aaa=\u597d bbb=\u591a\u8c22

 

package util.i18n;

import java.util.Locale;

import java.util.ResourceBundle;

 

publicclass TestResourceBundle {

    publicstaticvoid main(String[] args) {

        Locale locale1 = new Locale("zh","CN");

        ResourceBundle resb1 = ResourceBundle.getBundle("myres", locale1);

        System.out.println(resb1.getString("aaa"));

 

        ResourceBundle resb2 = ResourceBundle.getBundle("myres", Locale.getDefault());

        System.out.println(resb1.getString("aaa"));

 

        Locale locale3 = new Locale("en","US");

        ResourceBundle resb3 = ResourceBundle.getBundle("myres", locale3);

        System.out.println(resb3.getString("aaa"));

    }

} 

运行结果:

good

 

如果使用默认的Locale,那么在英文操作系统上,会选择myres_en_US.properties或myres.properties资源文件。

若要在properties文件中加参数{0} {1} {2} ....,可使用类:java.text.MessageForamt

  2

public static void main(String[] args) {    Locale defaultLocale = Locale.getDefault();    defaultLocale = new Locale("en", "US");    defaultLocale = new Locale("zh", "CN");    ResourceBundle rb = ResourceBundle.getBundle("MessageBundle" , defaultLocale);    String k1 = rb.getString("k1");    System.out.println(k1);    MessageFormat mf = new MessageFormat(k1);    String result = mf.format(new Object[]{"Tom"});    System.out.println(result); }

5.4.3 Locale

Locale 对象表示了特定的地理、政治和文化地区。需要 Locale 来执行其任务的操作称为语言环境敏感的 操作,它使用 Locale 为用户量身定制信息。例如,显示一个数值就是语言环境敏感的操作,应该根据用户的国家、地区或文化的风俗/传统来格式化该数值。

 

使用此类中的构造方法来创建 Locale:

 Locale(String language)  Locale(String language, String country)  Locale(String language, String country, String variant)

 

创建完 Locale 后,就可以查询有关其自身的信息。使用 getCountry 可获取 ISO 国家代码,使用 getLanguage 则获取 ISO 语言代码。可用使用 getDisplayCountry 来获取适合向用户显示的国家名。同样,可用使用 getDisplayLanguage 来获取适合向用户显示的语言名。有趣的是,getDisplayXXX 方法本身是语言环境敏感的,它有两个版本:一个使用默认的语言环境作为参数,另一个则使用指定的语言环境作为参数。

语言参数是一个有效的 ISO语言代码。这些代码是由 ISO-639定义的小写两字母代码。在许多网站上都可以找到这些代码的完整列表,如: http://www.loc.gov/standards/iso639-2/englangn.html    国家参数是一个有效的 ISO 国家代码。这些代码是由 ISO-3166 定义的大写两字母代码。在许多网站上都可以找到这些代码的完整列表,如: http://www.iso.ch/iso/en/prods-services/iso3166ma/02iso-3166-code-lists/list-en1.html 

 
原创粉丝点击