第三话--初探容器

写在前面

想看的东西太多,时间总是太少~ 好了,先甩个锅给时间不够,然后开始Java重造,抱歉的是可能有些东西我不会讲的太详细。对了,最近看了同学iamxiarui的文章。

容器

在日常撸code的过程中,很多时候我们都需要在运行时去创建新的对象。在此之前,我们可能不知道所需对象的数量甚至连类型都不知道,所以我们需要一个能在运行时保存对象引用的玩意。事实上数组是保存一组对象的最有效的方式,但是很多时候我们也不知道我们需要保存的对象的数量是多少,所以数组长度固定这一限制会让我们在实际应用中受到非常多的限制。不过好在Java提供了容器来帮我们搞定这些问题。首先上一个经典的容器图谱来理解一下~

经典.jpg

当然了关于数组有大小限制这个问题,也是可以解决的。我们先申请一个固定长度的数组,每当有元素向里面添加的时候就判断一下,当前数组是否已经满了,如果不满则直接添加进去,如果满了就按照** 一定策略 申请一个新大小的数组,将原来数组元素全部移至新数组。当数组空元素过多时,可以按照 一定策略 **缩小数组长度,这个策略取决于你的需求和应用场景。

容器&泛型

通常我们在使用容器的时候,需要的是一个类型统一的容器,即如果我插入了一个猫元素,那么我绝对不希望一只蟑螂被插入进去。我需要的是一个“猫”的容器而不是其他的,在Java SE5之前容器就存在着能向“猫”的容器中插入“蟑螂”的问题。但是说实话,《Thinking in java》作者也说了,在泛型没有出来之前,程序员会经常犯这种错误吗?不见得,在这种场景下泛型只是将错误提前在编译期就告知用户。但是在没有泛型之前,一个代码风格良好的写法应该是:

List cat = new ArrayList();

如此明显的一个cat集合你会插入什么其他奇奇怪怪的东西吗?所以说泛型之于容器是类型安全,但是泛型出现更重要的一个目的是 ** 让程序员编写更加通用的代码 **。

上面的话可能说的有些繁琐,请容我再整理一下:在很多实用容器的场景中,我们希望在未使用容器钱,容器容纳类型不确定,但是在放入一个类型后,我们只能使用该类型,泛型非常适合应用在这个场景。使用泛型可以让运行期的错误在编译器就被阻止。下面来个简单的例子来说明一下:

//<> 尖括号内的是类型参数
List<String> strList = new ArrayList<String>();
strList.add(1);//编译错误

List anotherList = new ArrayList();
anotherList.add(1);
anotherList.add("cat");//未检查警告,但是此操作不会报错

让我们用更直观的形式看一下这样操作的的结果:

情况如何

很明显使用泛型能够有效的避免将错误类型对象放置到容器中,但是关于泛型的使用不仅于此,更多的讨论放到文末。

迭代器

在《Thiniking in Java》中说到,任何容器类,都必须有某种方式可以插入元素并将它们再次取回(当然在某些书中你可能听说过bag这种只放入的容器,但是现在无需纠结这些东西)。

        List<Integer> integerList = new ArrayList<Integer>();
        for (int i = 0; i < 10; i++) {
            integerList.add(i);
        }

        Iterator<Integer> i = integerList.iterator();
        while (i.hasNext()) {
            Integer j = i.next();
            System.out.println("i-->" + j);
        }

以上是一个存放了0-9个值的集合,现在我们利用迭代器打印每个元素的值,先结果如下:

输出结果

LinkedList

其实我还有很多没提到的东西,但是这是初探容器啊肯定还会有再探容器啊

Java里的容器我用的比较多的大概就是HashMap、ArrayList和LinkedList,其中ArrayList用的最多。其实ArrayList和LinkedList都可以被理解为“链表”,只不过LinkedList更偏向于我们所熟知的“指针链表”,而ArrayList可以将之理解为内部是数组的链表。说实话,数组自带链子啊~

好了看上面的标题也该知道,我是不打算在这对其他的容器做更多的介绍的,大标题叫初探容器,说明我还会有再探容器的,那时再做更详细的介绍。最近看到一个有趣的东西,你输入一个"(1+(2-1))"这种格式的算是,可以用栈算出来,于是乎自己用jdk自带的LinkedList封装了一个stack,把那玩意做了出来,但是感觉不爽,因为链表用的是现成的,简单的封装也没什么难度。我不管我就要从Node开始搞一个Java的“指针链表”。

package thinking.Generator.other;

import java.util.NoSuchElementException;

/**
 * Created by luo-pc on 2016/9/30.
 * desc:一个双向链表
 */
public class LinkList<T> {

    /**
     * 链表的大小
     */
    int size = 0;

    /**
     * 表头
     */
    Node<T> first;

    /**
     * 表尾
     */
    Node<T> last;

    /**
     * 构造一个空的链表
     */
    public LinkList() {
    }

    public boolean add(T t) {
        insertToLast(t);
        return true;
    }

    /**
     * 在表尾插入一个元素
     *
     * @param t 元素值
     */
    private void insertToLast(T t) {
        //表尾元素
        Node<T> n = last;
        //新建一个指向表尾的元素
        Node<T> newNode = new Node<T>(n, t, null);
        //让表尾指向新元素
        last = newNode;
        //如果链表为空
        if (n == null)
            first = newNode;
        else
            n.next = newNode;
        size++;

    }

    /**
     * 获取表尾元素
     *
     * @return 返回表尾元素的值
     */
    public T getLast() {
        Node<T> n = last;
        if (n == null)
            throw new NoSuchElementException();
        return n.item;
    }

    /**
     * 根据索引获取对应元素的值
     *
     * @param index 索引
     * @return T 值
     */
    public T get(int index) {
        return node(index).item;
    }

    /**
     * 根据索引查找元素
     *
     * @param index 索引
     */
    Node<T> node(int index) {
        if (index < (size >> 1)) {
            Node<T> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            Node<T> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.pre;
            return x;
        }
    }


    public T remove(int index) {
        checkNodeIndex(index);
        return unlink(node(index));
    }

    private void checkNodeIndex(int index) {
        if (!isNodeIndex(index))
            throw new IndexOutOfBoundsException("Index:" + index + "Size:" + size);
    }

    T unlink(Node<T> x) {
        T value = x.item;
        Node<T> next = x.next;
        Node<T> prev = x.pre;

        if (prev == null) {
            first = next;
        } else {
            prev.next = next;
            x.pre = null;
        }

        if (next == null) {
            last = prev;
        } else {
            next.pre = prev;
            x.next = null;
        }

        x.item = null;
        size--;
        return value;
    }

    /**
     * 检查该索引是否是该链表内的元素
     *
     * @param index 索引
     * @return 是或者不是
     */
    public boolean isNodeIndex(int index) {
        return index >= 0 && index < size;
    }

    /**
     * 遍历并打印链表
     */
    public void travelList() {
        Node travelNode = first;
        if (travelNode == null)
            return;
        while (travelNode.next != null) {
            System.out.println(travelNode.item);
            travelNode = travelNode.next;
        }
    }

    public int size(){
        return this.size;
    }

    //--------------------------------Node-------------------------------//

    /**
     * 链表单个元素的类型
     *
     * @param <T>
     */
    private static class Node<T> {
        T item;
        Node<T> next;
        Node<T> pre;

        Node(Node<T> prev, T element, Node<T> next) {
            this.item = element;
            this.next = next;
            this.pre = prev;
        }
    }

    public static void main(String[] args) {
        LinkList<String> strList = new LinkList<>();
        strList.add("str1");
        strList.add("str2");
        strList.add("str3");
        strList.add("str4");

        strList.travelList();

    }
}

我这实现了一个双向链表,操作起来方便一点,实现的时候参考了一下Java的LinkedList。底下的main()函数仅仅作为测试之用。接下来简单的封装一下,将LinkedList封装为一个栈:

package thinking.Generator.other;

import java.util.NoSuchElementException;

/**
 * Created by luo-pc on 2016/10/1.
 * desc:
 */
@SuppressWarnings("unused")
public class MyStack<T> {
    private LinkList<T> stack;

    public MyStack() {
        stack = new LinkList<T>();
    }

    /**
     * 弹出栈顶元素并返回
     */
    public T pop() {
        if (stack.size() > 0) {
            return stack.remove(stack.size() - 1);
        } else {
            throw new NoSuchElementException();
        }
    }

    /**
     * 入栈操作
     *
     * @param t 入栈的值
     * @return 操作结果
     */
    public boolean push(T t) {
        return stack.add(t);
    }

    /**
     * 返回栈顶元素的值但是不弹出他
     */
    public T peek() {
        return stack.getLast();
    }

    public boolean isEmpty() {
        return stack.size == 0;
    }

    public int size() {
        return stack.size();
    }
}

好了前期工作准备好了,那我们来看一下该怎么算。一般的算术表达式我们可以通过二叉树的遍历来将其改造成中缀表达式从而用栈来算出其结果,但是上面的算式不用那么麻烦,因为有左右括号,按照以下规则预算就成了:

  • 将操作数压入操作数栈
  • 将运算符压入运算符栈
  • 忽略左括号
  • 在遇到右括号时,弹出一个运算符,弹出所需数量的操作数,并将运算符和操作数的运算结果压入操作数栈

好了算法也有了,搞起来!

    public static void main(String[] args) {
        /**
         * 关于表达式(由括号、运算符和操作数组成)处理的规则:
         * 1.将操作数压入栈
         * 2.将运算符压入运算符栈
         * 3.忽略左括号
         * 4.在遇到右括号时弹出一个运算符,弹出所需数量的操作数,并将
         * 运算符和操作数的运算结果压入操作数的栈
         */
        //操作数栈
        MyStack<Double> integerStack = new MyStack<>();
        //运算符栈
        MyStack<Character> opStack = new MyStack<>();

        String integers = "0123456789";
        String a = "(1+((2+3)*(4*5)))";
        char[] b = new char[a.length()];
        for (int i = 0; i < b.length; i++) {
            b[i] = a.charAt(i);
        }
        for (char i : b) {
            //1.将操作数压入栈
            if (contain(integers, i)) {
                integerStack.push(Double.parseDouble("" + i));
            } else if (i == '+' || i == '-' || i == '*' || i == '/') {//2.将操作符压入栈
                opStack.push(i);
            } else if (i == '(') {//3.忽略左括号
            } else if (i == ')') {//4.运算
                double opt1 = integerStack.pop();
                double opt2 = integerStack.pop();
                double opt3 = 0.0;
                char operator = opStack.pop();
                if (operator == '+') {
                    opt3 = opt1 + opt2;
                    System.out.println(opt1 + "+" + opt2 + "=" + opt3);
                }
                if (operator == '-') {
                    opt3 = opt1 - opt2;
                    System.out.println(opt1 + "-" + opt2 + "=" + opt3);
                }
                if (operator == '*') {
                    opt3 = opt1 * opt2;
                    System.out.println(opt1 + "*" + opt2 + "=" + opt3);

                }
                if (operator == '/') {
                    opt3 = opt1 / opt2;
                    System.out.println(opt1 + "/" + opt2 + "=" + opt3);
                }
                integerStack.push(opt3);
            }
        }
        System.out.println("the result is:" + integerStack.peek());
    }

    public static boolean contain(String s, char a) {
        char[] str = new char[s.length()];
        for (int i = 0; i < s.length(); i++) {
            str[i] = s.charAt(i);
        }
        for (char i : str) {
            if (a == i)
                return true;
        }
        return false;
    }

看下结果~

看下结果

结果是不是很有趣~好了这次的复习就先到这,等到下次再探容器的时候再好好的了解一下容器。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,372评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,368评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,415评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,157评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,171评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,125评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,028评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,887评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,310评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,533评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,690评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,411评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,004评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,812评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,693评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,577评论 2 353

推荐阅读更多精彩内容

  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,239评论 11 349
  • 在经过一次没有准备的面试后,发现自己虽然写了两年的android代码,基础知识却忘的差不多了。这是程序员的大忌,没...
    猿来如痴阅读 2,838评论 3 10
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,622评论 18 399
  • 文/怪鸭帆 书法是中国的国粹,是点画和线条的艺术。在古时候,无论你满腹经纶,才高八斗,没有一手好字是不能被认为是才...
    怪鸭帆阅读 465评论 2 8
  • 斗鱼既然已有节日主题礼物,那么网站导航栏也可以有节日主题背景图(皮肤)。为了强化鲨鱼和鲨鱼娘的平台品牌形象,在设计...
    missly117阅读 167评论 0 0