Java做题的常用技巧

Java做题的常用技巧

输入输出

借助缓冲流加速

  • 库:
    • java.util.Scanner
    • java.io.BufferedInputStream;
    • java.io.BufferedOutputStream;

输入

Scanner sc = new Scanner(
    new BufferedInputStream(System.in)
);

常用函数:

boolean nextBoolean()布尔
int nextInt()int32
long nextLong()int64
double nextDouble()双精浮点
String next()字符串遇空白字符结束
String nextLine()一行字符串
boolean hasNext()读入是否结束

tips: Scanner 没有直接读入单个字符的函数,可以读入字符串后,再用用处理字符串的方法代替。


输出

PrintWriter pw = new PrintWriter(
    new BufferedOutputStream(System.out)
);

pw.printf(formatString, args);    //用法类似C++的printf, 格式化输出
pw.println(objects);

pw.flush();         //刷新后才能显示出来


Object 超类

  Object 类是 java 所有类的祖先,所有类都可以使用 Object 的方法。基于 Object 的拆箱装箱机制是需要掌握的。

常用函数:

String toString()转换成字符串对象
boolean equals(Object obj)
Class<?> getClass()获得对象的类型
Object clone()浅拷贝


大数和高精度

Java不支持自定义的运算符重载,即 + - * / 等。所以在大数和高精度库中只能用相应的函数来代替。

大数 BigInteger

  • 库:java.math.BigInteger

构造函数:

BigInteger(String val[, int radix])字符串构造,还可以设置进制数参数,默认十进制

常用函数:

BigInteger add(BigInteger val)加法
int compareTo(BigInteger val)小于、等于、大于返回对应-1 0 1
BigInteger min(BigInteger val)返回较小值
BigInteger max(BigInteger val)返回较大值
int intValue()转换为int类型
double doubleValue()
long longValue()
BigInteger gcd(BigInteger val)
BigInteger negate()取相反数
BigInteger abs()取绝对值

函数名同理:

addsubtractmultiplydividemodpow
+-*/%乘方
notandorxorshiftLeftshiftRight
进制取反进制与进制或进制异或进制左移进制右移

tips: mod 是取模运算, 而 remainder 是求余运算, 即mod结果始终为正数。


高精度 BigDecimal

  • 库:java.math.BigDecimal

构造函数:
  可以使用String, int, long, double, BigInteger来构造,用double构造的时候需注意精度。

常用函数:

  以上和 BigInterger 的常用函数类似,除了进制运算,并多了舍入方法:

BigDecimal setScale(int newScale, RoundingMode roundingMode)保留位数和舍入模式

参数-舍入模式:

HALF_UPUPDOWN
四舍五入进位去尾

import java.math.*;

public class Main
{
    public static void main(String[] args)
    {
        System.out.println(
            new BigDecimal(Math.PI).setScale(
                3, RoundingMode.HALF_UP
            )
        );

        return ;
    }
}

/* 输出结果:
3.142
*/


字符串

常用函数:

int length()长度
char charAt(int index)索引对应的字符
String substring(int beginIndex, int endIndex)子串, 左闭右开
int compareTo(String anotherString)字典序比较字符串
char[] toCharArray()返回字符数组
String[] split(String regex)分割返回字符串数组
static String valueOf(Object obj)基本类型和 char 数组等转String
int indexOf(String str)返回第一次出现的下标

tips: 数组用 .length 属性获得长度,而字符串长度由 .length() 方法获得。


String 与 int 的转换

public class Main
{
    public static void main(String[] args)
    {
        // int 转 String
        int x = 3;  String str = "";
        str = "" + 3;

        System.out.print(str + " ");

        // String 转 int
        x = 0;  str = "5";
        x = Integer.valueOf(str);
        System.out.print(x + " ");
    }
}

/*输出:
3 5 */

  通用的方法可以用对应类型的包装类的 valueOf 函数来实现类型与字符串之间的转换。


StringBuilder 类

  • 库:java.lang.StringBuilder

StringBuilder可以方便地对字符串修改,不是线程安全的,但是相较 StringBuffer 有速度优势,所以无并发下的情况下使用StringBuilder。

常用函数:

StringBuilder append(Object obj)基本类型、char 数组、String 添加至字符串末尾
StringBuilder reverse()将字符串对象逆序
StringBuilder deleteCharAt(int index)
StringBuilder delete(int start, int end)左闭右开
StringBuilder insert(int offset, Object obj)
StringBuilder replace(int start, int end, String str)
int length()长度
char charAt(int index)索引对应的字符
void setCharAt(int index, char ch)修改索引上的字符
String substring(int beginIndex, int endIndex)子串, 左闭右开

import java.lang.*;

public class Main
{
    public static void main(String[] args)
    {
        StringBuilder str = new StringBuilder("Hello World");

        for( String s: str.toString().split(" "))
            System.out.println(s);    //输出按空格分割后的子串

        System.out.println(str.delete(5, str.length()).reverse());    //输出 Hello 的逆序

        return ;
    }
}

/*输出:
Hello
World
olleH
*/

tips: 对于 String 的一些方法,比如 compareTo,valueOf 等,还是需要 StringBuilder 转 String 才能使用。



Comparator 比较器

  Comparator 是一个外部比较器,不需要改变类内部的比较方式。

  • 库:java.util.Comparator

实现接口: int compare(T o1, T o2)

import java.util.Comparator;

public class Main
{
    public static void main(String[] args)
    {
        System.out.println(        //使用匿名类来实现接口的 compare 函数
            new Comparator<Integer>() {
                @Override
                public int compare(Integer x, Integer y) {
                    return x %10 < y %10? -1: 1;
                }
            }.compare(
                new Integer(25),
                new Integer(17)
            )
        );        // 按个位数大小来比较

        return ;
    }
}

/*输出:
-1
*/

tips: JDK8 以后 lamda 表达式也可以作为 sort 常用的比较定义



Arrays 工具类

  • 库:java.util.Arrays

  Arrays 工具类提供静态函数来实现对数组的操作。

二分查找 binarySearch

  • 方法:static <T> int binarySearch(T[] a[, int fromIndex, int toIndex], T key, Comparator<? super T> c)

  • 特点:

    • 查找的数组必须是排好序的。
    • 若查得到则返回数组内下标
    • 若查不到则返回从-1开始递减的计数
    • 对于查找到的相同的元素返回不固定,和查询范围有关

import java.util.Arrays;

public class Main
{
    public static void main(String[] args)
    {
        int[] arr = {1, 1, 3, 3, 10, 19, 31, 52, 68};

        System.out.print(Arrays.binarySearch(arr, 0) + " ");
        System.out.print(Arrays.binarySearch(arr, 1) + " ");    //返回第二个1下标
        System.out.print(Arrays.binarySearch(arr, 3) + " ");    //返回第一个3下标
        System.out.print(Arrays.binarySearch(arr, 20) + " ");
        System.out.print(Arrays.binarySearch(arr, 63) + " ");
        return ;
    }
}

/*输出:
-1 1 2 -7 -9 */

  查找的结果经过条件判断可以达到 C++ 的 lower_bound 和 upper_bound 的效果。


排序 sort

  • 方法:static void sort(T[] a[, int fromIndex, int toIndex], Comparator<? super T> c)
import java.util.Arrays;

public class Main
{
    public static void main(String[] args)
    {
        Integer[] arr = {42, 51, 33};

        Arrays.sort(arr,
            (Integer x, Integer y)->
            x %10 < y %10? -1: 1
        );    //按照个位数大小排序
        // lambda 表达式

        Arrays.asList(arr).forEach(
            (Integer x)-> System.out.print(x + " ")
        );

        return ;
    }
}

/*输出:
51 42 33 */

填充 fill

  • 方法:static void fill(Object[] a[, int fromIndex, int toIndex], Object val)

  作用有点像 C++ 的 memset,但是最好用来填充一维数组


数组和集合之间的转换

数组转换为集合类型

  • 方法:public static <T> List<T> asList(T... a)
import java.util.*;

public class Main
{
    public static void main(String[] args)
    {
        int[] arr = {1, 3, 5, 7};
        Integer[] warr = {1, 3, 5, 7};


        Arrays.asList(arr).forEach((Object o)->{
            System.out.print(o + " ");
        });
        System.out.println();
        // System.out.println(arr);

        Arrays.asList(warr).forEach((Object o)->{
            System.out.print(o + " ");
        });
    }
}

/*输出:
[I@3b6eb2ec 
1 3 5 7 */

tips:

  • 当对基本类型的数组如int[] 转为集合时,是把整个数组当做一个元素放入集合。所以需要先转化为对应的包装类数组Integer[]再转集合
  • 另外,因为asList返回的是Arrays的内部类ArrayList,所以通过asList生成的链表是不能添加和修改的

  以下代码是关于对asList添加删除的操作:

import java.util.*;

public class Main
{
    public static void main(String[] args)
    {
        List<Integer> ls = Arrays.asList(1, 3, 5);

        // ls.add(7);   // 报错

        ls = new ArrayList<>(ls);
        ls.add(7);

        ls.forEach(e-> System.out.print(e + " "));
    }
}

集合转换为数组

import java.util.*;

public class Main
{
    public static void main(String[] args)
    {
        List<Integer> ls = Arrays.asList(1, 3, 5, 7);

        // Integer[] arr = (Integer[]) ls.toArray();    //错误
        Integer[] arr = ls.toArray(new Integer[ls.size()]);

        for( Integer x: arr)
            System.out.print(x + " ");
        System.out.println();
    }
}

/*输出:
1 3 5 7 
*/

tips: 没有参数的toArray方法返回的是Object[]类型,需要遍历逐个转换,不能直接转换为Integer[]类型。



Collection 容器

常用方法:

boolean add(E e)添加元素
boolean addAll(Collection<? extends E> c)全部添加元素
void clear()
boolean contains(Object o)判断集合是否含有元素
boolean remove(Object o)删除首次出现的元素
boolean isEmpty()
boolean containsAll(Collection<?> c)判断包含
Object[] toArray()返回数组对象
int size()
Iterator<E> iterator()迭代器

继承关系

  因为 Collection 是接口,所以以下有关的派生子类实现了以上的常用方法。


ArrayList

  • 库:java.util.ArrayList

  ArrayList 是可变长数组,内部实现方式基于数组

  • ArrayList 不是线程安全的,但是相较 Vector 有速度优势
  • ArrayList 扩容速度为当前长度的 50%,Vector 为 100 %

如果不声明泛型的类型,则基于自动装箱和拆箱的特性, ArrayList 中可以添加不同类型的元素,但不是很安全。


常用函数:

void add(int index, E element)指定位置插入
E get(int index)获得索引元素
E set(int index, E element)修改索引元素
E remove(int index)删除索引处的元素
List<E> subList(int fromIndex, int toIndex)

LinkedList 容器

库:java.util.LinkedList

  LinkedList 是双向链表,同时也继承于 Deque 接口,所以也可以当做双向队列来使用,因为是双向队列,因此既可以当做队列,也可以当做栈。

双向链表 LinkedList

常用方法:

void add(int index, E element)指定索引插入
void addFirst(E e)插入链头
void addLast(E e)
E get(int index)
E getFirst()
E getLast()
E remove(int index)删除索引处元素
E removeFirst()
E removeLast()
E set(int index, E element)修改索引处元素
List<E> subList(int fromIndex, int toIndex)

双向队列接口 Deque

常用方法:

boolean offerFirst(E e)插入队头
boolean offerLast(E e)
E peekFirst()仅查看队头
E peekLast()
E pollFirst()查看并删除队头
E pollLast()

队列接口 Queue

常用方法:

boolean offer(E e)入队
E peek()查看队头
E poll()查看并删除队头

import java.util.*;

public class Main
{
    public static void main(String[] args)
    {
        Queue<Integer> que = new LinkedList<>();
        que.offer(1);
        que.offer(3);
        que.offer(5);

        while(!que.isEmpty())
            System.out.print(que.poll() + " ");

        return ;
    }
}

tips: 因为 Queue 和 Deque 都是接口类型,所以初始化的时候一般 new 它们的实现类 LinkedList


优先队列 PriorityQueue

构造函数:

PriorityQueue()默认初始容量 11, 按自然序比较
PriorityQueue(int initialCapacity, Comparator<? super E> comparator)指定容量和比较器

常用函数:与 Queue 用法相同

import java.util.*;

public class Main
{
    public static void main(String[] args)
    {
        PriorityQueue<Integer> que = new PriorityQueue<>(11,
            new Comparator<Integer>() {
                @Override
                public int compare(Integer x, Integer y) {
                    return x %10 < y %10? -1: 1;
                }
            }
        );

        que.offer(15);
        que.offer(24);
        que.offer(33);

        while(!que.isEmpty())
            System.out.print(que.poll() + " ");

        return ;
    }
}

/*输出:
33 24 15 */

tips:

  • PriorityQueue 在 java 中是最小堆,但 C++ 中是最大堆
  • PriorityQueue 因为是通过堆来实现 Queue 接口,因为多态性可以声明 Queue 类型对象并以 PriorityQueue 来初始化,来达到一种逻辑上的统一

  Stack 继承自 Vector

常用方法:

E peek()查看栈顶
E push(E item)入栈
E pop()出栈

注:目前由于种种问题,Vector并不被推荐使用,所以Stack也不推荐,于是可以用Deque代替Stack。


Set 容器

HashSet

  无序,不重复,无索引,底层基于哈希表。(同 C++ 的 unordered_set )

import java.util.*;
import java.lang.*;

public class Main
{
    static class Point
    {
        int x, y;
        Point(int x, int y) {
            this.x = x;
            this.y = y;
        }

        @Override
        public boolean equals(Object o) {
            if( o instanceof Point) {
                Point tmp = (Point) o;
                return tmp.x == x && tmp.y == y;
            }
            else return false;
        }

        @Override
        public int hashCode() {
            return x * y;
        }
    }    //定义二维坐标类,重写 Object 的 equals 和 hashCode 方法

    public static void main(String[] args)
    {
        HashSet<Point> st = new HashSet<>();

        st.add(new Point(6, 2));
        st.add(new Point(3, 4));
        st.add(new Point(12, 1));    // hash code 均为 12

        System.out.println(st.contains(new Point(1, 12)));    // false
        System.out.println(st.contains(new Point(3, 4)));    // ture

        return ;
    }
}

/*输出:
false
true
*/

tips: 若是自定义的对象需要重写Object 的 equals 和 HashCode 方法。 HashCode 的定义方法决定了哈希表的效率。


TreeSet

  有序,无重复,无索引,底层基于红黑树。(同 C++ 的 set )

构造函数:

TreeSet()默认
TreeSet(Comparator<? super E> comparator)比较器
TreeSet(Collection<? extends E> c)Collection 容器自然序

常用函数:

E first()最小元素
E last()最大元素
E lower(E e)返回小于参数的元素,若无返回null
E floor(E e)返回大于参数的元素,若无返回null
E pollFirst()返回并删除最小元素
E pollLast()返回并删除最大元素
SortedSet<E> subSet(E fromElement, E toElement)获得范围内的子集合

Map 容器

HashMap

  HashMap 相比 HashTable 不是线程安全的,但是效率较高;而且键值中可以出现 null。

常用方法:

V put(K key, V value)加入键值对
void putAll(Map<? extends K,? extends V> m)字典合并
V get(Object key)获得键对应的值
V remove(Object key)删除键
V replace(K key, V value)替换值
boolean containsKey(Object key)
boolean containsValue(Object value)
Set<Map.Entry<K,V>> entrySet()获得键值对集合
Set<K> keySet()键集合
Collection<V> values()值集合

TreeMap

构造函数:

TreeMap()
TreeMap(Comparator<? super K> comparator)

常用方法:

  除了以上 HashMap 提到的常用方法外,因为有序的特点还有:

K firstKey()最小键
K lastKey()
K lowerKey(K key)大于参数的键
K floorKey(K key)小于参数的键
SortedMap<K,V> subMap(K fromKey, K toKey)键范围内的子字典
import java.util.*;
import java.lang.*;

public class Main
{
    public static void main(String[] args)
    {
        Map<Integer, Character> mp = new HashMap<>();

        mp.put(1, 'A');
        mp.put(2, 'B');
        mp.put(3, 'C');

        Iterator<Map.Entry<Integer, Character> > iter =
            mp.entrySet().iterator();    //使用迭代器遍历键值对集合

        while(iter.hasNext())
        {
            Map.Entry<Integer, Character> pr = iter.next();        

            Integer key = pr.getKey();
            Character value = pr.getValue();

            System.out.println(key + " " + value);
        }

        return ;
    }
}

/*输出:
1 A
2 B
3 C
*/

tips: 可以使用迭代器接口的 remove 函数边遍历边删除。每次 remove 需要在 next 函数调用之后才能使用。


Collections 工具类

  • 库:java.util.Collections

  Collections 工具类提供静态函数来提供对 Collection 系列的操作。

常用函数:

static <T> int binarySearch(List<? extends T> list, T key, Comparator<? super T< c)使用比较器二分查找
static <T> void fill(List<? super T> list, T obj)
static <T> T max(Collection<? extends T> coll, Comparator<? super T> comp)根据比较器选出最大值
static <T> T min(Collection<? extends T> coll, Comparator<? super T> comp)根据比较器选出最小值
static <T> boolean replaceAll(List<T> list, T oldVal, T newVal)替换所有相应元素
static void reverse(List<?> list)翻转(列表系列)
static void shuffle(List<?> list)乱排
static <T> void sort(List<T> list, Comparator<? super T> c)根据比较器排序
static <T> Comparator<T> reverseOrder()获得反自然序比较器


Stream 流

  Stream 提供了高效处理计算数据的方式,可以对集合数组等进行处理

特点:

  • 不会存储元素
  • 不会改变源对象,返回新的 Stream
  • 操作是懒惰执行,需要结果时才执行

流的创建

通过数组或集合来创建

  • 数组:使用 Arrays 工具库的 stream 静态方法生成
  • 集合:对象通过 stream 方法生成
import java.util.*;
import java.util.stream.Stream;

public class Main
{
    public static void main(String[] args)
    {
        // Arrays --> Stream
        Integer[] arr = new Integer[]{1, 3, 5};
        Stream<Integer> ss = Arrays.stream(arr);
        ss.forEach((e)-> {
            System.out.print(e + " ");
        });
        System.out.println();

        // collection --> Stream
        List<Integer> ls = Arrays.asList(arr);
        ss = ls.stream();
        ss.forEach((e)-> {
            System.out.print(e + " ");
        });
    }
}

/*输出:
1 3 5 
1 3 5 */

流的使用

常用方法:

long count()返回元素个数
Stream<T> distinct()直接去重所以如果要对集合或数组去重时可以先转化为流对象再去重
Stream<T> filter(Predicate<? super T> predicate)过滤
<R> Stream<R> map (Function<? super T,? extends R> mapper)
Optional<T> reduce(BinaryOperator<T> accumulator)归约Optional 用于检查 null ,使用它的 get 方法直接获取结果
Stream<T> limit(long maxSize)前 maxSize 个元素
Optional<T> max(Comparator<? super T> comparator)相比 Math.max 只能两个数比较
Optional<T> min(Comparator<? super T> comparator)
Stream<T> sorted()
Object[] toArray()
<R,A> R collect(Collector<? super T,A,R> collector)将流转换成其它形式比如转换成列表形式collect(Collections.toList())

流的复用

  我们知道一个流对象只能使用一次,可以使用 Supplier 接口来解决

库:java.util.function.Supplier

每次需要使用流时调用 Supplier 的 get 方法获取流对象

综合示例:

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.function.Supplier;

public class Main
{
    public static void main(String[] args)
    {
        List<Integer> tmpls = Arrays.asList(
           1, 1, 3, 3, 10, 19, 31, 52, 68 
        );
        Collections.shuffle(tmpls);

        // 生成流对象,使用乱序的链表来初始化
        Supplier<Stream<Integer> > ss = ()-> tmpls.stream();
        System.out.println("序列为:");
        ss.get().forEach((e)-> System.out.print(e + " "));

        // 直接去重并打印
        System.out.println("\n去重:");
        ss.get().distinct().forEach((e)-> {
            System.out.print(e + " ");
        });


        // 聚合操作
        // map 映射:取个位数
        System.out.println("\nmap映射:取个位数:");
        ss.get().map((e)-> e %10).forEach(
            (e)->System.out.print(e + " "));

        // filter 过滤:取奇数
        System.out.println("\nfilter过滤:取奇数:");
        ss.get().filter((e)-> (e &1) == 1).forEach(
            (e)->System.out.print(e + " "));

        // reduce 归约:求和
        System.out.println("\nreduce归约:求和:");
        System.out.println(ss.get().reduce((x, y)-> x+y).get());    // 返回Optional对象,用get()方法取值


        // 流的转换
        // stream --> collection
        System.out.println("\n流转换成链表:");
        List<Integer> ls = ss.get().collect(Collectors.toList());
        ls.forEach((e)-> System.out.print(e + " "));

        // stream --> array
        System.out.println("\n流转换成数组:");
        Object[] arr = ss.get().toArray();
        Arrays.asList(arr).forEach((e)-> System.out.print(e + " "));

        return ;
    }
}


Java 各版本的语言特性

Java 各版本的语言特性



-------------本文结束-------------
0%