当前位置:   article > 正文

java的NumberFormat、DecimalFormat、MessageFormat类源码详解_dontcarefieldposition.instance

dontcarefieldposition.instance

java的NumberFormat、DecimalFormat、MessageFormat类源码详解

NumberFormat类的定义

  1. public abstract class NumberFormat extends Format {
  2. protected NumberFormat() {
  3. }
  4. @Override
  5. public StringBuffer format(Object number,
  6. StringBuffer toAppendTo,
  7. FieldPosition pos) {
  8. if (number instanceof Long || number instanceof Integer ||
  9. number instanceof Short || number instanceof Byte ||
  10. number instanceof AtomicInteger || number instanceof AtomicLong ||
  11. (number instanceof BigInteger &&
  12. ((BigInteger)number).bitLength() < 64)) {
  13. return format(((Number)number).longValue(), toAppendTo, pos);
  14. } else if (number instanceof Number) {
  15. return format(((Number)number).doubleValue(), toAppendTo, pos);
  16. } else {
  17. throw new IllegalArgumentException("Cannot format given Object as a Number");
  18. }
  19. }
  20. @Override
  21. public final Object parseObject(String source, ParsePosition pos) {
  22. return parse(source, pos);
  23. }
  24. public final String format(double number) {
  25. // Use fast-path for double result if that works
  26. String result = fastFormat(number);
  27. if (result != null)
  28. return result;
  29. return format(number, new StringBuffer(),
  30. DontCareFieldPosition.INSTANCE).toString();
  31. }
  32. String fastFormat(double number) { return null; }
  33. public final String format(long number) {
  34. return format(number, new StringBuffer(),
  35. DontCareFieldPosition.INSTANCE).toString();
  36. }
  37. public abstract StringBuffer format(double number,
  38. StringBuffer toAppendTo,
  39. FieldPosition pos);
  40. public abstract StringBuffer format(long number,
  41. StringBuffer toAppendTo,
  42. FieldPosition pos);
  43. public abstract Number parse(String source, ParsePosition parsePosition);
  44. public Number parse(String source) throws ParseException {
  45. ParsePosition parsePosition = new ParsePosition(0);
  46. Number result = parse(source, parsePosition);
  47. if (parsePosition.index == 0) {
  48. throw new ParseException("Unparseable number: \"" + source + "\"",
  49. parsePosition.errorIndex);
  50. }
  51. return result;
  52. }
  53. public boolean isParseIntegerOnly() {
  54. return parseIntegerOnly;
  55. }
  56. public void setParseIntegerOnly(boolean value) {
  57. parseIntegerOnly = value;
  58. }
  59. //============== Locale Stuff =====================
  60. public final static NumberFormat getInstance() {
  61. return getInstance(Locale.getDefault(Locale.Category.FORMAT), NUMBERSTYLE);
  62. }
  63. public static NumberFormat getInstance(Locale inLocale) {
  64. return getInstance(inLocale, NUMBERSTYLE);
  65. }
  66. public final static NumberFormat getNumberInstance() {
  67. return getInstance(Locale.getDefault(Locale.Category.FORMAT), NUMBERSTYLE);
  68. }
  69. public static NumberFormat getNumberInstance(Locale inLocale) {
  70. return getInstance(inLocale, NUMBERSTYLE);
  71. }
  72. public final static NumberFormat getIntegerInstance() {
  73. return getInstance(Locale.getDefault(Locale.Category.FORMAT), INTEGERSTYLE);
  74. }
  75. public static NumberFormat getIntegerInstance(Locale inLocale) {
  76. return getInstance(inLocale, INTEGERSTYLE);
  77. }
  78. public final static NumberFormat getCurrencyInstance() {
  79. return getInstance(Locale.getDefault(Locale.Category.FORMAT), CURRENCYSTYLE);
  80. }
  81. public static NumberFormat getCurrencyInstance(Locale inLocale) {
  82. return getInstance(inLocale, CURRENCYSTYLE);
  83. }
  84. public final static NumberFormat getPercentInstance() {
  85. return getInstance(Locale.getDefault(Locale.Category.FORMAT), PERCENTSTYLE);
  86. }
  87. public static NumberFormat getPercentInstance(Locale inLocale) {
  88. return getInstance(inLocale, PERCENTSTYLE);
  89. }
  90. /*public*/ final static NumberFormat getScientificInstance() {
  91. return getInstance(Locale.getDefault(Locale.Category.FORMAT), SCIENTIFICSTYLE);
  92. }
  93. /*public*/ static NumberFormat getScientificInstance(Locale inLocale) {
  94. return getInstance(inLocale, SCIENTIFICSTYLE);
  95. }
  96. public static Locale[] getAvailableLocales() {
  97. LocaleServiceProviderPool pool =
  98. LocaleServiceProviderPool.getPool(NumberFormatProvider.class);
  99. return pool.getAvailableLocales();
  100. }
  101. @Override
  102. public int hashCode() {
  103. return maximumIntegerDigits * 37 + maxFractionDigits;
  104. // just enough fields for a reasonable distribution
  105. }
  106. @Override
  107. public boolean equals(Object obj) {
  108. if (obj == null) {
  109. return false;
  110. }
  111. if (this == obj) {
  112. return true;
  113. }
  114. if (getClass() != obj.getClass()) {
  115. return false;
  116. }
  117. NumberFormat other = (NumberFormat) obj;
  118. return (maximumIntegerDigits == other.maximumIntegerDigits
  119. && minimumIntegerDigits == other.minimumIntegerDigits
  120. && maximumFractionDigits == other.maximumFractionDigits
  121. && minimumFractionDigits == other.minimumFractionDigits
  122. && groupingUsed == other.groupingUsed
  123. && parseIntegerOnly == other.parseIntegerOnly);
  124. }
  125. @Override
  126. public Object clone() {
  127. NumberFormat other = (NumberFormat) super.clone();
  128. return other;
  129. }
  130. }

  1. import java.text.* ;
  2. public class NumberFormatDemo01{
  3. public static void main(String args[]){
  4. NumberFormat nf = null ; // 声明一个NumberFormat对象
  5. nf = NumberFormat.getInstance() ; // 得到默认的数字格式化显示
  6. System.out.println("格式化之后的数字:" + nf.format(10000000)) ;
  7. System.out.println("格式化之后的数字:" + nf.format(1000.345)) ;
  8. }
  9. };

import java.text.NumberFormat;

1。Decimalformat df1 = new Decimalformat("####.000"); 
   
      System.out.println(df1.format(1234.56));

     显示:1234.560

2。NumberFormat nf   =   NumberFormat.getPercentInstance();     
  // nf.setMinimumFractionDigits( 2 );        保留到小数点后几位        显示:47.00%

  System.out.println(nf.format(0.47));

     显示:47%

     (法二)

     DecimalFormat df1 = new DecimalFormat("##.00%");    //##.00%   百分比格式,后面不足2位的用0补齐
     baifenbi= df1.format(fen);  

     显示:47.00%

3。DecimalFormat   df   =   new   DecimalFormat("###,##0.00");

      System.out.println(nf.format(24.7));

     显示:24.70

      System.out.println(nf.format(23123.47));

    显示:123,23.47

补充:0.00、0.01; 0.00%、0.12%这样的数据,如果按照上面的格式可能会造成数据显示成:.00、.01; .00%、.12%,怎么办呢?只要把格式改成:

    DecimalFormat df1 = new DecimalFormat("0.00");    
    DecimalFormat df2 = new DecimalFormat("0.00%");

    df1.formatI(number);df2.formatI(number);

     显示:0.00、0.01; 0.00%、0.12%

  1. public class Test {
  2. public static void main(String[] args) {
  3. Double myNumber=23323.3323232323;
  4. Double test=0.3434;
  5. //getInstance()
  6. //返回当前缺省语言环境的缺省数值格式。
  7. String myString = NumberFormat.getInstance().format(myNumber);
  8. System.out.println(myString);
  9. //getCurrencyInstance()返回当前缺省语言环境的通用格式
  10. myString = NumberFormat.getCurrencyInstance().format(myNumber);
  11. System.out.println(myString);
  12. //getNumberInstance() 返回当前缺省语言环境的通用数值格式。
  13. myString = NumberFormat.getNumberInstance().format(myNumber);
  14. System.out.println(myString);
  15. //getPercentInstance() 返回当前缺省语言环境的百分比格式。
  16. myString = NumberFormat.getPercentInstance().format(test);
  17. System.out.println(myString);
  18. //setMaximumFractionDigits(int) 设置数值的小数部分允许的最大位数。
  19. //setMaximumIntegerDigits(int) 设置数值的整数部分允许的最大位数。
  20. //setMinimumFractionDigits(int) 设置数值的小数部分允许的最小位数。
  21. //setMinimumIntegerDigits(int) 设置数值的整数部分允许的最小位数.
  22. NumberFormat format = NumberFormat.getInstance();
  23. format.setMinimumFractionDigits( 3 );
  24. format.setMaximumFractionDigits(5);
  25. format.setMaximumIntegerDigits( 10 );
  26. format.setMinimumIntegerDigits(0);
  27. System.out.println(format.format(2132323213.23266666666));
  28. }
  29. }

结果为:
23,323.332
¥23,323.33
23,323.332
34%
2,132,323,213.23267

 

MessageFormat

  1. public class MessageFormat extends Format {
  2. private static final long serialVersionUID = 6479157306784022952L;
  3. public MessageFormat(String pattern) {
  4. this.locale = Locale.getDefault(Locale.Category.FORMAT);
  5. applyPattern(pattern);
  6. }
  7. public MessageFormat(String pattern, Locale locale) {
  8. this.locale = locale;
  9. applyPattern(pattern);
  10. }
  11. public void setLocale(Locale locale) {
  12. this.locale = locale;
  13. }
  14. public Locale getLocale() {
  15. return locale;
  16. }
  17. @SuppressWarnings("fallthrough") // fallthrough in switch is expected, suppress it
  18. public void applyPattern(String pattern) {
  19. StringBuilder[] segments = new StringBuilder[4];
  20. // Allocate only segments[SEG_RAW] here. The rest are
  21. // allocated on demand.
  22. segments[SEG_RAW] = new StringBuilder();
  23. int part = SEG_RAW;
  24. int formatNumber = 0;
  25. boolean inQuote = false;
  26. int braceStack = 0;
  27. maxOffset = -1;
  28. for (int i = 0; i < pattern.length(); ++i) {
  29. char ch = pattern.charAt(i);
  30. if (part == SEG_RAW) {
  31. if (ch == '\'') {
  32. if (i + 1 < pattern.length()
  33. && pattern.charAt(i+1) == '\'') {
  34. segments[part].append(ch); // handle doubles
  35. ++i;
  36. } else {
  37. inQuote = !inQuote;
  38. }
  39. } else if (ch == '{' && !inQuote) {
  40. part = SEG_INDEX;
  41. if (segments[SEG_INDEX] == null) {
  42. segments[SEG_INDEX] = new StringBuilder();
  43. }
  44. } else {
  45. segments[part].append(ch);
  46. }
  47. } else {
  48. if (inQuote) { // just copy quotes in parts
  49. segments[part].append(ch);
  50. if (ch == '\'') {
  51. inQuote = false;
  52. }
  53. } else {
  54. switch (ch) {
  55. case ',':
  56. if (part < SEG_MODIFIER) {
  57. if (segments[++part] == null) {
  58. segments[part] = new StringBuilder();
  59. }
  60. } else {
  61. segments[part].append(ch);
  62. }
  63. break;
  64. case '{':
  65. ++braceStack;
  66. segments[part].append(ch);
  67. break;
  68. case '}':
  69. if (braceStack == 0) {
  70. part = SEG_RAW;
  71. makeFormat(i, formatNumber, segments);
  72. formatNumber++;
  73. // throw away other segments
  74. segments[SEG_INDEX] = null;
  75. segments[SEG_TYPE] = null;
  76. segments[SEG_MODIFIER] = null;
  77. } else {
  78. --braceStack;
  79. segments[part].append(ch);
  80. }
  81. break;
  82. case ' ':
  83. // Skip any leading space chars for SEG_TYPE.
  84. if (part != SEG_TYPE || segments[SEG_TYPE].length() > 0) {
  85. segments[part].append(ch);
  86. }
  87. break;
  88. case '\'':
  89. inQuote = true;
  90. // fall through, so we keep quotes in other parts
  91. default:
  92. segments[part].append(ch);
  93. break;
  94. }
  95. }
  96. }
  97. }
  98. if (braceStack == 0 && part != 0) {
  99. maxOffset = -1;
  100. throw new IllegalArgumentException("Unmatched braces in the pattern.");
  101. }
  102. this.pattern = segments[0].toString();
  103. }
  104. public String toPattern() {
  105. // later, make this more extensible
  106. int lastOffset = 0;
  107. StringBuilder result = new StringBuilder();
  108. for (int i = 0; i <= maxOffset; ++i) {
  109. copyAndFixQuotes(pattern, lastOffset, offsets[i], result);
  110. lastOffset = offsets[i];
  111. result.append('{').append(argumentNumbers[i]);
  112. Format fmt = formats[i];
  113. if (fmt == null) {
  114. // do nothing, string format
  115. } else if (fmt instanceof NumberFormat) {
  116. if (fmt.equals(NumberFormat.getInstance(locale))) {
  117. result.append(",number");
  118. } else if (fmt.equals(NumberFormat.getCurrencyInstance(locale))) {
  119. result.append(",number,currency");
  120. } else if (fmt.equals(NumberFormat.getPercentInstance(locale))) {
  121. result.append(",number,percent");
  122. } else if (fmt.equals(NumberFormat.getIntegerInstance(locale))) {
  123. result.append(",number,integer");
  124. } else {
  125. if (fmt instanceof DecimalFormat) {
  126. result.append(",number,").append(((DecimalFormat)fmt).toPattern());
  127. } else if (fmt instanceof ChoiceFormat) {
  128. result.append(",choice,").append(((ChoiceFormat)fmt).toPattern());
  129. } else {
  130. // UNKNOWN
  131. }
  132. }
  133. } else if (fmt instanceof DateFormat) {
  134. int index;
  135. for (index = MODIFIER_DEFAULT; index < DATE_TIME_MODIFIERS.length; index++) {
  136. DateFormat df = DateFormat.getDateInstance(DATE_TIME_MODIFIERS[index],
  137. locale);
  138. if (fmt.equals(df)) {
  139. result.append(",date");
  140. break;
  141. }
  142. df = DateFormat.getTimeInstance(DATE_TIME_MODIFIERS[index],
  143. locale);
  144. if (fmt.equals(df)) {
  145. result.append(",time");
  146. break;
  147. }
  148. }
  149. if (index >= DATE_TIME_MODIFIERS.length) {
  150. if (fmt instanceof SimpleDateFormat) {
  151. result.append(",date,").append(((SimpleDateFormat)fmt).toPattern());
  152. } else {
  153. // UNKNOWN
  154. }
  155. } else if (index != MODIFIER_DEFAULT) {
  156. result.append(',').append(DATE_TIME_MODIFIER_KEYWORDS[index]);
  157. }
  158. } else {
  159. //result.append(", unknown");
  160. }
  161. result.append('}');
  162. }
  163. copyAndFixQuotes(pattern, lastOffset, pattern.length(), result);
  164. return result.toString();
  165. }
  166. public final StringBuffer format(Object[] arguments, StringBuffer result,
  167. FieldPosition pos)
  168. {
  169. return subformat(arguments, result, pos, null);
  170. }
  171. public static String format(String pattern, Object ... arguments) {
  172. MessageFormat temp = new MessageFormat(pattern);
  173. return temp.format(arguments);
  174. }
  175. // Overrides
  176. public final StringBuffer format(Object arguments, StringBuffer result,
  177. FieldPosition pos)
  178. {
  179. return subformat((Object[]) arguments, result, pos, null);
  180. }
  181. public Object[] parse(String source, ParsePosition pos) {
  182. if (source == null) {
  183. Object[] empty = {};
  184. return empty;
  185. }
  186. int maximumArgumentNumber = -1;
  187. for (int i = 0; i <= maxOffset; i++) {
  188. if (argumentNumbers[i] > maximumArgumentNumber) {
  189. maximumArgumentNumber = argumentNumbers[i];
  190. }
  191. }
  192. Object[] resultArray = new Object[maximumArgumentNumber + 1];
  193. int patternOffset = 0;
  194. int sourceOffset = pos.index;
  195. ParsePosition tempStatus = new ParsePosition(0);
  196. for (int i = 0; i <= maxOffset; ++i) {
  197. // match up to format
  198. int len = offsets[i] - patternOffset;
  199. if (len == 0 || pattern.regionMatches(patternOffset,
  200. source, sourceOffset, len)) {
  201. sourceOffset += len;
  202. patternOffset += len;
  203. } else {
  204. pos.errorIndex = sourceOffset;
  205. return null; // leave index as is to signal error
  206. }
  207. // now use format
  208. if (formats[i] == null) { // string format
  209. // if at end, use longest possible match
  210. // otherwise uses first match to intervening string
  211. // does NOT recursively try all possibilities
  212. int tempLength = (i != maxOffset) ? offsets[i+1] : pattern.length();
  213. int next;
  214. if (patternOffset >= tempLength) {
  215. next = source.length();
  216. }else{
  217. next = source.indexOf(pattern.substring(patternOffset, tempLength),
  218. sourceOffset);
  219. }
  220. if (next < 0) {
  221. pos.errorIndex = sourceOffset;
  222. return null; // leave index as is to signal error
  223. } else {
  224. String strValue= source.substring(sourceOffset,next);
  225. if (!strValue.equals("{"+argumentNumbers[i]+"}"))
  226. resultArray[argumentNumbers[i]]
  227. = source.substring(sourceOffset,next);
  228. sourceOffset = next;
  229. }
  230. } else {
  231. tempStatus.index = sourceOffset;
  232. resultArray[argumentNumbers[i]]
  233. = formats[i].parseObject(source,tempStatus);
  234. if (tempStatus.index == sourceOffset) {
  235. pos.errorIndex = sourceOffset;
  236. return null; // leave index as is to signal error
  237. }
  238. sourceOffset = tempStatus.index; // update
  239. }
  240. }
  241. int len = pattern.length() - patternOffset;
  242. if (len == 0 || pattern.regionMatches(patternOffset,
  243. source, sourceOffset, len)) {
  244. pos.index = sourceOffset + len;
  245. } else {
  246. pos.errorIndex = sourceOffset;
  247. return null; // leave index as is to signal error
  248. }
  249. return resultArray;
  250. }
  251. public Object[] parse(String source) throws ParseException {
  252. ParsePosition pos = new ParsePosition(0);
  253. Object[] result = parse(source, pos);
  254. if (pos.index == 0) // unchanged, returned object is null
  255. throw new ParseException("MessageFormat parse error!", pos.errorIndex);
  256. return result;
  257. }
  258. public Object parseObject(String source, ParsePosition pos) {
  259. return parse(source, pos);
  260. }
  261. public Object clone() {
  262. MessageFormat other = (MessageFormat) super.clone();
  263. // clone arrays. Can't do with utility because of bug in Cloneable
  264. other.formats = formats.clone(); // shallow clone
  265. for (int i = 0; i < formats.length; ++i) {
  266. if (formats[i] != null)
  267. other.formats[i] = (Format)formats[i].clone();
  268. }
  269. // for primitives or immutables, shallow clone is enough
  270. other.offsets = offsets.clone();
  271. other.argumentNumbers = argumentNumbers.clone();
  272. return other;
  273. }
  274. public boolean equals(Object obj) {
  275. if (this == obj) // quick check
  276. return true;
  277. if (obj == null || getClass() != obj.getClass())
  278. return false;
  279. MessageFormat other = (MessageFormat) obj;
  280. return (maxOffset == other.maxOffset
  281. && pattern.equals(other.pattern)
  282. && ((locale != null && locale.equals(other.locale))
  283. || (locale == null && other.locale == null))
  284. && Arrays.equals(offsets,other.offsets)
  285. && Arrays.equals(argumentNumbers,other.argumentNumbers)
  286. && Arrays.equals(formats,other.formats));
  287. }
  288. public static class Field extends Format.Field {
  289. // Proclaim serial compatibility with 1.4 FCS
  290. private static final long serialVersionUID = 7899943957617360810L;
  291. protected Field(String name) {
  292. super(name);
  293. }
  294. protected Object readResolve() throws InvalidObjectException {
  295. if (this.getClass() != MessageFormat.Field.class) {
  296. throw new InvalidObjectException("subclass didn't correctly implement readResolve");
  297. }
  298. return ARGUMENT;
  299. }
  300. //
  301. // The constants
  302. //
  303. public final static Field ARGUMENT =
  304. new Field("message argument field");
  305. }
  306. }

为什么要使用静态内部类

  1. class Company {
  2. private String theCEO = "stupid";
  3. private static String companyName = "STUPID COM";
  4. // 1
  5. static class Employee {
  6. public Employee() {
  7. System.out.println("company name - " + companyName);
  8. // 2
  9. //System.out.println("CEO - " + theCEO);
  10. }
  11. }
  12. public Company(){
  13. System.out.println("Company object is created");
  14. }
  15. }
  16. public class Main {
  17. public static void main(String[] args) {
  18. // Company company = new Company();
  19. // Company.Employee employee = company.new Employee();
  20. // 3
  21. Company.Employee employee = new Company.Employee();
  22. }
  23. }

如果内部类不是静态的。要用这个内部类就必须先初始化包裹类。也是,如果不初始化出来对应的包裹类,内部类对象怎么能访问这些包裹类对象的非静态实例对象呢。

  1. static,员工类变成静态内部类。
  2. 静态内部类不能再访问包裹类的非静态成员。
  3. 看起来正常的初始化方法:new Company.Employee()

一般来说,内部类不需要访问包裹类的非静态成员的时候。或者只是做一个工具给外部调用,而这些工具需要按照功能区分为的时候使用静态内部类。

 

FormatElement:
         { ArgumentIndex }
         { ArgumentIndex , FormatType }
         { ArgumentIndex , FormatType , FormatStyle }

FormatType: 
         number
         date
         time
         choice(需要使用ChoiceFormat)
FormatStyle:
         short
         medium
         long
         full
         integer
         currency
         percent
         SubformatPattern(子模式)

{0}、{1,number,short}、{2,number,#.#}属于FormatElement,0,1,2是ArgumentIndex

{1,number,short}里面的number属于FormatType,short则属于FormatStyle

{1,number,#.#}里面的#.#就属于子格式模式

  1. import java.text.MessageFormat;
  2. String str = "{0}{1}{2}{3}{4}{5}{6}{7}{8}{9}{10}{11}{12}{13}{14}{15}{16}";
  3. Object[] array = new Object[]{"A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q"};
  4. String value = MessageFormat.format(str, array);
  5. System.out.println(value); // ABCDEFGHIJKLMNOPQ
  6. String message = "oh, {0} is a person";
  7. Object[] array = new Object[]{"ZhangSan"};
  8. String value = MessageFormat.format(message, array);
  9. System.out.println(value); // oh, ZhangSan is a person
  10. String message = "oh, {0,number,#.#} is a number";
  11. Object[] array = new Object[]{new Double(3.1415)};
  12. String value = MessageFormat.format(message, array);
  13. System.out.println(value); // oh, 3.1 is a number
  14. // MessageFormat的format方法源码
  15. public static String format(String pattern, Object ... arguments)
  16. {
  17. MessageFormat temp = new MessageFormat(pattern);
  18. return temp.format(arguments);
  19. }

对字符串的匹配比较智能

  1. String str = "{0} | {1} | {0} | {1}";
  2. Object[] array = new Object[] { "A", "B" };
  3. String value = MessageFormat.format(str, array);
  4. System.out.println(value); // A | B | A | B
  1. StringBuilder sb=new StringBuilder();
  2. sb.append(" insert into test_tb (");
  3. sb.append(" createTime, ");
  4. sb.append(" datefrom, ");
  5. sb.append(" dateto, ");
  6. sb.append(" name, ");
  7. sb.append(" intranetid, ");
  8. sb.append(" actualhour, ");
  9. sb.append(" planhour, ");
  10. sb.append(" status");
  11. sb.append(" ) values (");
  12. sb.append(" ''{0}'',");
  13. sb.append(" ''{1}'',");
  14. sb.append(" ''{2}'',");
  15. sb.append(" ''{3}'',");
  16. sb.append(" ''{4}'',");
  17. sb.append(" ''{5}'',");
  18. sb.append(" ''{6}'',");
  19. sb.append(" ''{7}''");
  20. sb.append(" )");
  21. String result=sb.toString();
  22. Object[] arr={createTime,datefrom,dateto,name,intranetid,actualhour,planhour,status};
  23. String sql=MessageFormat.format(result, arr);

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/2023面试高手/article/detail/201804
推荐阅读
相关标签
  

闽ICP备14008679号